1<?php
2/*
3 * Set tabs to 4 for best viewing.
4 *
5 * Latest version is available at https://adodb.org/
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   v5.21.0  2021-02-27
18	@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
19	@copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
20
21	Released under both BSD license and Lesser GPL library license. You can choose which license
22	you prefer.
23
24	PHP's database access functions are not standardised. This creates a need for a database
25	class library to hide the differences between the different database API's (encapsulate
26	the differences) so we can easily switch databases.
27
28	We currently support MySQL, Oracle, Microsoft SQL Server, Sybase, Sybase SQL Anywhere, DB2,
29	Informix, PostgreSQL, FrontBase, Interbase (Firebird and Borland variants), Foxpro, Access,
30	ADO, SAP DB, SQLite and ODBC. We have had successful reports of connecting to Progress and
31	other databases via ODBC.
32 */
33
34if (!defined('_ADODB_LAYER')) {
35	define('_ADODB_LAYER',1);
36
37	// The ADOdb extension is no longer maintained and effectively unsupported
38	// since v5.04. The library will not function properly if it is present.
39	if(defined('ADODB_EXTENSION')) {
40		$msg = "Unsupported ADOdb Extension (v" . ADODB_EXTENSION . ") detected! "
41			. "Disable it to use ADOdb";
42
43		$errorfn = defined('ADODB_ERROR_HANDLER') ? ADODB_ERROR_HANDLER : false;
44		if ($errorfn) {
45			$conn = false;
46			$errorfn('ADOdb', basename(__FILE__), -9999, $msg, null, null, $conn);
47		} else {
48			die($msg . PHP_EOL);
49		}
50	}
51
52	//==============================================================================================
53	// CONSTANT DEFINITIONS
54	//==============================================================================================
55
56
57	/**
58	 * Set ADODB_DIR to the directory where this file resides...
59	 * This constant was formerly called $ADODB_RootPath
60	 */
61	if (!defined('ADODB_DIR')) {
62		define('ADODB_DIR',dirname(__FILE__));
63	}
64
65	//==============================================================================================
66	// GLOBAL VARIABLES
67	//==============================================================================================
68
69	GLOBAL
70		$ADODB_vers,		// database version
71		$ADODB_COUNTRECS,	// count number of records returned - slows down query
72		$ADODB_CACHE_DIR,	// directory to cache recordsets
73		$ADODB_CACHE,
74		$ADODB_CACHE_CLASS,
75		$ADODB_COMPAT_FETCH, // If $ADODB_COUNTRECS and this is true, $rs->fields is available on EOF
76		$ADODB_FETCH_MODE,	// DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default...
77		$ADODB_GETONE_EOF,
78		$ADODB_QUOTE_FIELDNAMES; // Allows you to force quotes (backticks) around field names in queries generated by getinsertsql and getupdatesql.
79
80	//==============================================================================================
81	// GLOBAL SETUP
82	//==============================================================================================
83
84	/*********************************************************
85	* Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3).
86	* Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi
87	* @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:adodb_force_type
88	*
89	* 0 = ignore empty fields. All empty fields in array are ignored.
90	* 1 = force null. All empty, php null and string 'null' fields are
91	*     changed to sql NULL values.
92	* 2 = force empty. All empty, php null and string 'null' fields are
93	*     changed to sql empty '' or 0 values.
94	* 3 = force value. Value is left as it is. Php null and string 'null'
95	*     are set to sql NULL values and empty fields '' are set to empty '' sql values.
96	* 4 = force value. Like 1 but numeric empty fields are set to zero.
97    */
98		define('ADODB_FORCE_IGNORE',0);
99		define('ADODB_FORCE_NULL',1);
100		define('ADODB_FORCE_EMPTY',2);
101		define('ADODB_FORCE_VALUE',3);
102		define('ADODB_FORCE_NULL_AND_ZERO',4);
103	// ********************************************************
104
105
106	/**
107	 * Constants for returned values from the charMax and textMax methods.
108	 * If not specifically defined in the driver, methods return the NOTSET value.
109	 */
110	define ('ADODB_STRINGMAX_NOTSET', -1);
111	define ('ADODB_STRINGMAX_NOLIMIT',-2);
112
113	/*
114	* Defines the the default meta type returned
115	* when ADOdb encounters a type that it is not
116	* defined in the metaTypes.
117	*/
118	if (!defined('ADODB_DEFAULT_METATYPE'))
119		define ('ADODB_DEFAULT_METATYPE','N');
120
121	define('ADODB_BAD_RS','<p>Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;</p>');
122
123	// allow [ ] @ ` " and . in table names
124	define('ADODB_TABLE_REGEX','([]0-9a-z_\:\"\`\.\@\[-]*)');
125
126	// prefetching used by oracle
127	if (!defined('ADODB_PREFETCH_ROWS')) {
128		define('ADODB_PREFETCH_ROWS',10);
129	}
130
131
132	/**
133	 * Fetch mode
134	 *
135	 * Set global variable $ADODB_FETCH_MODE to one of these constants or use
136	 * the SetFetchMode() method to control how recordset fields are returned
137	 * when fetching data.
138	 *
139	 *   - NUM:     array()
140	 *   - ASSOC:   array('id' => 456, 'name' => 'john')
141	 *   - BOTH:    array(0 => 456, 'id' => 456, 1 => 'john', 'name' => 'john')
142	 *   - DEFAULT: driver-dependent
143	 */
144	define('ADODB_FETCH_DEFAULT', 0);
145	define('ADODB_FETCH_NUM', 1);
146	define('ADODB_FETCH_ASSOC', 2);
147	define('ADODB_FETCH_BOTH', 3);
148
149	/**
150	 * Associative array case constants
151	 *
152	 * By defining the ADODB_ASSOC_CASE constant to one of these values, it is
153	 * possible to control the case of field names (associative array's keys)
154	 * when operating in ADODB_FETCH_ASSOC fetch mode.
155	 *   - LOWER:  $rs->fields['orderid']
156	 *   - UPPER:  $rs->fields['ORDERID']
157	 *   - NATIVE: $rs->fields['OrderID'] (or whatever the RDBMS will return)
158	 *
159	 * The default is to use native case-names.
160	 *
161	 * NOTE: This functionality is not implemented everywhere, it currently
162	 * works only with: mssql, odbc, oci8 and ibase derived drivers
163	 */
164	define('ADODB_ASSOC_CASE_LOWER', 0);
165	define('ADODB_ASSOC_CASE_UPPER', 1);
166	define('ADODB_ASSOC_CASE_NATIVE', 2);
167
168
169	if (!defined('TIMESTAMP_FIRST_YEAR')) {
170		define('TIMESTAMP_FIRST_YEAR',100);
171	}
172
173	/**
174	 * AutoExecute constants
175	 * (moved from adodb-pear.inc.php since they are only used in here)
176	 */
177	define('DB_AUTOQUERY_INSERT', 1);
178	define('DB_AUTOQUERY_UPDATE', 2);
179
180
181
182	function ADODB_Setup() {
183	GLOBAL
184		$ADODB_vers,		// database version
185		$ADODB_COUNTRECS,	// count number of records returned - slows down query
186		$ADODB_CACHE_DIR,	// directory to cache recordsets
187		$ADODB_FETCH_MODE,
188		$ADODB_CACHE,
189		$ADODB_CACHE_CLASS,
190		$ADODB_FORCE_TYPE,
191		$ADODB_GETONE_EOF,
192		$ADODB_QUOTE_FIELDNAMES;
193
194		if (empty($ADODB_CACHE_CLASS)) {
195			$ADODB_CACHE_CLASS =  'ADODB_Cache_File' ;
196		}
197		$ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT;
198		$ADODB_FORCE_TYPE = ADODB_FORCE_VALUE;
199		$ADODB_GETONE_EOF = null;
200
201		if (!isset($ADODB_CACHE_DIR)) {
202			$ADODB_CACHE_DIR = '/tmp'; //(isset($_ENV['TMP'])) ? $_ENV['TMP'] : '/tmp';
203		} else {
204			// do not accept url based paths, eg. http:/ or ftp:/
205			if (strpos($ADODB_CACHE_DIR,'://') !== false) {
206				die("Illegal path http:// or ftp://");
207			}
208		}
209
210		/**
211		 * ADODB version as a string.
212		 */
213		$ADODB_vers = 'v5.21.0  2021-02-27';
214
215		/**
216		 * Determines whether recordset->RecordCount() is used.
217		 * Set to false for highest performance -- RecordCount() will always return -1 then
218		 * for databases that provide "virtual" recordcounts...
219		 */
220		if (!isset($ADODB_COUNTRECS)) {
221			$ADODB_COUNTRECS = true;
222		}
223	}
224
225
226	//==============================================================================================
227	// CHANGE NOTHING BELOW UNLESS YOU ARE DESIGNING ADODB
228	//==============================================================================================
229
230	ADODB_Setup();
231
232	//==============================================================================================
233	// CLASS ADOFieldObject
234	//==============================================================================================
235	/**
236	 * Helper class for FetchFields -- holds info on a column
237	 */
238	class ADOFieldObject {
239		var $name = '';
240		var $max_length=0;
241		var $type="";
242/*
243		// additional fields by dannym... (danny_milo@yahoo.com)
244		var $not_null = false;
245		// actually, this has already been built-in in the postgres, fbsql AND mysql module? ^-^
246		// so we can as well make not_null standard (leaving it at "false" does not harm anyways)
247
248		var $has_default = false; // this one I have done only in mysql and postgres for now ...
249			// others to come (dannym)
250		var $default_value; // default, if any, and supported. Check has_default first.
251*/
252	}
253
254
255	function _adodb_safedate($s) {
256		return str_replace(array("'", '\\'), '', $s);
257	}
258
259	// parse date string to prevent injection attack
260	// date string will have one quote at beginning e.g. '3434343'
261	function _adodb_safedateq($s) {
262		$len = strlen($s);
263		if ($s[0] !== "'") {
264			$s2 = "'".$s[0];
265		} else {
266			$s2 = "'";
267		}
268		for($i=1; $i<$len; $i++) {
269			$ch = $s[$i];
270			if ($ch === '\\') {
271				$s2 .= "'";
272				break;
273			} elseif ($ch === "'") {
274				$s2 .= $ch;
275				break;
276			}
277
278			$s2 .= $ch;
279		}
280
281		return strlen($s2) == 0 ? 'null' : $s2;
282	}
283
284
285	// for transaction handling
286
287	function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) {
288		//print "Errorno ($fn errno=$errno m=$errmsg) ";
289		$thisConnection->_transOK = false;
290		if ($thisConnection->_oldRaiseFn) {
291			$errfn = $thisConnection->_oldRaiseFn;
292			$errfn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection);
293		}
294	}
295
296	//------------------
297	// class for caching
298	class ADODB_Cache_File {
299
300		var $createdir = true; // requires creation of temp dirs
301
302		function __construct() {
303			global $ADODB_INCLUDED_CSV;
304			if (empty($ADODB_INCLUDED_CSV)) {
305				include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
306			}
307		}
308
309		// write serialised recordset to cache item/file
310		function writecache($filename, $contents,  $debug, $secs2cache) {
311			return adodb_write_file($filename, $contents,$debug);
312		}
313
314		// load serialised recordset and unserialise it
315		function &readcache($filename, &$err, $secs2cache, $rsClass) {
316			$rs = csv2rs($filename,$err,$secs2cache,$rsClass);
317			return $rs;
318		}
319
320		// flush all items in cache
321		function flushall($debug=false) {
322			global $ADODB_CACHE_DIR;
323
324			$rez = false;
325
326			if (strlen($ADODB_CACHE_DIR) > 1) {
327				$rez = $this->_dirFlush($ADODB_CACHE_DIR);
328				if ($debug) {
329					ADOConnection::outp( "flushall: $ADODB_CACHE_DIR<br><pre>\n". $rez."</pre>");
330				}
331			}
332			return $rez;
333		}
334
335		// flush one file in cache
336		function flushcache($f, $debug=false) {
337			if (!@unlink($f)) {
338				if ($debug) {
339					ADOConnection::outp( "flushcache: failed for $f");
340				}
341			}
342		}
343
344		function getdirname($hash) {
345			global $ADODB_CACHE_DIR;
346			if (!isset($this->notSafeMode)) {
347				$this->notSafeMode = !ini_get('safe_mode');
348			}
349			return ($this->notSafeMode) ? $ADODB_CACHE_DIR.'/'.substr($hash,0,2) : $ADODB_CACHE_DIR;
350		}
351
352		// create temp directories
353		function createdir($hash, $debug) {
354			global $ADODB_CACHE_PERMS;
355
356			$dir = $this->getdirname($hash);
357			if ($this->notSafeMode && !file_exists($dir)) {
358				$oldu = umask(0);
359				if (!@mkdir($dir, empty($ADODB_CACHE_PERMS) ? 0771 : $ADODB_CACHE_PERMS)) {
360					if(!is_dir($dir) && $debug) {
361						ADOConnection::outp("Cannot create $dir");
362					}
363				}
364				umask($oldu);
365			}
366
367			return $dir;
368		}
369
370		/**
371		* Private function to erase all of the files and subdirectories in a directory.
372		*
373		* Just specify the directory, and tell it if you want to delete the directory or just clear it out.
374		* Note: $kill_top_level is used internally in the function to flush subdirectories.
375		*/
376		function _dirFlush($dir, $kill_top_level = false) {
377			if(!$dh = @opendir($dir)) return;
378
379			while (($obj = readdir($dh))) {
380				if($obj=='.' || $obj=='..') continue;
381				$f = $dir.'/'.$obj;
382
383				if (strpos($obj,'.cache')) {
384					@unlink($f);
385				}
386				if (is_dir($f)) {
387					$this->_dirFlush($f, true);
388				}
389			}
390			if ($kill_top_level === true) {
391				@rmdir($dir);
392			}
393			return true;
394		}
395	}
396
397	//==============================================================================================
398	// CLASS ADOConnection
399	//==============================================================================================
400
401	/**
402	 * Connection object. For connecting to databases, and executing queries.
403	 */
404	abstract class ADOConnection {
405	//
406	// PUBLIC VARS
407	//
408	var $dataProvider = 'native';
409	var $databaseType = '';		/// RDBMS currently in use, eg. odbc, mysql, mssql
410	var $database = '';			/// Name of database to be used.
411	var $host = '';				/// The hostname of the database server
412	var $port = '';				/// The port of the database server
413	var $user = '';				/// The username which is used to connect to the database server.
414	var $password = '';			/// Password for the username. For security, we no longer store it.
415	var $debug = false;			/// if set to true will output sql statements
416	var $maxblobsize = 262144;	/// maximum size of blobs or large text fields (262144 = 256K)-- some db's die otherwise like foxpro
417	var $concat_operator = '+'; /// default concat operator -- change to || for Oracle/Interbase
418	var $substr = 'substr';		/// substring operator
419	var $length = 'length';		/// string length ofperator
420	var $random = 'rand()';		/// random function
421	var $upperCase = 'upper';		/// uppercase function
422	var $fmtDate = "'Y-m-d'";	/// used by DBDate() as the default date format used by the database
423	var $fmtTimeStamp = "'Y-m-d, h:i:s A'"; /// used by DBTimeStamp as the default timestamp fmt.
424	var $true = '1';			/// string that represents TRUE for a database
425	var $false = '0';			/// string that represents FALSE for a database
426	var $replaceQuote = "\\'";	/// string to use to replace quotes
427	var $nameQuote = '"';		/// string to use to quote identifiers and names
428	var $leftBracket = '[';		/// left square bracked for t-sql styled column names
429	var $rightBracket = ']';	/// right square bracked for t-sql styled column names
430	var $charSet=false;			/// character set to use - only for interbase, postgres and oci8
431	var $metaDatabasesSQL = '';
432	var $metaTablesSQL = '';
433	var $uniqueOrderBy = false; /// All order by columns have to be unique
434	var $emptyDate = '&nbsp;';
435	var $emptyTimeStamp = '&nbsp;';
436	var $lastInsID = false;
437	//--
438	var $hasInsertID = false;		/// supports autoincrement ID?
439	var $hasAffectedRows = false;	/// supports affected rows for update/delete?
440	var $hasTop = false;			/// support mssql/access SELECT TOP 10 * FROM TABLE
441	var $hasLimit = false;			/// support pgsql/mysql SELECT * FROM TABLE LIMIT 10
442	var $readOnly = false;			/// this is a readonly database - used by phpLens
443	var $hasMoveFirst = false;		/// has ability to run MoveFirst(), scrolling backwards
444	var $hasGenID = false;			/// can generate sequences using GenID();
445	var $hasTransactions = true;	/// has transactions
446	//--
447	var $genID = 0;					/// sequence id used by GenID();
448
449	/** @var bool|callable Error function to call */
450	var $raiseErrorFn = false;
451
452	var $isoDates = false;			/// accepts dates in ISO format
453	var $cacheSecs = 3600;			/// cache for 1 hour
454
455	// memcache
456	var $memCache = false; /// should we use memCache instead of caching in files
457	var $memCacheHost; /// memCache host
458	var $memCachePort = 11211; /// memCache port
459	var $memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib, not supported w/memcached library)
460
461	var $sysDate = false; /// name of function that returns the current date
462	var $sysTimeStamp = false; /// name of function that returns the current timestamp
463	var $sysUTimeStamp = false; // name of function that returns the current timestamp accurate to the microsecond or nearest fraction
464	var $arrayClass = 'ADORecordSet_array'; /// name of class used to generate array recordsets, which are pre-downloaded recordsets
465
466	var $noNullStrings = false; /// oracle specific stuff - if true ensures that '' is converted to ' '
467	var $numCacheHits = 0;
468	var $numCacheMisses = 0;
469	var $pageExecuteCountRows = true;
470	var $uniqueSort = false; /// indicates that all fields in order by must be unique
471	var $leftOuter = false; /// operator to use for left outer join in WHERE clause
472	var $rightOuter = false; /// operator to use for right outer join in WHERE clause
473	var $ansiOuter = false; /// whether ansi outer join syntax supported
474	var $autoRollback = false; // autoRollback on PConnect().
475	var $poorAffectedRows = false; // affectedRows not working or unreliable
476
477	/** @var bool|callable Execute function to call */
478	var $fnExecute = false;
479
480	/** @var bool|callable Cache execution function to call */
481	var $fnCacheExecute = false;
482
483	var $blobEncodeType = false; // false=not required, 'I'=encode to integer, 'C'=encode to char
484	var $rsPrefix = "ADORecordSet_";
485
486	var $autoCommit = true;		/// do not modify this yourself - actually private
487	var $transOff = 0;			/// temporarily disable transactions
488	var $transCnt = 0;			/// count of nested transactions
489
490	var $fetchMode=false;
491
492	var $null2null = 'null'; // in autoexecute/getinsertsql/getupdatesql, this value will be converted to a null
493	var $bulkBind = false; // enable 2D Execute array
494	//
495	// PRIVATE VARS
496	//
497	var $_oldRaiseFn =  false;
498	var $_transOK = null;
499	var $_connectionID	= false;	/// The returned link identifier whenever a successful database connection is made.
500	var $_errorMsg = false;		/// A variable which was used to keep the returned last error message.  The value will
501								/// then returned by the errorMsg() function
502	var $_errorCode = false;	/// Last error code, not guaranteed to be used - only by oci8
503	var $_queryID = false;		/// This variable keeps the last created result link identifier
504
505	var $_isPersistentConnection = false;	/// A boolean variable to state whether its a persistent connection or normal connection.	*/
506	var $_bindInputArray = false; /// set to true if ADOConnection.Execute() permits binding of array parameters.
507	var $_evalAll = false;
508	var $_affected = false;
509	var $_logsql = false;
510	var $_transmode = ''; // transaction mode
511
512
513	/**
514	 * Default Constructor.
515	 * We define it even though it does not actually do anything. This avoids
516	 * getting a PHP Fatal error:  Cannot call constructor if a subclass tries
517	 * to call its parent constructor.
518	 */
519	public function __construct()
520	{
521	}
522
523	/*
524	 * Additional parameters that may be passed to drivers in the connect string
525	 * Driver must be coded to accept the parameters
526	 */
527	protected $connectionParameters = array();
528
529	/**
530	* Adds a parameter to the connection string.
531	*
532	* These parameters are added to the connection string when connecting,
533	* if the driver is coded to use it.
534	*
535	* @param	string	$parameter	The name of the parameter to set
536	* @param	string	$value		The value of the parameter
537	*
538	* @return null
539	*
540	* @example, for mssqlnative driver ('CharacterSet','UTF-8')
541	*/
542	final public function setConnectionParameter($parameter,$value) {
543
544		$this->connectionParameters[] = array($parameter=>$value);
545
546	}
547
548	/**
549	 * ADOdb version.
550	 *
551	 * @return string
552	 */
553	static function Version() {
554		global $ADODB_vers;
555
556		// Semantic Version number matching regex
557		$regex = '^[vV]?(\d+\.\d+\.\d+'         // Version number (X.Y.Z) with optional 'V'
558			. '(?:-(?:'                         // Optional preprod version: a '-'
559			. 'dev|'                            // followed by 'dev'
560			. '(?:(?:alpha|beta|rc)(?:\.\d+))'  // or a preprod suffix and version number
561			. '))?)(?:\s|$)';                   // Whitespace or end of string
562
563		if (!preg_match("/$regex/", $ADODB_vers, $matches)) {
564			// This should normally not happen... Return whatever is between the start
565			// of the string and the first whitespace (or the end of the string).
566			self::outp("Invalid version number: '$ADODB_vers'", 'Version');
567			$regex = '^[vV]?(.*?)(?:\s|$)';
568			preg_match("/$regex/", $ADODB_vers, $matches);
569		}
570		return $matches[1];
571	}
572
573	/**
574	 * Get server version info.
575	 *
576	 * @return string[] An array with 2 elements: $arr['string'] is the description string,
577	 *				 	and $arr[version] is the version (also a string).
578	 */
579	function ServerInfo() {
580		return array('description' => '', 'version' => '');
581	}
582
583	/**
584	 * Return true if connected to the database.
585	 *
586	 * @return bool
587	 */
588	function IsConnected() {
589		return !empty($this->_connectionID);
590	}
591
592	function _findvers($str) {
593		if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) {
594			return $arr[1];
595		} else {
596			return '';
597		}
598	}
599
600	/**
601	 * All error messages go through this bottleneck function.
602	 *
603	 * You can define your own handler by defining the function name in ADODB_OUTP.
604	 *
605	 * @param string $msg     Message to print
606	 * @param bool   $newline True to add a newline after printing $msg
607	 */
608	static function outp($msg,$newline=true) {
609		global $ADODB_FLUSH,$ADODB_OUTP;
610
611		if (defined('ADODB_OUTP')) {
612			$fn = ADODB_OUTP;
613			$fn($msg,$newline);
614			return;
615		} else if (isset($ADODB_OUTP)) {
616			call_user_func($ADODB_OUTP,$msg,$newline);
617			return;
618		}
619
620		if ($newline) {
621			$msg .= "<br>\n";
622		}
623
624		if (isset($_SERVER['HTTP_USER_AGENT']) || !$newline) {
625			echo $msg;
626		} else {
627			echo strip_tags($msg);
628		}
629
630
631		if (!empty($ADODB_FLUSH) && ob_get_length() !== false) {
632			flush(); //  do not flush if output buffering enabled - useless - thx to Jesse Mullan
633		}
634
635	}
636
637	/**
638	 * Return the database server's current date and time.
639	 * @return int|false
640	 */
641	function Time() {
642		$rs = $this->_Execute("select $this->sysTimeStamp");
643		if ($rs && !$rs->EOF) {
644			return $this->UnixTimeStamp(reset($rs->fields));
645		}
646
647		return false;
648	}
649
650	/**
651	 * Parses the hostname to extract the port.
652	 * Overwrites $this->host and $this->port, only if a port is specified.
653	 * The Hostname can be fully or partially qualified,
654	 * ie: "db.mydomain.com:5432" or "ldaps://ldap.mydomain.com:636"
655	 * Any specified scheme such as ldap:// or ldaps:// is maintained.
656	 */
657	protected function parseHostNameAndPort() {
658		$parsed_url = parse_url($this->host);
659		if (is_array($parsed_url) && isset($parsed_url['host']) && isset($parsed_url['port'])) {
660			if ( isset($parsed_url['scheme']) ) {
661				// If scheme is specified (ie: ldap:// or ldaps://, make sure we retain that.
662				$this->host = $parsed_url['scheme'] . "://" . $parsed_url['host'];
663			} else {
664				$this->host = $parsed_url['host'];
665			}
666			$this->port = $parsed_url['port'];
667		}
668	}
669
670	/**
671	 * Connect to database.
672	 *
673	 * @param string $argHostname     Host to connect to
674	 * @param string $argUsername     Userid to login
675	 * @param string $argPassword     Associated password
676	 * @param string $argDatabaseName Database name
677	 * @param bool   $forceNew        Force new connection
678	 *
679	 * @return bool
680	 */
681	function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false) {
682		if ($argHostname != "") {
683			$this->host = $argHostname;
684		}
685		// Overwrites $this->host and $this->port if a port is specified.
686		$this->parseHostNameAndPort();
687
688		if ($argUsername != "") {
689			$this->user = $argUsername;
690		}
691		if ($argPassword != "") {
692			$this->password = 'not stored'; // not stored for security reasons
693		}
694		if ($argDatabaseName != "") {
695			$this->database = $argDatabaseName;
696		}
697
698		$this->_isPersistentConnection = false;
699
700		if ($forceNew) {
701			if ($rez=$this->_nconnect($this->host, $this->user, $argPassword, $this->database)) {
702				return true;
703			}
704		} else {
705			if ($rez=$this->_connect($this->host, $this->user, $argPassword, $this->database)) {
706				return true;
707			}
708		}
709		if (isset($rez)) {
710			$err = $this->ErrorMsg();
711			$errno = $this->ErrorNo();
712			if (empty($err)) {
713				$err = "Connection error to server '$argHostname' with user '$argUsername'";
714			}
715		} else {
716			$err = "Missing extension for ".$this->dataProvider;
717			$errno = 0;
718		}
719		if ($fn = $this->raiseErrorFn) {
720			$fn($this->databaseType, 'CONNECT', $errno, $err, $this->host, $this->database, $this);
721		}
722
723		$this->_connectionID = false;
724		if ($this->debug) {
725			ADOConnection::outp( $this->host.': '.$err);
726		}
727		return false;
728	}
729
730	function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) {
731		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
732	}
733
734	/**
735	 * Always force a new connection to database.
736	 *
737	 * Currently this only works with Oracle.
738	 *
739	 * @param string $argHostname     Host to connect to
740	 * @param string $argUsername     Userid to login
741	 * @param string $argPassword     Associated password
742	 * @param string $argDatabaseName Database name
743	 *
744	 * @return bool
745	 */
746	function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") {
747		return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true);
748	}
749
750	/**
751	 * Establish persistent connection to database.
752	 *
753	 * @param string $argHostname     Host to connect to
754	 * @param string $argUsername     Userid to login
755	 * @param string $argPassword     Associated password
756	 * @param string $argDatabaseName Database name
757	 *
758	 * @return bool
759	 */
760	function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") {
761
762		if (defined('ADODB_NEVER_PERSIST')) {
763			return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName);
764		}
765
766		if ($argHostname != "") {
767			$this->host = $argHostname;
768		}
769		// Overwrites $this->host and $this->port if a port is specified.
770		$this->parseHostNameAndPort();
771
772		if ($argUsername != "") {
773			$this->user = $argUsername;
774		}
775		if ($argPassword != "") {
776			$this->password = 'not stored';
777		}
778		if ($argDatabaseName != "") {
779			$this->database = $argDatabaseName;
780		}
781
782		$this->_isPersistentConnection = true;
783
784		if ($rez = $this->_pconnect($this->host, $this->user, $argPassword, $this->database)) {
785			return true;
786		}
787		if (isset($rez)) {
788			$err = $this->ErrorMsg();
789			if (empty($err)) {
790				$err = "Connection error to server '$argHostname' with user '$argUsername'";
791			}
792			$ret = false;
793		} else {
794			$err = "Missing extension for ".$this->dataProvider;
795			$ret = false;
796		}
797		if ($fn = $this->raiseErrorFn) {
798			$fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this);
799		}
800
801		$this->_connectionID = false;
802		if ($this->debug) {
803			ADOConnection::outp( $this->host.': '.$err);
804		}
805		return $ret;
806	}
807
808	function outp_throw($msg,$src='WARN',$sql='') {
809		if (defined('ADODB_ERROR_HANDLER') &&  ADODB_ERROR_HANDLER == 'adodb_throw') {
810			adodb_throw($this->databaseType,$src,-9999,$msg,$sql,false,$this);
811			return;
812		}
813		ADOConnection::outp($msg);
814	}
815
816	/**
817	 * Create cache class.
818	 *
819	 * Code is backwards-compatible with old memcache implementation.
820	 */
821	function _CreateCache() {
822		global $ADODB_CACHE, $ADODB_CACHE_CLASS;
823
824		if ($this->memCache) {
825			global $ADODB_INCLUDED_MEMCACHE;
826
827			if (empty($ADODB_INCLUDED_MEMCACHE)) {
828				include_once(ADODB_DIR.'/adodb-memcache.lib.inc.php');
829			}
830			$ADODB_CACHE = new ADODB_Cache_MemCache($this);
831		} else {
832			$ADODB_CACHE = new $ADODB_CACHE_CLASS($this);
833		}
834	}
835
836	/**
837	 * Format date column in sql string.
838	 *
839	 * See https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:sqldate
840	 * for documentation on supported formats.
841	 *
842	 * @param string $fmt Format string
843	 * @param string $col Date column; use system date if not specified.
844	 */
845	function SQLDate($fmt, $col = '') {
846		if (!$col) {
847			$col = $this->sysDate;
848		}
849		return $col; // child class implement
850	}
851
852	/**
853	 * Prepare an sql statement and return the statement resource.
854	 *
855	 * For databases that do not support this, we return the $sql. To ensure
856	 * compatibility with databases that do not support prepare:
857	 *
858	 *   $stmt = $db->Prepare("insert into table (id, name) values (?,?)");
859	 *   $db->Execute($stmt,array(1,'Jill')) or die('insert failed');
860	 *   $db->Execute($stmt,array(2,'Joe')) or die('insert failed');
861	 *
862	 * @param string $sql SQL to send to database
863	 *
864	 * @return mixed|false The prepared statement, or the original sql if the
865	 *                     database does not support prepare.
866	 */
867	function Prepare($sql) {
868		return $sql;
869	}
870
871	/**
872	 * Prepare a Stored Procedure and return the statement resource.
873	 *
874	 * Some databases, eg. mssql require a different function for preparing
875	 * stored procedures. So we cannot use Prepare().
876	 *
877	 * For databases that do not support this, we return the $sql.
878	 *
879	 * @param string $sql   SQL to send to database
880	 * @param bool   $param
881	 *
882	 * @return mixed|false The prepared statement, or the original sql if the
883	 *                     database does not support prepare.
884	 */
885	function PrepareSP($sql,$param=true) {
886		return $this->Prepare($sql,$param);
887	}
888
889	/**
890	 * PEAR DB Compat - alias for qStr.
891	 * @param $s
892	 * @return string
893	 */
894	function Quote($s) {
895		return $this->qstr($s);
896	}
897
898	function q(&$s) {
899		//if (!empty($this->qNull && $s == 'null') {
900		//	return $s;
901		//}
902		$s = $this->qstr($s);
903	}
904
905	/**
906	* PEAR DB Compat - do not use internally.
907	*/
908	function ErrorNative() {
909		return $this->ErrorNo();
910	}
911
912
913	/**
914	 * PEAR DB Compat - do not use internally.
915	 */
916	function nextId($seq_name) {
917		return $this->GenID($seq_name);
918	}
919
920	/**
921	 * Lock a row, will escalate and lock the table if row locking not supported
922	 * will normally free the lock at the end of the transaction
923	 *
924	 * @param string $table	name of table to lock
925	 * @param string $where	where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock
926     * @param string $col
927	 */
928	function RowLock($table,$where,$col='1 as adodbignore') {
929		return false;
930	}
931
932	/**
933	 * @param string $table
934	 * @return true
935	 */
936	function CommitLock($table) {
937		return $this->CommitTrans();
938	}
939
940	/**
941	 * @param string $table
942	 * @return true
943	 */
944	function RollbackLock($table) {
945		return $this->RollbackTrans();
946	}
947
948	/**
949	* PEAR DB Compat - do not use internally.
950	*
951	* The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical
952	* for easy porting :-)
953	*
954	* @param int $mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM
955	*
956	* @return int Previous fetch mode
957	*/
958	function SetFetchMode($mode) {
959		$old = $this->fetchMode;
960		$this->fetchMode = $mode;
961
962		if ($old === false) {
963			global $ADODB_FETCH_MODE;
964			return $ADODB_FETCH_MODE;
965		}
966		return $old;
967	}
968
969
970	/**
971	* PEAR DB Compat - do not use internally.
972	 *
973	 * @param string     $sql
974	 * @param array|bool $inputarr
975	 *
976	 * @return ADORecordSet|bool
977	*/
978	function Query($sql, $inputarr=false) {
979		$rs = $this->Execute($sql, $inputarr);
980		if (!$rs && defined('ADODB_PEAR')) {
981			return ADODB_PEAR_Error();
982		}
983		return $rs;
984	}
985
986
987	/**
988	 * PEAR DB Compat - do not use internally
989	 */
990	function LimitQuery($sql, $offset, $count, $params=false) {
991		$rs = $this->SelectLimit($sql, $count, $offset, $params);
992		if (!$rs && defined('ADODB_PEAR')) {
993			return ADODB_PEAR_Error();
994		}
995		return $rs;
996	}
997
998
999	/**
1000	 * PEAR DB Compat - do not use internally
1001	 */
1002	function Disconnect() {
1003		return $this->Close();
1004	}
1005
1006	/**
1007	 * Returns a placeholder for query parameters.
1008	 *
1009	 * e.g. $DB->Param('a') will return
1010	 * - '?' for most databases
1011	 * - ':a' for Oracle
1012	 * - '$1', '$2', etc. for PostgreSQL
1013	 *
1014	 * @param mixed $name parameter's name.
1015	 *                    For databases that require positioned params (e.g. PostgreSQL),
1016	 *                    a "falsy" value can be used to force resetting the placeholder
1017	 *                    count; using boolean 'false' will reset it without actually
1018	 *                    returning a placeholder. ADOdb will also automatically reset
1019	 *                    the count when executing a query.
1020	 * @param string $type (unused)
1021	 * @return string query parameter placeholder
1022	 */
1023	function Param($name,$type='C') {
1024		return '?';
1025	}
1026
1027	/*
1028		InParameter and OutParameter are self-documenting versions of Parameter().
1029	*/
1030	function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) {
1031		return $this->Parameter($stmt,$var,$name,false,$maxLen,$type);
1032	}
1033
1034	/*
1035	*/
1036	function OutParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) {
1037		return $this->Parameter($stmt,$var,$name,true,$maxLen,$type);
1038
1039	}
1040
1041
1042	/*
1043	Usage in oracle
1044		$stmt = $db->Prepare('select * from table where id =:myid and group=:group');
1045		$db->Parameter($stmt,$id,'myid');
1046		$db->Parameter($stmt,$group,'group',64);
1047		$db->Execute();
1048
1049		@param $stmt Statement returned by Prepare() or PrepareSP().
1050		@param $var PHP variable to bind to
1051		@param $name Name of stored procedure variable name to bind to.
1052		@param [$isOutput] Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
1053		@param [$maxLen] Holds an maximum length of the variable.
1054		@param [$type] The data type of $var. Legal values depend on driver.
1055
1056	*/
1057	function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) {
1058		return false;
1059	}
1060
1061
1062	function IgnoreErrors($saveErrs=false) {
1063		if (!$saveErrs) {
1064			$saveErrs = array($this->raiseErrorFn,$this->_transOK);
1065			$this->raiseErrorFn = false;
1066			return $saveErrs;
1067		} else {
1068			$this->raiseErrorFn = $saveErrs[0];
1069			$this->_transOK = $saveErrs[1];
1070		}
1071	}
1072
1073	/**
1074	 * Improved method of initiating a transaction. Used together with CompleteTrans().
1075	 * Advantages include:
1076     *
1077	 * a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans.
1078	 *    Only the outermost block is treated as a transaction.<br>
1079	 * b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.<br>
1080	 * c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block
1081	 *    are disabled, making it backward compatible.
1082	 */
1083	function StartTrans($errfn = 'ADODB_TransMonitor') {
1084		if ($this->transOff > 0) {
1085			$this->transOff += 1;
1086			return true;
1087		}
1088
1089		$this->_oldRaiseFn = $this->raiseErrorFn;
1090		$this->raiseErrorFn = $errfn;
1091		$this->_transOK = true;
1092
1093		if ($this->debug && $this->transCnt > 0) {
1094			ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans");
1095		}
1096		$ok = $this->BeginTrans();
1097		$this->transOff = 1;
1098		return $ok;
1099	}
1100
1101
1102	/**
1103		Used together with StartTrans() to end a transaction. Monitors connection
1104		for sql errors, and will commit or rollback as appropriate.
1105
1106		@autoComplete if true, monitor sql errors and commit and rollback as appropriate,
1107		and if set to false force rollback even if no SQL error detected.
1108		@returns true on commit, false on rollback.
1109	*/
1110	function CompleteTrans($autoComplete = true) {
1111		if ($this->transOff > 1) {
1112			$this->transOff -= 1;
1113			return true;
1114		}
1115		$this->raiseErrorFn = $this->_oldRaiseFn;
1116
1117		$this->transOff = 0;
1118		if ($this->_transOK && $autoComplete) {
1119			if (!$this->CommitTrans()) {
1120				$this->_transOK = false;
1121				if ($this->debug) {
1122					ADOConnection::outp("Smart Commit failed");
1123				}
1124			} else {
1125				if ($this->debug) {
1126					ADOConnection::outp("Smart Commit occurred");
1127				}
1128			}
1129		} else {
1130			$this->_transOK = false;
1131			$this->RollbackTrans();
1132			if ($this->debug) {
1133				ADOCOnnection::outp("Smart Rollback occurred");
1134			}
1135		}
1136
1137		return $this->_transOK;
1138	}
1139
1140	/*
1141		At the end of a StartTrans/CompleteTrans block, perform a rollback.
1142	*/
1143	function FailTrans() {
1144		if ($this->debug)
1145			if ($this->transOff == 0) {
1146				ADOConnection::outp("FailTrans outside StartTrans/CompleteTrans");
1147			} else {
1148				ADOConnection::outp("FailTrans was called");
1149				adodb_backtrace();
1150			}
1151		$this->_transOK = false;
1152	}
1153
1154	/**
1155		Check if transaction has failed, only for Smart Transactions.
1156	*/
1157	function HasFailedTrans() {
1158		if ($this->transOff > 0) {
1159			return $this->_transOK == false;
1160		}
1161		return false;
1162	}
1163
1164	/**
1165	 * Execute SQL
1166	 *
1167	 * @param string     $sql      SQL statement to execute, or possibly an array
1168	 *                             holding prepared statement ($sql[0] will hold sql text)
1169	 * @param array|bool $inputarr holds the input data to bind to.
1170	 *                             Null elements will be set to null.
1171	 *
1172	 * @return ADORecordSet|bool
1173	 */
1174	public function Execute($sql, $inputarr = false) {
1175		if ($this->fnExecute) {
1176			$fn = $this->fnExecute;
1177			$ret = $fn($this,$sql,$inputarr);
1178			if (isset($ret)) {
1179				return $ret;
1180			}
1181		}
1182		if ($inputarr !== false) {
1183			if (!is_array($inputarr)) {
1184				$inputarr = array($inputarr);
1185			}
1186
1187			$element0 = reset($inputarr);
1188			# is_object check because oci8 descriptors can be passed in
1189			$array_2d = $this->bulkBind && is_array($element0) && !is_object(reset($element0));
1190
1191			//remove extra memory copy of input -mikefedyk
1192			unset($element0);
1193
1194			if (!is_array($sql) && !$this->_bindInputArray) {
1195				// @TODO this would consider a '?' within a string as a parameter...
1196				$sqlarr = explode('?',$sql);
1197				$nparams = sizeof($sqlarr)-1;
1198
1199				if (!$array_2d) {
1200					// When not Bind Bulk - convert to array of arguments list
1201					$inputarr = array($inputarr);
1202				} else {
1203					// Bulk bind - Make sure all list of params have the same number of elements
1204					$countElements = array_map('count', $inputarr);
1205					if (1 != count(array_unique($countElements))) {
1206						$this->outp_throw(
1207							"[bulk execute] Input array has different number of params  [" . print_r($countElements, true) .  "].",
1208							'Execute'
1209						);
1210						return false;
1211					}
1212					unset($countElements);
1213				}
1214				// Make sure the number of parameters provided in the input
1215				// array matches what the query expects
1216				$element0 = reset($inputarr);
1217				if ($nparams != count($element0)) {
1218					$this->outp_throw(
1219						"Input array has " . count($element0) .
1220						" params, does not match query: '" . htmlspecialchars($sql) . "'",
1221						'Execute'
1222					);
1223					return false;
1224				}
1225
1226				// clean memory
1227				unset($element0);
1228
1229				foreach($inputarr as $arr) {
1230					$sql = ''; $i = 0;
1231					foreach ($arr as $v) {
1232						$sql .= $sqlarr[$i];
1233						// from Ron Baldwin <ron.baldwin#sourceprose.com>
1234						// Only quote string types
1235						$typ = gettype($v);
1236						if ($typ == 'string') {
1237							//New memory copy of input created here -mikefedyk
1238							$sql .= $this->qstr($v);
1239						} else if ($typ == 'double') {
1240							$sql .= str_replace(',','.',$v); // locales fix so 1.1 does not get converted to 1,1
1241						} else if ($typ == 'boolean') {
1242							$sql .= $v ? $this->true : $this->false;
1243						} else if ($typ == 'object') {
1244							if (method_exists($v, '__toString')) {
1245								$sql .= $this->qstr($v->__toString());
1246							} else {
1247								$sql .= $this->qstr((string) $v);
1248							}
1249						} else if ($v === null) {
1250							$sql .= 'NULL';
1251						} else {
1252							$sql .= $v;
1253						}
1254						$i += 1;
1255
1256						if ($i == $nparams) {
1257							break;
1258						}
1259					} // while
1260					if (isset($sqlarr[$i])) {
1261						$sql .= $sqlarr[$i];
1262						if ($i+1 != sizeof($sqlarr)) {
1263							$this->outp_throw( "Input Array does not match ?: ".htmlspecialchars($sql),'Execute');
1264						}
1265					} else if ($i != sizeof($sqlarr)) {
1266						$this->outp_throw( "Input array does not match ?: ".htmlspecialchars($sql),'Execute');
1267					}
1268
1269					$ret = $this->_Execute($sql);
1270					if (!$ret) {
1271						return $ret;
1272					}
1273				}
1274			} else {
1275				if ($array_2d) {
1276					if (is_string($sql)) {
1277						$stmt = $this->Prepare($sql);
1278					} else {
1279						$stmt = $sql;
1280					}
1281
1282					foreach($inputarr as $arr) {
1283						$ret = $this->_Execute($stmt,$arr);
1284						if (!$ret) {
1285							return $ret;
1286						}
1287					}
1288				} else {
1289					$ret = $this->_Execute($sql,$inputarr);
1290				}
1291			}
1292		} else {
1293			$ret = $this->_Execute($sql,false);
1294		}
1295
1296		return $ret;
1297	}
1298
1299	function _Execute($sql,$inputarr=false) {
1300		// ExecuteCursor() may send non-string queries (such as arrays),
1301		// so we need to ignore those.
1302		if( is_string($sql) ) {
1303			// Strips keyword used to help generate SELECT COUNT(*) queries
1304			// from SQL if it exists.
1305			$sql = str_replace( '_ADODB_COUNT', '', $sql );
1306		}
1307
1308		if ($this->debug) {
1309			global $ADODB_INCLUDED_LIB;
1310			if (empty($ADODB_INCLUDED_LIB)) {
1311				include_once(ADODB_DIR.'/adodb-lib.inc.php');
1312			}
1313			$this->_queryID = _adodb_debug_execute($this, $sql,$inputarr);
1314		} else {
1315			$this->_queryID = @$this->_query($sql,$inputarr);
1316		}
1317
1318		// ************************
1319		// OK, query executed
1320		// ************************
1321
1322		// error handling if query fails
1323		if ($this->_queryID === false) {
1324			if ($this->debug == 99) {
1325				adodb_backtrace(true,5);
1326			}
1327			$fn = $this->raiseErrorFn;
1328			if ($fn) {
1329				$fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this);
1330			}
1331			return false;
1332		}
1333
1334		// return simplified recordset for inserts/updates/deletes with lower overhead
1335		if ($this->_queryID === true) {
1336			$rsclass = $this->rsPrefix.'empty';
1337			$rs = (class_exists($rsclass)) ? new $rsclass():  new ADORecordSet_empty();
1338
1339			return $rs;
1340		}
1341
1342		if ($this->dataProvider == 'pdo' && $this->databaseType != 'pdo') {
1343			// PDO uses a slightly different naming convention for the
1344			// recordset class if the database type is changed, so we must
1345			// treat it specifically. The mysql driver leaves the
1346			// databaseType as pdo
1347			$rsclass = $this->rsPrefix . 'pdo_' . $this->databaseType;
1348		} else {
1349			$rsclass = $this->rsPrefix . $this->databaseType;
1350		}
1351
1352		// return real recordset from select statement
1353		$rs = new $rsclass($this->_queryID,$this->fetchMode);
1354		$rs->connection = $this; // Pablo suggestion
1355		$rs->Init();
1356		if (is_array($sql)) {
1357			$rs->sql = $sql[0];
1358		} else {
1359			$rs->sql = $sql;
1360		}
1361		if ($rs->_numOfRows <= 0) {
1362			global $ADODB_COUNTRECS;
1363			if ($ADODB_COUNTRECS) {
1364				if (!$rs->EOF) {
1365					$rs = $this->_rs2rs($rs,-1,-1,!is_array($sql));
1366					$rs->_queryID = $this->_queryID;
1367				} else
1368					$rs->_numOfRows = 0;
1369			}
1370		}
1371		return $rs;
1372	}
1373
1374	function CreateSequence($seqname='adodbseq',$startID=1) {
1375		if (empty($this->_genSeqSQL)) {
1376			return false;
1377		}
1378		return $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
1379	}
1380
1381	function DropSequence($seqname='adodbseq') {
1382		if (empty($this->_dropSeqSQL)) {
1383			return false;
1384		}
1385		return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
1386	}
1387
1388	/**
1389	 * Generates a sequence id and stores it in $this->genID.
1390	 *
1391	 * GenID is only available if $this->hasGenID = true;
1392	 *
1393	 * @param string $seqname Name of sequence to use
1394	 * @param int    $startID If sequence does not exist, start at this ID
1395	 *
1396	 * @return int Sequence id, 0 if not supported
1397	 */
1398	function GenID($seqname='adodbseq',$startID=1) {
1399		if (!$this->hasGenID) {
1400			return 0; // formerly returns false pre 1.60
1401		}
1402
1403		$getnext = sprintf($this->_genIDSQL,$seqname);
1404
1405		$holdtransOK = $this->_transOK;
1406
1407		$save_handler = $this->raiseErrorFn;
1408		$this->raiseErrorFn = '';
1409		@($rs = $this->Execute($getnext));
1410		$this->raiseErrorFn = $save_handler;
1411
1412		if (!$rs) {
1413			$this->_transOK = $holdtransOK; //if the status was ok before reset
1414			$createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
1415			$rs = $this->Execute($getnext);
1416		}
1417		if ($rs && !$rs->EOF) {
1418			$this->genID = reset($rs->fields);
1419		} else {
1420			$this->genID = 0; // false
1421		}
1422
1423		if ($rs) {
1424			$rs->Close();
1425		}
1426
1427		return $this->genID;
1428	}
1429
1430	/**
1431	 * Returns the last inserted ID.
1432	 *
1433	 * Not all databases support this feature. Some do not require to specify
1434	 * table or column name (e.g. MySQL).
1435	 *
1436	 * @param string $table  Table name, default ''
1437	 * @param string $column Column name, default ''
1438	 *
1439	 * @return int The last inserted ID.
1440	 */
1441	function Insert_ID($table='',$column='') {
1442		if ($this->_logsql && $this->lastInsID) {
1443			return $this->lastInsID;
1444		}
1445		if ($this->hasInsertID) {
1446			return $this->_insertid($table,$column);
1447		}
1448		if ($this->debug) {
1449			ADOConnection::outp( '<p>Insert_ID error</p>');
1450			adodb_backtrace();
1451		}
1452		return false;
1453	}
1454
1455
1456	/**
1457	 * Portable Insert ID. Pablo Roca <pabloroca#mvps.org>
1458	 *
1459	 * @param string $table
1460	 * @param string $id
1461
1462	 * @return mixed The last inserted ID. All databases support this, but be
1463	 *               aware of possible problems in multiuser environments.
1464	 *               Heavily test this before deploying.
1465	 */
1466	function PO_Insert_ID($table="", $id="") {
1467		if ($this->hasInsertID){
1468			return $this->Insert_ID($table,$id);
1469		} else {
1470			return $this->GetOne("SELECT MAX($id) FROM $table");
1471		}
1472	}
1473
1474	/**
1475	* @return # rows affected by UPDATE/DELETE
1476	*/
1477	function Affected_Rows() {
1478		if ($this->hasAffectedRows) {
1479			if ($this->fnExecute === 'adodb_log_sql') {
1480				if ($this->_logsql && $this->_affected !== false) {
1481					return $this->_affected;
1482				}
1483			}
1484			$val = $this->_affectedrows();
1485			return ($val < 0) ? false : $val;
1486		}
1487
1488		if ($this->debug) {
1489			ADOConnection::outp( '<p>Affected_Rows error</p>',false);
1490		}
1491		return false;
1492	}
1493
1494
1495	/**
1496	 * @return string the last error message
1497	 */
1498	function ErrorMsg() {
1499		if ($this->_errorMsg) {
1500			return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg;
1501		} else {
1502			return '';
1503		}
1504	}
1505
1506
1507	/**
1508	 * @return int the last error number. Normally 0 means no error.
1509	 */
1510	function ErrorNo() {
1511		return ($this->_errorMsg) ? -1 : 0;
1512	}
1513
1514	function MetaError($err=false) {
1515		include_once(ADODB_DIR."/adodb-error.inc.php");
1516		if ($err === false) {
1517			$err = $this->ErrorNo();
1518		}
1519		return adodb_error($this->dataProvider,$this->databaseType,$err);
1520	}
1521
1522	function MetaErrorMsg($errno) {
1523		include_once(ADODB_DIR."/adodb-error.inc.php");
1524		return adodb_errormsg($errno);
1525	}
1526
1527	/**
1528	 * @returns an array with the primary key columns in it.
1529	 */
1530	function MetaPrimaryKeys($table, $owner=false) {
1531	// owner not used in base class - see oci8
1532		$p = array();
1533		$objs = $this->MetaColumns($table);
1534		if ($objs) {
1535			foreach($objs as $v) {
1536				if (!empty($v->primary_key)) {
1537					$p[] = $v->name;
1538				}
1539			}
1540		}
1541		if (sizeof($p)) {
1542			return $p;
1543		}
1544		if (function_exists('ADODB_VIEW_PRIMARYKEYS')) {
1545			return ADODB_VIEW_PRIMARYKEYS($this->databaseType, $this->database, $table, $owner);
1546		}
1547		return false;
1548	}
1549
1550	/**
1551	 * @returns assoc array where keys are tables, and values are foreign keys
1552	 */
1553	function MetaForeignKeys($table, $owner=false, $upper=false) {
1554		return false;
1555	}
1556	/**
1557	 * Choose a database to connect to. Many databases do not support this.
1558	 *
1559	 * @param string $dbName the name of the database to select
1560	 * @return bool
1561	 */
1562	function SelectDB($dbName) {return false;}
1563
1564
1565	/**
1566	 * Select a limited number of rows.
1567	 *
1568	 * Will select, getting rows from $offset (1-based), for $nrows.
1569	 * This simulates the MySQL "select * from table limit $offset,$nrows" , and
1570	 * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
1571	 * MySQL and PostgreSQL parameter ordering is the opposite of the other.
1572	 * eg.
1573	 *  SelectLimit('select * from table',3); will return rows 1 to 3 (1-based)
1574	 *  SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based)
1575	 *
1576	 * Uses SELECT TOP for Microsoft databases (when $this->hasTop is set)
1577	 * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set
1578	 *
1579	 * @param string     $sql
1580	 * @param int        $offset     Row to start calculations from (1-based)
1581	 * @param int        $nrows      Number of rows to get
1582	 * @param array|bool $inputarr   Array of bind variables
1583	 * @param int        $secs2cache Private parameter only used by jlim
1584	 *
1585	 * @return ADORecordSet The recordset ($rs->databaseType == 'array')
1586	 */
1587	function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) {
1588		$nrows = (int)$nrows;
1589		$offset = (int)$offset;
1590
1591		if ($this->hasTop && $nrows > 0) {
1592			// suggested by Reinhard Balling. Access requires top after distinct
1593			// Informix requires first before distinct - F Riosa
1594			$ismssql = (strpos($this->databaseType,'mssql') !== false);
1595			if ($ismssql) {
1596				$isaccess = false;
1597			} else {
1598				$isaccess = (strpos($this->databaseType,'access') !== false);
1599			}
1600
1601			if ($offset <= 0) {
1602				// access includes ties in result
1603				if ($isaccess) {
1604					$sql = preg_replace(
1605						'/(^\s*select\s+(distinctrow|distinct)?)/i',
1606						'\\1 '.$this->hasTop.' '.$nrows.' ',
1607						$sql
1608					);
1609
1610					if ($secs2cache != 0) {
1611						$ret = $this->CacheExecute($secs2cache, $sql,$inputarr);
1612					} else {
1613						$ret = $this->Execute($sql,$inputarr);
1614					}
1615					return $ret; // PHP5 fix
1616				} else if ($ismssql){
1617					$sql = preg_replace(
1618						'/(^\s*select\s+(distinctrow|distinct)?)/i',
1619						'\\1 '.$this->hasTop.' '.$nrows.' ',
1620						$sql
1621					);
1622				} else {
1623					$sql = preg_replace(
1624						'/(^\s*select\s)/i',
1625						'\\1 '.$this->hasTop.' '.$nrows.' ',
1626						$sql
1627					);
1628				}
1629			} else {
1630				$nn = $nrows + $offset;
1631				if ($isaccess || $ismssql) {
1632					$sql = preg_replace(
1633						'/(^\s*select\s+(distinctrow|distinct)?)/i',
1634						'\\1 '.$this->hasTop.' '.$nn.' ',
1635						$sql
1636					);
1637				} else {
1638					$sql = preg_replace(
1639						'/(^\s*select\s)/i',
1640						'\\1 '.$this->hasTop.' '.$nn.' ',
1641						$sql
1642					);
1643				}
1644			}
1645		}
1646
1647		// if $offset>0, we want to skip rows, and $ADODB_COUNTRECS is set, we buffer  rows
1648		// 0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS.
1649		global $ADODB_COUNTRECS;
1650
1651		$savec = $ADODB_COUNTRECS;
1652		$ADODB_COUNTRECS = false;
1653
1654
1655		if ($secs2cache != 0) {
1656			$rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
1657		} else {
1658			$rs = $this->Execute($sql,$inputarr);
1659		}
1660
1661		$ADODB_COUNTRECS = $savec;
1662		if ($rs && !$rs->EOF) {
1663			$rs = $this->_rs2rs($rs,$nrows,$offset);
1664		}
1665		//print_r($rs);
1666		return $rs;
1667	}
1668
1669	/**
1670	* Create serializable recordset. Breaks rs link to connection.
1671	*
1672	* @param ADORecordSet $rs the recordset to serialize
1673	 *
1674	* @return ADORecordSet_array|bool the new recordset
1675	*/
1676	function SerializableRS(&$rs) {
1677		$rs2 = $this->_rs2rs($rs);
1678		$ignore = false;
1679		$rs2->connection = $ignore;
1680
1681		return $rs2;
1682	}
1683
1684	/**
1685	* Convert a database recordset to an array recordset.
1686	 *
1687	* Input recordset's cursor should be at beginning, and old $rs will be closed.
1688	*
1689	 * @param ADORecordSet $rs     the recordset to copy
1690	 * @param int          $nrows  number of rows to retrieve (optional)
1691	 * @param int          $offset offset by number of rows (optional)
1692	 * @param bool         $close
1693	 *
1694	 * @return ADORecordSet_array|ADORecordSet|bool the new recordset
1695	*/
1696	function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) {
1697		if (! $rs) {
1698			$ret = false;
1699			return $ret;
1700		}
1701		$dbtype = $rs->databaseType;
1702		if (!$dbtype) {
1703			$rs = $rs;  // required to prevent crashing in 4.2.1, but does not happen in 4.3.1 -- why ?
1704			return $rs;
1705		}
1706		if (($dbtype == 'array' || $dbtype == 'csv') && $nrows == -1 && $offset == -1) {
1707			$rs->MoveFirst();
1708			$rs = $rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1-- why ?
1709			return $rs;
1710		}
1711		$flds = array();
1712		for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) {
1713			$flds[] = $rs->FetchField($i);
1714		}
1715
1716		$arr = $rs->GetArrayLimit($nrows,$offset);
1717		//print_r($arr);
1718		if ($close) {
1719			$rs->Close();
1720		}
1721
1722		$arrayClass = $this->arrayClass;
1723
1724		$rs2 = new $arrayClass();
1725		$rs2->connection = $this;
1726		$rs2->sql = $rs->sql;
1727		$rs2->dataProvider = $this->dataProvider;
1728		$rs2->InitArrayFields($arr,$flds);
1729		$rs2->fetchMode = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode;
1730		return $rs2;
1731	}
1732
1733	/*
1734	 * Return all rows.
1735	 *
1736	 * Compat with PEAR DB.
1737	 *
1738	 * @param string     $sql      SQL statement
1739	 * @param array|bool $inputarr Input bind array
1740	 *
1741	 * @return array|false
1742	*/
1743	function GetAll($sql, $inputarr=false) {
1744        return $this->GetArray($sql,$inputarr);
1745	}
1746
1747	/**
1748	 * Execute statement and return rows in an array.
1749	 *
1750	 * The function executes a statement and returns all of the returned rows in
1751	 * an array, or false if the statement execution fails or if only 1 column
1752	 * is requested in the SQL statement.
1753	 * If no records match the provided SQL statement, an empty array is returned.
1754	 *
1755	 * @param string     $sql         SQL statement
1756	 * @param array|bool $inputarr    input bind array
1757	 * @param bool       $force_array
1758	 * @param bool       $first2cols
1759	 *
1760	 * @return array|bool
1761	 */
1762	public function GetAssoc($sql, $inputarr = false, $force_array = false, $first2cols = false) {
1763		$rs = $this->Execute($sql, $inputarr);
1764
1765		if (!$rs) {
1766			/*
1767			* Execution failure
1768			*/
1769			return false;
1770		}
1771		return $rs->GetAssoc($force_array, $first2cols);
1772	}
1773
1774	/**
1775	 * Search for the results of an executed query in the cache.
1776	 *
1777	 * @param int $secs2cache
1778	 * @param string|bool $sql         SQL statement
1779	 * @param array|bool  $inputarr    input bind array
1780	 * @param bool        $force_array
1781	 * @param bool        $first2cols
1782	 *
1783	 * @return false|array
1784	 */
1785	public function CacheGetAssoc($secs2cache, $sql = false, $inputarr = false,$force_array = false, $first2cols = false) {
1786		if (!is_numeric($secs2cache)) {
1787			$first2cols = $force_array;
1788			$force_array = $inputarr;
1789		}
1790		$rs = $this->CacheExecute($secs2cache, $sql, $inputarr);
1791		if (!$rs) {
1792			return false;
1793		}
1794		return $rs->GetAssoc($force_array, $first2cols);
1795	}
1796
1797	/**
1798	 * Return first element of first row of sql statement. Recordset is disposed
1799	 * for you.
1800	 *
1801	 * @param string		$sql		SQL statement
1802	 * @param array|bool	$inputarr	input bind array
1803	 * @return mixed
1804	 */
1805	public function GetOne($sql, $inputarr=false) {
1806		global $ADODB_COUNTRECS,$ADODB_GETONE_EOF;
1807
1808		$crecs = $ADODB_COUNTRECS;
1809		$ADODB_COUNTRECS = false;
1810
1811		$ret = false;
1812		$rs = $this->Execute($sql,$inputarr);
1813		if ($rs) {
1814			if ($rs->EOF) {
1815				$ret = $ADODB_GETONE_EOF;
1816			} else {
1817				$ret = reset($rs->fields);
1818			}
1819
1820			$rs->Close();
1821		}
1822		$ADODB_COUNTRECS = $crecs;
1823		return $ret;
1824	}
1825
1826	// $where should include 'WHERE fld=value'
1827	function GetMedian($table, $field,$where = '') {
1828		$total = $this->GetOne("select count(*) from $table $where");
1829		if (!$total) {
1830			return false;
1831		}
1832
1833		$midrow = (integer) ($total/2);
1834		$rs = $this->SelectLimit("select $field from $table $where order by 1",1,$midrow);
1835		if ($rs && !$rs->EOF) {
1836			return reset($rs->fields);
1837		}
1838		return false;
1839	}
1840
1841
1842	function CacheGetOne($secs2cache,$sql=false,$inputarr=false) {
1843		global $ADODB_GETONE_EOF;
1844
1845		$ret = false;
1846		$rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
1847		if ($rs) {
1848			if ($rs->EOF) {
1849				$ret = $ADODB_GETONE_EOF;
1850			} else {
1851				$ret = reset($rs->fields);
1852			}
1853			$rs->Close();
1854		}
1855
1856		return $ret;
1857	}
1858
1859	/**
1860	 * Executes a statement and returns each row's first column in an array.
1861	 *
1862	 * @param string     $sql      SQL statement
1863	 * @param array|bool $inputarr input bind array
1864	 * @param bool       $trim     enables space trimming of the returned value.
1865	 *                             This is only relevant if the returned string
1866	 *                             is coming from a CHAR type field.
1867	 *
1868	 * @return array|bool 1D array containning the first row of the query
1869	 */
1870	function GetCol($sql, $inputarr = false, $trim = false) {
1871
1872		$rs = $this->Execute($sql, $inputarr);
1873		if ($rs) {
1874			$rv = array();
1875			if ($trim) {
1876				while (!$rs->EOF) {
1877					$rv[] = trim(reset($rs->fields));
1878					$rs->MoveNext();
1879				}
1880			} else {
1881				while (!$rs->EOF) {
1882					$rv[] = reset($rs->fields);
1883					$rs->MoveNext();
1884				}
1885			}
1886			$rs->Close();
1887		} else {
1888			$rv = false;
1889		}
1890		return $rv;
1891	}
1892
1893	function CacheGetCol($secs, $sql = false, $inputarr = false,$trim=false) {
1894		$rs = $this->CacheExecute($secs, $sql, $inputarr);
1895		if ($rs) {
1896			$rv = array();
1897			if ($trim) {
1898				while (!$rs->EOF) {
1899					$rv[] = trim(reset($rs->fields));
1900					$rs->MoveNext();
1901				}
1902			} else {
1903				while (!$rs->EOF) {
1904					$rv[] = reset($rs->fields);
1905					$rs->MoveNext();
1906				}
1907			}
1908			$rs->Close();
1909		} else
1910			$rv = false;
1911
1912		return $rv;
1913	}
1914
1915	function Transpose(&$rs,$addfieldnames=true) {
1916		$rs2 = $this->_rs2rs($rs);
1917		if (!$rs2) {
1918			return false;
1919		}
1920
1921		$rs2->_transpose($addfieldnames);
1922		return $rs2;
1923	}
1924
1925	/*
1926		Calculate the offset of a date for a particular database and generate
1927			appropriate SQL. Useful for calculating future/past dates and storing
1928			in a database.
1929
1930		If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour.
1931	*/
1932	function OffsetDate($dayFraction,$date=false) {
1933		if (!$date) {
1934			$date = $this->sysDate;
1935		}
1936		return  '('.$date.'+'.$dayFraction.')';
1937	}
1938
1939
1940	/**
1941	 * Executes a statement and returns a the entire recordset in an array.
1942	 *
1943	 * @param string     $sql      SQL statement
1944	 * @param array|bool $inputarr input bind array
1945	 *
1946	 * @return array|false
1947	 */
1948	function GetArray($sql,$inputarr=false) {
1949		global $ADODB_COUNTRECS;
1950
1951		$savec = $ADODB_COUNTRECS;
1952		$ADODB_COUNTRECS = false;
1953		$rs = $this->Execute($sql,$inputarr);
1954		$ADODB_COUNTRECS = $savec;
1955		if (!$rs)
1956			if (defined('ADODB_PEAR')) {
1957				return ADODB_PEAR_Error();
1958			} else {
1959				return false;
1960			}
1961		$arr = $rs->GetArray();
1962		$rs->Close();
1963		return $arr;
1964	}
1965
1966	function CacheGetAll($secs2cache,$sql=false,$inputarr=false) {
1967		return $this->CacheGetArray($secs2cache,$sql,$inputarr);
1968	}
1969
1970	function CacheGetArray($secs2cache,$sql=false,$inputarr=false) {
1971		global $ADODB_COUNTRECS;
1972
1973		$savec = $ADODB_COUNTRECS;
1974		$ADODB_COUNTRECS = false;
1975		$rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
1976		$ADODB_COUNTRECS = $savec;
1977
1978		if (!$rs)
1979			if (defined('ADODB_PEAR')) {
1980				return ADODB_PEAR_Error();
1981			} else {
1982				return false;
1983			}
1984		$arr = $rs->GetArray();
1985		$rs->Close();
1986		return $arr;
1987	}
1988
1989	function GetRandRow($sql, $arr= false) {
1990		$rezarr = $this->GetAll($sql, $arr);
1991		$sz = sizeof($rezarr);
1992		return $rezarr[abs(rand()) % $sz];
1993	}
1994
1995	/**
1996	* Return one row of sql statement. Recordset is disposed for you.
1997	* Note that SelectLimit should not be called.
1998	*
1999	* @param string     $sql      SQL statement
2000	* @param array|bool $inputarr input bind array
2001	 *
2002	* @return array|false Array containing the first row of the query
2003	*/
2004	function GetRow($sql,$inputarr=false) {
2005		global $ADODB_COUNTRECS;
2006
2007		$crecs = $ADODB_COUNTRECS;
2008		$ADODB_COUNTRECS = false;
2009
2010		$rs = $this->Execute($sql,$inputarr);
2011
2012		$ADODB_COUNTRECS = $crecs;
2013		if ($rs) {
2014			if (!$rs->EOF) {
2015				$arr = $rs->fields;
2016			} else {
2017				$arr = array();
2018			}
2019			$rs->Close();
2020			return $arr;
2021		}
2022
2023		return false;
2024	}
2025
2026	/**
2027	 * @param int $secs2cache
2028	 * @param string|false $sql
2029	 * @param mixed[]|bool $inputarr
2030	 * @return mixed[]|bool
2031	 */
2032	function CacheGetRow($secs2cache,$sql=false,$inputarr=false) {
2033		$rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
2034		if ($rs) {
2035			if (!$rs->EOF) {
2036				$arr = $rs->fields;
2037			} else {
2038				$arr = array();
2039			}
2040
2041			$rs->Close();
2042			return $arr;
2043		}
2044		return false;
2045	}
2046
2047	/**
2048	* Insert or replace a single record. Note: this is not the same as MySQL's replace.
2049	* ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL.
2050	* Also note that no table locking is done currently, so it is possible that the
2051	* record be inserted twice by two programs...
2052	*
2053	* $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname');
2054	*
2055	* $table		table name
2056	* $fieldArray	associative array of data (you must quote strings yourself).
2057	* $keyCol		the primary key field name or if compound key, array of field names
2058	* autoQuote		set to true to use a heuristic to quote strings. Works with nulls and numbers
2059	*					but does not work with dates nor SQL functions.
2060	* has_autoinc	the primary key is an auto-inc field, so skip in insert.
2061	*
2062	* Currently blob replace not supported
2063	*
2064	* returns 0 = fail, 1 = update, 2 = insert
2065	*/
2066
2067	function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false) {
2068		global $ADODB_INCLUDED_LIB;
2069		if (empty($ADODB_INCLUDED_LIB)) {
2070			include_once(ADODB_DIR.'/adodb-lib.inc.php');
2071		}
2072
2073		return _adodb_replace($this, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc);
2074	}
2075
2076
2077	/**
2078	 * Will select, getting rows from $offset (1-based), for $nrows.
2079	 * This simulates the MySQL "select * from table limit $offset,$nrows" , and
2080	 * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
2081	 * MySQL and PostgreSQL parameter ordering is the opposite of the other.
2082	 * eg.
2083	 *  CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based)
2084	 *  CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based)
2085	 *
2086	 * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set
2087	 *
2088	 * @param int    $secs2cache Seconds to cache data, set to 0 to force query. This is optional
2089	 * @param string $sql
2090	 * @param int    $offset     Row to start calculations from (1-based)
2091	 * @param int    $nrows      Number of rows to get
2092	 * @param array $inputarr    Array of bind variables
2093	 *
2094	 * @return ADORecordSet The recordset ($rs->databaseType == 'array')
2095	 */
2096	function CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false) {
2097		if (!is_numeric($secs2cache)) {
2098			if ($sql === false) {
2099				$sql = -1;
2100			}
2101			if ($offset == -1) {
2102				$offset = false;
2103			}
2104												// sql,	nrows, offset,inputarr
2105			$rs = $this->SelectLimit($secs2cache,$sql,$nrows,$offset,$this->cacheSecs);
2106		} else {
2107			if ($sql === false) {
2108				$this->outp_throw("Warning: \$sql missing from CacheSelectLimit()",'CacheSelectLimit');
2109			}
2110			$rs = $this->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
2111		}
2112		return $rs;
2113	}
2114
2115	/**
2116	 * Flush cached recordsets that match a particular $sql statement.
2117	 * If $sql == false, then we purge all files in the cache.
2118	 */
2119	function CacheFlush($sql=false,$inputarr=false) {
2120		global $ADODB_CACHE_DIR, $ADODB_CACHE;
2121
2122		# Create cache if it does not exist
2123		if (empty($ADODB_CACHE)) {
2124			$this->_CreateCache();
2125		}
2126
2127		if (!$sql) {
2128			$ADODB_CACHE->flushall($this->debug);
2129			return;
2130		}
2131
2132		$f = $this->_gencachename($sql.serialize($inputarr),false);
2133		return $ADODB_CACHE->flushcache($f, $this->debug);
2134	}
2135
2136
2137	/**
2138	 * Private function to generate filename for caching.
2139	 * Filename is generated based on:
2140	 *
2141	 *  - sql statement
2142	 *  - database type (oci8, ibase, ifx, etc)
2143	 *  - database name
2144	 *  - userid
2145	 *  - setFetchMode (adodb 4.23)
2146	 *
2147	 * When not in safe mode, we create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR).
2148	 * Assuming that we can have 50,000 files per directory with good performance,
2149	 * then we can scale to 12.8 million unique cached recordsets. Wow!
2150	 */
2151	function _gencachename($sql,$createdir) {
2152		global $ADODB_CACHE, $ADODB_CACHE_DIR;
2153
2154		if ($this->fetchMode === false) {
2155			global $ADODB_FETCH_MODE;
2156			$mode = $ADODB_FETCH_MODE;
2157		} else {
2158			$mode = $this->fetchMode;
2159		}
2160		$m = md5($sql.$this->databaseType.$this->database.$this->user.$mode);
2161		if (!$ADODB_CACHE->createdir) {
2162			return $m;
2163		}
2164		if (!$createdir) {
2165			$dir = $ADODB_CACHE->getdirname($m);
2166		} else {
2167			$dir = $ADODB_CACHE->createdir($m, $this->debug);
2168		}
2169
2170		return $dir.'/adodb_'.$m.'.cache';
2171	}
2172
2173
2174	/**
2175	 * Execute SQL, caching recordsets.
2176	 *
2177	 * @param int         $secs2cache Seconds to cache data, set to 0 to force query.
2178	 *                                This is an optional parameter.
2179	 * @param string|bool $sql        SQL statement to execute
2180	 * @param array|bool  $inputarr   Holds the input data to bind
2181	 *
2182	 * @return ADORecordSet RecordSet or false
2183	 */
2184	function CacheExecute($secs2cache,$sql=false,$inputarr=false) {
2185		global $ADODB_CACHE;
2186
2187		if (empty($ADODB_CACHE)) {
2188			$this->_CreateCache();
2189		}
2190
2191		if (!is_numeric($secs2cache)) {
2192			$inputarr = $sql;
2193			$sql = $secs2cache;
2194			$secs2cache = $this->cacheSecs;
2195		}
2196
2197		if (is_array($sql)) {
2198			$sqlparam = $sql;
2199			$sql = $sql[0];
2200		} else
2201			$sqlparam = $sql;
2202
2203
2204		$md5file = $this->_gencachename($sql.serialize($inputarr),true);
2205		$err = '';
2206
2207		if ($secs2cache > 0){
2208			$rs = $ADODB_CACHE->readcache($md5file,$err,$secs2cache,$this->arrayClass);
2209			$this->numCacheHits += 1;
2210		} else {
2211			$err='Timeout 1';
2212			$rs = false;
2213			$this->numCacheMisses += 1;
2214		}
2215
2216		if (!$rs) {
2217			// no cached rs found
2218			if ($this->debug) {
2219				if ($this->debug !== -1) {
2220					ADOConnection::outp( " $md5file cache failure: $err (this is a notice and not an error)");
2221				}
2222			}
2223
2224			$rs = $this->Execute($sqlparam,$inputarr);
2225
2226			if ($rs) {
2227				$eof = $rs->EOF;
2228				$rs = $this->_rs2rs($rs); // read entire recordset into memory immediately
2229				$rs->timeCreated = time(); // used by caching
2230				$txt = _rs2serialize($rs,false,$sql); // serialize
2231
2232				$ok = $ADODB_CACHE->writecache($md5file,$txt,$this->debug, $secs2cache);
2233				if (!$ok) {
2234					if ($ok === false) {
2235						$em = 'Cache write error';
2236						$en = -32000;
2237
2238						if ($fn = $this->raiseErrorFn) {
2239							$fn($this->databaseType,'CacheExecute', $en, $em, $md5file,$sql,$this);
2240						}
2241					} else {
2242						$em = 'Cache file locked warning';
2243						$en = -32001;
2244						// do not call error handling for just a warning
2245					}
2246
2247					if ($this->debug) {
2248						ADOConnection::outp( " ".$em);
2249					}
2250				}
2251				if ($rs->EOF && !$eof) {
2252					$rs->MoveFirst();
2253					//$rs = csv2rs($md5file,$err);
2254					$rs->connection = $this; // Pablo suggestion
2255				}
2256
2257			} else if (!$this->memCache) {
2258				$ADODB_CACHE->flushcache($md5file);
2259			}
2260		} else {
2261			$this->_errorMsg = '';
2262			$this->_errorCode = 0;
2263
2264			if ($this->fnCacheExecute) {
2265				$fn = $this->fnCacheExecute;
2266				$fn($this, $secs2cache, $sql, $inputarr);
2267			}
2268			// ok, set cached object found
2269			$rs->connection = $this; // Pablo suggestion
2270			if ($this->debug){
2271				if ($this->debug == 99) {
2272					adodb_backtrace();
2273				}
2274				$inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
2275				$ttl = $rs->timeCreated + $secs2cache - time();
2276				$s = is_array($sql) ? $sql[0] : $sql;
2277				if ($inBrowser) {
2278					$s = '<i>'.htmlspecialchars($s).'</i>';
2279				}
2280
2281				ADOConnection::outp( " $md5file reloaded, ttl=$ttl [ $s ]");
2282			}
2283		}
2284		return $rs;
2285	}
2286
2287
2288	/*
2289
2290
2291		$forceUpdate .
2292	 */
2293	/**
2294	 * Similar to PEAR DB's autoExecute(), except that $mode can be 'INSERT'
2295	 * or 'UPDATE' or DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE.
2296	 * If $mode == 'UPDATE', then $where is compulsory as a safety measure.
2297	 *
2298	 * @param $table
2299	 * @param $fields_values
2300	 * @param string $mode
2301	 * @param false $where
2302	 * @param bool $forceUpdate  If true, perform update even if the data has not changed.
2303	 * @param bool $magic_quotes This param is not used since 5.21.0.
2304	 *                           It remains for backwards compatibility.
2305	 *
2306	 * @return bool
2307	 *
2308	 * @noinspection PhpUnusedParameterInspection
2309	 */
2310	function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $forceUpdate = true, $magic_quotes = false) {
2311		if (empty($fields_values)) {
2312			$this->outp_throw('AutoExecute: Empty fields array', 'AutoExecute');
2313			return false;
2314		}
2315		if ($where === false && ($mode == 'UPDATE' || $mode == 2 /* DB_AUTOQUERY_UPDATE */) ) {
2316			$this->outp_throw('AutoExecute: Illegal mode=UPDATE with empty WHERE clause', 'AutoExecute');
2317			return false;
2318		}
2319
2320		$sql = "SELECT * FROM $table";
2321		$rs = $this->SelectLimit($sql, 1);
2322		if (!$rs) {
2323			return false; // table does not exist
2324		}
2325
2326		$rs->tableName = $table;
2327		if ($where !== false) {
2328			$sql .= " WHERE $where";
2329		}
2330		$rs->sql = $sql;
2331
2332		switch($mode) {
2333			case 'UPDATE':
2334			case DB_AUTOQUERY_UPDATE:
2335				$sql = $this->GetUpdateSQL($rs, $fields_values, $forceUpdate);
2336				break;
2337			case 'INSERT':
2338			case DB_AUTOQUERY_INSERT:
2339				$sql = $this->GetInsertSQL($rs, $fields_values);
2340				break;
2341			default:
2342				$this->outp_throw("AutoExecute: Unknown mode=$mode", 'AutoExecute');
2343				return false;
2344		}
2345		return $sql && $this->Execute($sql);
2346	}
2347
2348
2349	/**
2350	 * Generates an Update Query based on an existing recordset.
2351	 *
2352	 * $arrFields is an associative array of fields with the value
2353	 * that should be assigned.
2354	 *
2355	 * Note: This function should only be used on a recordset
2356	 *       that is run against a single table and sql should only
2357	 *       be a simple select stmt with no groupby/orderby/limit
2358	 * @author "Jonathan Younger" <jyounger@unilab.com>
2359	 *
2360	 * @param $rs
2361	 * @param $arrFields
2362	 * @param bool $forceUpdate
2363	 * @param bool $magic_quotes This param is not used since 5.21.0.
2364	 *                           It remains for backwards compatibility.
2365	 * @param null $force
2366	 *
2367	 * @return false|string
2368	 *
2369	 * @noinspection PhpUnusedParameterInspection
2370	 */
2371	function GetUpdateSQL(&$rs, $arrFields, $forceUpdate=false, $magic_quotes=false, $force=null) {
2372		global $ADODB_INCLUDED_LIB;
2373
2374		// ********************************************************
2375		// This is here to maintain compatibility
2376		// with older adodb versions. Sets force type to force nulls if $forcenulls is set.
2377		if (!isset($force)) {
2378			global $ADODB_FORCE_TYPE;
2379			$force = $ADODB_FORCE_TYPE;
2380		}
2381		// ********************************************************
2382
2383		if (empty($ADODB_INCLUDED_LIB)) {
2384			include_once(ADODB_DIR.'/adodb-lib.inc.php');
2385		}
2386		return _adodb_getupdatesql($this, $rs, $arrFields, $forceUpdate, $force);
2387	}
2388
2389	/**
2390	 * Generates an Insert Query based on an existing recordset.
2391	 *
2392	 * $arrFields is an associative array of fields with the value
2393	 * that should be assigned.
2394	 *
2395	 * Note: This function should only be used on a recordset
2396	 *       that is run against a single table.
2397	 *
2398	 * @param $rs
2399	 * @param $arrFields
2400	 * @param bool $magic_quotes This param is not used since 5.21.0.
2401	 *                           It remains for backwards compatibility.
2402	 * @param null $force
2403	 *
2404	 * @return false|string
2405	 *
2406	 * @noinspection PhpUnusedParameterInspection
2407	 */
2408	function GetInsertSQL(&$rs, $arrFields, $magic_quotes=false, $force=null) {
2409		global $ADODB_INCLUDED_LIB;
2410		if (!isset($force)) {
2411			global $ADODB_FORCE_TYPE;
2412			$force = $ADODB_FORCE_TYPE;
2413		}
2414		if (empty($ADODB_INCLUDED_LIB)) {
2415			include_once(ADODB_DIR.'/adodb-lib.inc.php');
2416		}
2417		return _adodb_getinsertsql($this, $rs, $arrFields, $force);
2418	}
2419
2420
2421	/**
2422	* Update a blob column, given a where clause. There are more sophisticated
2423	* blob handling functions that we could have implemented, but all require
2424	* a very complex API. Instead we have chosen something that is extremely
2425	* simple to understand and use.
2426	*
2427	* Note: $blobtype supports 'BLOB' and 'CLOB', default is BLOB of course.
2428	*
2429	* Usage to update a $blobvalue which has a primary key blob_id=1 into a
2430	* field blobtable.blobcolumn:
2431	*
2432	*	UpdateBlob('blobtable', 'blobcolumn', $blobvalue, 'blob_id=1');
2433	*
2434	* Insert example:
2435	*
2436	*	$conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
2437	*	$conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
2438	*/
2439	function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') {
2440		return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
2441	}
2442
2443	/**
2444	* Usage:
2445	*	UpdateBlob('TABLE', 'COLUMN', '/path/to/file', 'ID=1');
2446	*
2447	*	$blobtype supports 'BLOB' and 'CLOB'
2448	*
2449	*	$conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
2450	*	$conn->UpdateBlob('blobtable','blobcol',$blobpath,'id=1');
2451	*/
2452	function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') {
2453		$fd = fopen($path,'rb');
2454		if ($fd === false) {
2455			return false;
2456		}
2457		$val = fread($fd,filesize($path));
2458		fclose($fd);
2459		return $this->UpdateBlob($table,$column,$val,$where,$blobtype);
2460	}
2461
2462	function BlobDecode($blob) {
2463		return $blob;
2464	}
2465
2466	function BlobEncode($blob) {
2467		return $blob;
2468	}
2469
2470	function GetCharSet() {
2471		return $this->charSet;
2472	}
2473
2474	function SetCharSet($charset) {
2475		$this->charSet = $charset;
2476		return true;
2477	}
2478
2479	function IfNull( $field, $ifNull ) {
2480		return " CASE WHEN $field is null THEN $ifNull ELSE $field END ";
2481	}
2482
2483	function LogSQL($enable=true) {
2484		include_once(ADODB_DIR.'/adodb-perf.inc.php');
2485
2486		if ($enable) {
2487			$this->fnExecute = 'adodb_log_sql';
2488		} else {
2489			$this->fnExecute = false;
2490		}
2491
2492		$old = $this->_logsql;
2493		$this->_logsql = $enable;
2494		if ($enable && !$old) {
2495			$this->_affected = false;
2496		}
2497		return $old;
2498	}
2499
2500	/**
2501	* Usage:
2502	*	UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
2503	*
2504	*	$conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
2505	*	$conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
2506	*/
2507	function UpdateClob($table,$column,$val,$where) {
2508		return $this->UpdateBlob($table,$column,$val,$where,'CLOB');
2509	}
2510
2511	// not the fastest implementation - quick and dirty - jlim
2512	// for best performance, use the actual $rs->MetaType().
2513	function MetaType($t,$len=-1,$fieldobj=false) {
2514
2515		if (empty($this->_metars)) {
2516			$rsclass = $this->rsPrefix.$this->databaseType;
2517			$this->_metars = new $rsclass(false,$this->fetchMode);
2518			$this->_metars->connection = $this;
2519		}
2520		return $this->_metars->MetaType($t,$len,$fieldobj);
2521	}
2522
2523
2524	/**
2525	*  Change the SQL connection locale to a specified locale.
2526	*  This is used to get the date formats written depending on the client locale.
2527	*/
2528	function SetDateLocale($locale = 'En') {
2529		$this->locale = $locale;
2530		switch (strtoupper($locale))
2531		{
2532			case 'EN':
2533				$this->fmtDate="'Y-m-d'";
2534				$this->fmtTimeStamp = "'Y-m-d H:i:s'";
2535				break;
2536
2537			case 'US':
2538				$this->fmtDate = "'m-d-Y'";
2539				$this->fmtTimeStamp = "'m-d-Y H:i:s'";
2540				break;
2541
2542			case 'PT_BR':
2543			case 'NL':
2544			case 'FR':
2545			case 'RO':
2546			case 'IT':
2547				$this->fmtDate="'d-m-Y'";
2548				$this->fmtTimeStamp = "'d-m-Y H:i:s'";
2549				break;
2550
2551			case 'GE':
2552				$this->fmtDate="'d.m.Y'";
2553				$this->fmtTimeStamp = "'d.m.Y H:i:s'";
2554				break;
2555
2556			default:
2557				$this->fmtDate="'Y-m-d'";
2558				$this->fmtTimeStamp = "'Y-m-d H:i:s'";
2559				break;
2560		}
2561	}
2562
2563	/**
2564	 * GetActiveRecordsClass Performs an 'ALL' query
2565	 *
2566	 * @param mixed $class This string represents the class of the current active record
2567	 * @param mixed $table Table used by the active record object
2568	 * @param mixed $whereOrderBy Where, order, by clauses
2569	 * @param mixed $bindarr
2570	 * @param mixed $primkeyArr
2571	 * @param array $extra Query extras: limit, offset...
2572	 * @param mixed $relations Associative array: table's foreign name, "hasMany", "belongsTo"
2573	 * @access public
2574	 * @return void
2575	 */
2576	function GetActiveRecordsClass(
2577			$class, $table,$whereOrderBy=false,$bindarr=false, $primkeyArr=false,
2578			$extra=array(),
2579			$relations=array())
2580	{
2581		global $_ADODB_ACTIVE_DBS;
2582		## reduce overhead of adodb.inc.php -- moved to adodb-active-record.inc.php
2583		## if adodb-active-recordx is loaded -- should be no issue as they will probably use Find()
2584		if (!isset($_ADODB_ACTIVE_DBS)) {
2585			include_once(ADODB_DIR.'/adodb-active-record.inc.php');
2586		}
2587		return adodb_GetActiveRecordsClass($this, $class, $table, $whereOrderBy, $bindarr, $primkeyArr, $extra, $relations);
2588	}
2589
2590	function GetActiveRecords($table,$where=false,$bindarr=false,$primkeyArr=false) {
2591		$arr = $this->GetActiveRecordsClass('ADODB_Active_Record', $table, $where, $bindarr, $primkeyArr);
2592		return $arr;
2593	}
2594
2595	/**
2596	 * Close Connection
2597	 */
2598	function Close() {
2599		$rez = $this->_close();
2600		$this->_queryID = false;
2601		$this->_connectionID = false;
2602		return $rez;
2603	}
2604
2605	/**
2606	 * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans().
2607	 *
2608	 * @return bool true if succeeded or false if database does not support transactions
2609	 */
2610	function BeginTrans() {
2611		if ($this->debug) {
2612			ADOConnection::outp("BeginTrans: Transactions not supported for this driver");
2613		}
2614		return false;
2615	}
2616
2617	/* set transaction mode */
2618	function SetTransactionMode( $transaction_mode ) {
2619		$transaction_mode = $this->MetaTransaction($transaction_mode, $this->dataProvider);
2620		$this->_transmode  = $transaction_mode;
2621	}
2622/*
2623http://msdn2.microsoft.com/en-US/ms173763.aspx
2624http://dev.mysql.com/doc/refman/5.0/en/innodb-transaction-isolation.html
2625http://www.postgresql.org/docs/8.1/interactive/sql-set-transaction.html
2626http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_10005.htm
2627*/
2628	function MetaTransaction($mode,$db) {
2629		$mode = strtoupper($mode);
2630		$mode = str_replace('ISOLATION LEVEL ','',$mode);
2631
2632		switch($mode) {
2633
2634		case 'READ UNCOMMITTED':
2635			switch($db) {
2636			case 'oci8':
2637			case 'oracle':
2638				return 'ISOLATION LEVEL READ COMMITTED';
2639			default:
2640				return 'ISOLATION LEVEL READ UNCOMMITTED';
2641			}
2642			break;
2643
2644		case 'READ COMMITTED':
2645				return 'ISOLATION LEVEL READ COMMITTED';
2646			break;
2647
2648		case 'REPEATABLE READ':
2649			switch($db) {
2650			case 'oci8':
2651			case 'oracle':
2652				return 'ISOLATION LEVEL SERIALIZABLE';
2653			default:
2654				return 'ISOLATION LEVEL REPEATABLE READ';
2655			}
2656			break;
2657
2658		case 'SERIALIZABLE':
2659				return 'ISOLATION LEVEL SERIALIZABLE';
2660			break;
2661
2662		default:
2663			return $mode;
2664		}
2665	}
2666
2667	/**
2668	 * If database does not support transactions, always return true as data always committed
2669	 *
2670	 * @param bool $ok  set to false to rollback transaction, true to commit
2671	 *
2672	 * @return true/false.
2673	 */
2674	function CommitTrans($ok=true) {
2675		return true;
2676	}
2677
2678
2679	/**
2680	 * If database does not support transactions, rollbacks always fail, so return false
2681	 *
2682	 * @return bool
2683	 */
2684	function RollbackTrans() {
2685		return false;
2686	}
2687
2688
2689	/**
2690	 * return the databases that the driver can connect to.
2691	 * Some databases will return an empty array.
2692	 *
2693	 * @return array|bool an array of database names.
2694	 */
2695	function MetaDatabases() {
2696		global $ADODB_FETCH_MODE;
2697
2698		if ($this->metaDatabasesSQL) {
2699			$save = $ADODB_FETCH_MODE;
2700			$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
2701
2702			if ($this->fetchMode !== false) {
2703				$savem = $this->SetFetchMode(false);
2704			}
2705
2706			$arr = $this->GetCol($this->metaDatabasesSQL);
2707			if (isset($savem)) {
2708				$this->SetFetchMode($savem);
2709			}
2710			$ADODB_FETCH_MODE = $save;
2711
2712			return $arr;
2713		}
2714
2715		return false;
2716	}
2717
2718	/**
2719	 * List procedures or functions in an array.
2720	 * @param procedureNamePattern  a procedure name pattern; must match the procedure name as it is stored in the database
2721	 * @param catalog a catalog name; must match the catalog name as it is stored in the database;
2722	 * @param schemaPattern a schema name pattern;
2723	 *
2724	 * @return array of procedures on current database.
2725	 *
2726	 * Array(
2727	 *   [name_of_procedure] => Array(
2728	 *     [type] => PROCEDURE or FUNCTION
2729	 *     [catalog] => Catalog_name
2730	 *     [schema] => Schema_name
2731	 *     [remarks] => explanatory comment on the procedure
2732	 *   )
2733	 * )
2734	 */
2735	function MetaProcedures($procedureNamePattern = null, $catalog  = null, $schemaPattern  = null) {
2736		return false;
2737	}
2738
2739
2740	/**
2741	 * @param ttype can either be 'VIEW' or 'TABLE' or false.
2742	 *		If false, both views and tables are returned.
2743	 *		"VIEW" returns only views
2744	 *		"TABLE" returns only tables
2745	 * @param showSchema returns the schema/user with the table name, eg. USER.TABLE
2746	 * @param mask  is the input mask - only supported by oci8 and postgresql
2747	 *
2748	 * @return  array of tables for current database.
2749	 */
2750	function MetaTables($ttype=false,$showSchema=false,$mask=false) {
2751		global $ADODB_FETCH_MODE;
2752
2753		if ($mask) {
2754			return false;
2755		}
2756		if ($this->metaTablesSQL) {
2757			$save = $ADODB_FETCH_MODE;
2758			$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
2759
2760			if ($this->fetchMode !== false) {
2761				$savem = $this->SetFetchMode(false);
2762			}
2763
2764			$rs = $this->Execute($this->metaTablesSQL);
2765			if (isset($savem)) {
2766				$this->SetFetchMode($savem);
2767			}
2768			$ADODB_FETCH_MODE = $save;
2769
2770			if ($rs === false) {
2771				return false;
2772			}
2773			$arr = $rs->GetArray();
2774			$arr2 = array();
2775
2776			if ($hast = ($ttype && isset($arr[0][1]))) {
2777				$showt = strncmp($ttype,'T',1);
2778			}
2779
2780			for ($i=0; $i < sizeof($arr); $i++) {
2781				if ($hast) {
2782					if ($showt == 0) {
2783						if (strncmp($arr[$i][1],'T',1) == 0) {
2784							$arr2[] = trim($arr[$i][0]);
2785						}
2786					} else {
2787						if (strncmp($arr[$i][1],'V',1) == 0) {
2788							$arr2[] = trim($arr[$i][0]);
2789						}
2790					}
2791				} else
2792					$arr2[] = trim($arr[$i][0]);
2793			}
2794			$rs->Close();
2795			return $arr2;
2796		}
2797		return false;
2798	}
2799
2800
2801	function _findschema(&$table,&$schema) {
2802		if (!$schema && ($at = strpos($table,'.')) !== false) {
2803			$schema = substr($table,0,$at);
2804			$table = substr($table,$at+1);
2805		}
2806	}
2807
2808	/**
2809	 * List columns in a database as an array of ADOFieldObjects.
2810	 * See top of file for definition of object.
2811	 *
2812	 * @param $table	table name to query
2813	 * @param $normalize	makes table name case-insensitive (required by some databases)
2814	 * @schema is optional database schema to use - not supported by all databases.
2815	 *
2816	 * @return  array of ADOFieldObjects for current table.
2817	 */
2818	function MetaColumns($table,$normalize=true) {
2819		global $ADODB_FETCH_MODE;
2820
2821		if (!empty($this->metaColumnsSQL)) {
2822			$schema = false;
2823			$this->_findschema($table,$schema);
2824
2825			$save = $ADODB_FETCH_MODE;
2826			$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
2827			if ($this->fetchMode !== false) {
2828				$savem = $this->SetFetchMode(false);
2829			}
2830			$rs = $this->Execute(sprintf($this->metaColumnsSQL,($normalize)?strtoupper($table):$table));
2831			if (isset($savem)) {
2832				$this->SetFetchMode($savem);
2833			}
2834			$ADODB_FETCH_MODE = $save;
2835			if ($rs === false || $rs->EOF) {
2836				return false;
2837			}
2838
2839			$retarr = array();
2840			while (!$rs->EOF) { //print_r($rs->fields);
2841				$fld = new ADOFieldObject();
2842				$fld->name = $rs->fields[0];
2843				$fld->type = $rs->fields[1];
2844				if (isset($rs->fields[3]) && $rs->fields[3]) {
2845					if ($rs->fields[3]>0) {
2846						$fld->max_length = $rs->fields[3];
2847					}
2848					$fld->scale = $rs->fields[4];
2849					if ($fld->scale>0) {
2850						$fld->max_length += 1;
2851					}
2852				} else {
2853					$fld->max_length = $rs->fields[2];
2854				}
2855
2856				if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) {
2857					$retarr[] = $fld;
2858				} else {
2859					$retarr[strtoupper($fld->name)] = $fld;
2860				}
2861				$rs->MoveNext();
2862			}
2863			$rs->Close();
2864			return $retarr;
2865		}
2866		return false;
2867	}
2868
2869	/**
2870	 * List indexes on a table as an array.
2871	 * @param table  table name to query
2872	 * @param primary true to only show primary keys. Not actually used for most databases
2873	 *
2874	 * @return array of indexes on current table. Each element represents an index, and is itself an associative array.
2875	 *
2876	 * Array(
2877	 *   [name_of_index] => Array(
2878	 *     [unique] => true or false
2879	 *     [columns] => Array(
2880	 *       [0] => firstname
2881	 *       [1] => lastname
2882	 *     )
2883	 *   )
2884	 * )
2885	 */
2886	function MetaIndexes($table, $primary = false, $owner = false) {
2887		return false;
2888	}
2889
2890	/**
2891	 * List columns names in a table as an array.
2892	 * @param table	table name to query
2893	 *
2894	 * @return  array of column names for current table.
2895	 */
2896	function MetaColumnNames($table, $numIndexes=false,$useattnum=false /* only for postgres */) {
2897		$objarr = $this->MetaColumns($table);
2898		if (!is_array($objarr)) {
2899			return false;
2900		}
2901		$arr = array();
2902		if ($numIndexes) {
2903			$i = 0;
2904			if ($useattnum) {
2905				foreach($objarr as $v)
2906					$arr[$v->attnum] = $v->name;
2907
2908			} else
2909				foreach($objarr as $v) $arr[$i++] = $v->name;
2910		} else
2911			foreach($objarr as $v) $arr[strtoupper($v->name)] = $v->name;
2912
2913		return $arr;
2914	}
2915
2916	/**
2917	 * Concatenate strings.
2918	 *
2919	 * Different SQL databases used different methods to combine strings together.
2920	 * This function provides a wrapper.
2921	 *
2922	 * Usage: $db->Concat($str1,$str2);
2923	 *
2924	 * @param string $s Variable number of string parameters
2925	 *
2926	 * @return string concatenated string
2927	 */
2928	function Concat() {
2929		$arr = func_get_args();
2930		return implode($this->concat_operator, $arr);
2931	}
2932
2933
2934	/**
2935	 * Converts a date "d" to a string that the database can understand.
2936	 *
2937	 * @param mixed $d a date in Unix date time format.
2938	 *
2939	 * @return string date string in database date format
2940	 */
2941	function DBDate($d, $isfld=false) {
2942		if (empty($d) && $d !== 0) {
2943			return 'null';
2944		}
2945		if ($isfld) {
2946			return $d;
2947		}
2948		if (is_object($d)) {
2949			return $d->format($this->fmtDate);
2950		}
2951
2952		if (is_string($d) && !is_numeric($d)) {
2953			if ($d === 'null') {
2954				return $d;
2955			}
2956			if (strncmp($d,"'",1) === 0) {
2957				$d = _adodb_safedateq($d);
2958				return $d;
2959			}
2960			if ($this->isoDates) {
2961				return "'$d'";
2962			}
2963			$d = ADOConnection::UnixDate($d);
2964		}
2965
2966		return adodb_date($this->fmtDate,$d);
2967	}
2968
2969	function BindDate($d) {
2970		$d = $this->DBDate($d);
2971		if (strncmp($d,"'",1)) {
2972			return $d;
2973		}
2974
2975		return substr($d,1,strlen($d)-2);
2976	}
2977
2978	function BindTimeStamp($d) {
2979		$d = $this->DBTimeStamp($d);
2980		if (strncmp($d,"'",1)) {
2981			return $d;
2982		}
2983
2984		return substr($d,1,strlen($d)-2);
2985	}
2986
2987
2988	/**
2989	 * Converts a timestamp "ts" to a string that the database can understand.
2990	 *
2991	 * @param int|object $ts A timestamp in Unix date time format.
2992	 *
2993	 * @return string $timestamp string in database timestamp format
2994	 */
2995	function DBTimeStamp($ts,$isfld=false) {
2996		if (empty($ts) && $ts !== 0) {
2997			return 'null';
2998		}
2999		if ($isfld) {
3000			return $ts;
3001		}
3002		if (is_object($ts)) {
3003			return $ts->format($this->fmtTimeStamp);
3004		}
3005
3006		# strlen(14) allows YYYYMMDDHHMMSS format
3007		if (!is_string($ts) || (is_numeric($ts) && strlen($ts)<14)) {
3008			return adodb_date($this->fmtTimeStamp,$ts);
3009		}
3010
3011		if ($ts === 'null') {
3012			return $ts;
3013		}
3014		if ($this->isoDates && strlen($ts) !== 14) {
3015			$ts = _adodb_safedate($ts);
3016			return "'$ts'";
3017		}
3018		$ts = ADOConnection::UnixTimeStamp($ts);
3019		return adodb_date($this->fmtTimeStamp,$ts);
3020	}
3021
3022	/**
3023	 * Also in ADORecordSet.
3024	 * @param mixed $v is a date string in YYYY-MM-DD format
3025	 *
3026	 * @return int|false Date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
3027	 */
3028	static function UnixDate($v) {
3029		if (is_object($v)) {
3030		// odbtp support
3031		//( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 )
3032			return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year);
3033		}
3034
3035		if (is_numeric($v) && strlen($v) !== 8) {
3036			return $v;
3037		}
3038		if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|", $v, $rr)) {
3039			return false;
3040		}
3041
3042		if ($rr[1] <= TIMESTAMP_FIRST_YEAR) {
3043			return 0;
3044		}
3045
3046		// h-m-s-MM-DD-YY
3047		return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
3048	}
3049
3050
3051	/**
3052	 * Also in ADORecordSet.
3053	 * @param string|object $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
3054	 *
3055	 * @return int|false Date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
3056	 */
3057	static function UnixTimeStamp($v) {
3058		if (is_object($v)) {
3059		// odbtp support
3060		//( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 )
3061			return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year);
3062		}
3063
3064		if (!preg_match(
3065			"|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ ,-]*(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
3066			($v), $rr)) return false;
3067
3068		if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) {
3069			return 0;
3070		}
3071
3072		// h-m-s-MM-DD-YY
3073		if (!isset($rr[5])) {
3074			return  adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
3075		}
3076		return @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]);
3077	}
3078
3079	/**
3080	 * Format database date based on user defined format.
3081	 *
3082	 * Also in ADORecordSet.
3083	 *
3084	 * @param mixed  $v    Date in YYYY-MM-DD format, returned by database
3085	 * @param string $fmt  Format to apply, using date()
3086	 * @param bool   $gmt
3087	 *
3088	 * @return string Formatted date
3089	 */
3090	function UserDate($v,$fmt='Y-m-d',$gmt=false) {
3091		$tt = $this->UnixDate($v);
3092
3093		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
3094		if (($tt === false || $tt == -1) && $v != false) {
3095			return $v;
3096		} else if ($tt == 0) {
3097			return $this->emptyDate;
3098		} else if ($tt == -1) {
3099			// pre-TIMESTAMP_FIRST_YEAR
3100		}
3101
3102		return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt);
3103
3104	}
3105
3106	/**
3107	 * Format timestamp based on user defined format.
3108	 *
3109	 * @param mixed  $v    Date in YYYY-MM-DD hh:mm:ss format
3110	 * @param string $fmt  Format to apply, using date()
3111	 * @param bool   $gmt
3112	 *
3113	 * @return string Formatted timestamp
3114	 */
3115	function UserTimeStamp($v,$fmt='Y-m-d H:i:s',$gmt=false) {
3116		if (!isset($v)) {
3117			return $this->emptyTimeStamp;
3118		}
3119		# strlen(14) allows YYYYMMDDHHMMSS format
3120		if (is_numeric($v) && strlen($v)<14) {
3121			return ($gmt) ? adodb_gmdate($fmt,$v) : adodb_date($fmt,$v);
3122		}
3123		$tt = $this->UnixTimeStamp($v);
3124		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
3125		if (($tt === false || $tt == -1) && $v != false) {
3126			return $v;
3127		}
3128		if ($tt == 0) {
3129			return $this->emptyTimeStamp;
3130		}
3131		return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt);
3132	}
3133
3134	/**
3135	 * Alias for addQ()
3136	 * @param string $s
3137	 * @param bool [$magic_quotes]
3138	 * @return mixed
3139	 *
3140	 * @deprecated 5.21.0
3141	 * @noinspection PhpUnusedParameterInspection
3142	 */
3143	function escape($s,$magic_quotes=false) {
3144		return $this->addQ($s);
3145	}
3146
3147	/**
3148	 * Quotes a string, without prefixing nor appending quotes.
3149	 *
3150	 * @param string $s            The string to quote
3151	 * @param bool   $magic_quotes This param is not used since 5.21.0.
3152	 *                             It remains for backwards compatibility.
3153	 *
3154	 * @return string Quoted string
3155	 *
3156	 * @noinspection PhpUnusedParameterInspection
3157	 */
3158	function addQ($s, $magic_quotes=false) {
3159		if ($this->replaceQuote[0] == '\\') {
3160			$s = str_replace(
3161				array('\\', "\0"),
3162				array('\\\\', "\\\0"),
3163				$s
3164			);
3165		}
3166		return str_replace("'", $this->replaceQuote, $s);
3167	}
3168
3169	/**
3170	 * Correctly quotes a string so that all strings are escaped.
3171	 * We prefix and append to the string single-quotes.
3172	 * An example is  $db->qstr("Don't bother");
3173	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:qstr
3174	 *
3175	 * @param string $s            The string to quote
3176	 * @param bool   $magic_quotes This param is not used since 5.21.0.
3177	 *                             It remains for backwards compatibility.
3178	 *
3179	 * @return string Quoted string to be sent back to database
3180	 *
3181	 * @noinspection PhpUnusedParameterInspection
3182	 */
3183	function qStr($s, $magic_quotes=false) {
3184		return  "'" . $this->addQ($s) . "'";
3185	}
3186
3187
3188	/**
3189	 * Will select the supplied $page number from a recordset, given that it is paginated in pages of
3190	 * $nrows rows per page. It also saves two boolean values saying if the given page is the first
3191	 * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
3192	 *
3193	 * See docs-adodb.htm#ex8 for an example of usage.
3194	 * NOTE: phpLens uses a different algorithm and does not use PageExecute().
3195	 *
3196	 * @param string $sql
3197	 * @param int    $nrows          Number of rows per page to get
3198	 * @param int    $page           Page number to get (1-based)
3199	 * @param mixed[]|bool $inputarr Array of bind variables
3200	 * @param int    $secs2cache     Private parameter only used by jlim
3201	 *
3202	 * @return mixed		the recordset ($rs->databaseType == 'array')
3203	 */
3204	function PageExecute($sql, $nrows, $page, $inputarr=false, $secs2cache=0) {
3205		global $ADODB_INCLUDED_LIB;
3206		if (empty($ADODB_INCLUDED_LIB)) {
3207			include_once(ADODB_DIR.'/adodb-lib.inc.php');
3208		}
3209		if ($this->pageExecuteCountRows) {
3210			$rs = _adodb_pageexecute_all_rows($this, $sql, $nrows, $page, $inputarr, $secs2cache);
3211		} else {
3212			$rs = _adodb_pageexecute_no_last_page($this, $sql, $nrows, $page, $inputarr, $secs2cache);
3213		}
3214		return $rs;
3215	}
3216
3217
3218	/**
3219	* Will select the supplied $page number from a recordset, given that it is paginated in pages of
3220	* $nrows rows per page. It also saves two boolean values saying if the given page is the first
3221	* and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
3222	*
3223	* @param int $secs2cache	seconds to cache data, set to 0 to force query
3224	* @param string $sql
3225	* @param int $nrows		is the number of rows per page to get
3226	* @param int $page		is the page number to get (1-based)
3227	* @param mixed[]|bool $inputarr	array of bind variables
3228	* @return mixed	the recordset ($rs->databaseType == 'array')
3229	*/
3230	function CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) {
3231		/*switch($this->dataProvider) {
3232		case 'postgres':
3233		case 'mysql':
3234			break;
3235		default: $secs2cache = 0; break;
3236		}*/
3237		return $this->PageExecute($sql,$nrows,$page,$inputarr,$secs2cache);
3238	}
3239
3240	/**
3241	* Returns the maximum size of a MetaType C field. If the method
3242	* is not defined in the driver returns ADODB_STRINGMAX_NOTSET
3243	*
3244	* @return int
3245	*/
3246	function charMax() {
3247		return ADODB_STRINGMAX_NOTSET;
3248	}
3249
3250	/**
3251	* Returns the maximum size of a MetaType X field. If the method
3252	* is not defined in the driver returns ADODB_STRINGMAX_NOTSET
3253	*
3254	* @return int
3255	*/
3256	function textMax() {
3257		return ADODB_STRINGMAX_NOTSET;
3258	}
3259
3260	/**
3261	* Returns a substring of a varchar type field
3262	*
3263	* Some databases have variations of the parameters, which is why
3264	* we have an ADOdb function for it
3265	*
3266	* @param	string	$fld	The field to sub-string
3267	* @param	int		$start	The start point
3268	* @param	int		$length	An optional length
3269	*
3270	* @return string	The SQL text
3271	*/
3272	function substr($fld,$start,$length=0) {
3273		$text = "{$this->substr}($fld,$start";
3274		if ($length > 0)
3275			$text .= ",$length";
3276		$text .= ')';
3277		return $text;
3278	}
3279
3280	/*
3281	 * Formats the date into Month only format MM with leading zeroes
3282	 *
3283	 * @param	string		$fld	The name of the date to format
3284	 *
3285	 * @return	string				The SQL text
3286	 */
3287	function month($fld) {
3288		return $this->sqlDate('m',$fld);
3289	}
3290
3291	/*
3292	 * Formats the date into Day only format DD with leading zeroes
3293	 *
3294	 * @param	string		$fld	The name of the date to format
3295	 * @return	string		The SQL text
3296	 */
3297	function day($fld) {
3298		return $this->sqlDate('d',$fld);
3299	}
3300
3301	/*
3302	 * Formats the date into year only format YYYY
3303	 *
3304	 * @param	string		$fld The name of the date to format
3305	 *
3306	 * @return	string		The SQL text
3307	 */
3308	function year($fld) {
3309		return $this->sqlDate('Y',$fld);
3310	}
3311
3312	/**
3313	 * Get the last error recorded by PHP and clear the message.
3314	 *
3315	 * By clearing the message, it becomes possible to detect whether a new error
3316	 * has occurred, even when it is the same error as before being repeated.
3317	 *
3318	 * @return mixed[]|null Array if an error has previously occurred. Null otherwise.
3319	 */
3320	protected function resetLastError() {
3321		$error = error_get_last();
3322
3323		if (is_array($error)) {
3324			$error['message'] = '';
3325		}
3326
3327		return $error;
3328	}
3329
3330	/**
3331	 * Compare a previously stored error message with the last error recorded by PHP
3332	 * to determine whether a new error has occurred.
3333	 *
3334	 * @param mixed[]|null $old Optional. Previously stored return value of error_get_last().
3335	 *
3336	 * @return string The error message if a new error has occurred
3337	 *                or an empty string if no (new) errors have occurred..
3338	 */
3339	protected function getChangedErrorMsg($old = null) {
3340		$new = error_get_last();
3341
3342		if (is_null($new)) {
3343			// No error has occurred yet at all.
3344			return '';
3345		}
3346
3347		if (is_null($old)) {
3348			// First error recorded.
3349			return $new['message'];
3350		}
3351
3352		$changed = false;
3353		foreach($new as $key => $value) {
3354			if ($new[$key] !== $old[$key]) {
3355				$changed = true;
3356				break;
3357			}
3358		}
3359
3360		if ($changed === true) {
3361			return $new['message'];
3362		}
3363
3364		return '';
3365	}
3366
3367} // end class ADOConnection
3368
3369
3370
3371	//==============================================================================================
3372	// CLASS ADOFetchObj
3373	//==============================================================================================
3374
3375	/**
3376	* Internal placeholder for record objects. Used by ADORecordSet->FetchObj().
3377	*/
3378	class ADOFetchObj {
3379	};
3380
3381	//==============================================================================================
3382	// CLASS ADORecordSet_empty
3383	//==============================================================================================
3384
3385	class ADODB_Iterator_empty implements Iterator {
3386
3387		private $rs;
3388
3389		function __construct($rs) {
3390			$this->rs = $rs;
3391		}
3392
3393		function rewind() {}
3394
3395		function valid() {
3396			return !$this->rs->EOF;
3397		}
3398
3399		function key() {
3400			return false;
3401		}
3402
3403		function current() {
3404			return false;
3405		}
3406
3407		function next() {}
3408
3409		function __call($func, $params) {
3410			return call_user_func_array(array($this->rs, $func), $params);
3411		}
3412
3413		function hasMore() {
3414			return false;
3415		}
3416
3417	}
3418
3419
3420	/**
3421	* Lightweight recordset when there are no records to be returned
3422	*/
3423	class ADORecordSet_empty implements IteratorAggregate
3424	{
3425		var $dataProvider = 'empty';
3426		var $databaseType = false;
3427		var $EOF = true;
3428		var $_numOfRows = 0;
3429		/** @var bool|array  */
3430		var $fields = false;
3431		var $connection = false;
3432
3433		function RowCount() {
3434			return 0;
3435		}
3436
3437		function RecordCount() {
3438			return 0;
3439		}
3440
3441		function PO_RecordCount() {
3442			return 0;
3443		}
3444
3445		function Close() {
3446			return true;
3447		}
3448
3449		function FetchRow() {
3450			return false;
3451		}
3452
3453		function FieldCount() {
3454			return 0;
3455		}
3456
3457		function Init() {}
3458
3459		function getIterator() {
3460			return new ADODB_Iterator_empty($this);
3461		}
3462
3463		function GetAssoc() {
3464			return array();
3465		}
3466
3467		function GetArray() {
3468			return array();
3469		}
3470
3471		function GetAll() {
3472			return array();
3473		}
3474
3475		function GetArrayLimit() {
3476			return array();
3477		}
3478
3479		function GetRows() {
3480			return array();
3481		}
3482
3483		function GetRowAssoc() {
3484			return array();
3485		}
3486
3487		function MaxRecordCount() {
3488			return 0;
3489		}
3490
3491		function NumRows() {
3492			return 0;
3493		}
3494
3495		function NumCols() {
3496			return 0;
3497		}
3498	}
3499
3500	//==============================================================================================
3501	// DATE AND TIME FUNCTIONS
3502	//==============================================================================================
3503	if (!defined('ADODB_DATE_VERSION')) {
3504		include_once(ADODB_DIR.'/adodb-time.inc.php');
3505	}
3506
3507	//==============================================================================================
3508	// CLASS ADORecordSet
3509	//==============================================================================================
3510
3511	class ADODB_Iterator implements Iterator {
3512
3513		private $rs;
3514
3515		function __construct($rs) {
3516			$this->rs = $rs;
3517		}
3518
3519		function rewind() {
3520			$this->rs->MoveFirst();
3521		}
3522
3523		function valid() {
3524			return !$this->rs->EOF;
3525		}
3526
3527		function key() {
3528			return $this->rs->_currentRow;
3529		}
3530
3531		function current() {
3532			return $this->rs->fields;
3533		}
3534
3535		function next() {
3536			$this->rs->MoveNext();
3537		}
3538
3539		function __call($func, $params) {
3540			return call_user_func_array(array($this->rs, $func), $params);
3541		}
3542
3543		function hasMore() {
3544			return !$this->rs->EOF;
3545		}
3546
3547	}
3548
3549
3550	/**
3551	 * RecordSet class that represents the dataset returned by the database.
3552	 * To keep memory overhead low, this class holds only the current row in memory.
3553	 * No prefetching of data is done, so the RecordCount() can return -1 ( which
3554	 * means recordcount not known).
3555	 */
3556	class ADORecordSet implements IteratorAggregate {
3557
3558	/**
3559	 * public variables
3560	 */
3561	var $dataProvider = "native";
3562	/** @var bool|array  */
3563	var $fields = false;	/// holds the current row data
3564	var $blobSize = 100;	/// any varchar/char field this size or greater is treated as a blob
3565							/// in other words, we use a text area for editing.
3566	var $canSeek = false;	/// indicates that seek is supported
3567	var $sql;				/// sql text
3568	var $EOF = false;		/// Indicates that the current record position is after the last record in a Recordset object.
3569
3570	var $emptyTimeStamp = '&nbsp;'; /// what to display when $time==0
3571	var $emptyDate = '&nbsp;'; /// what to display when $time==0
3572	var $debug = false;
3573	var $timeCreated=0;		/// datetime in Unix format rs created -- for cached recordsets
3574
3575	var $bind = false;		/// used by Fields() to hold array - should be private?
3576	var $fetchMode;			/// default fetch mode
3577	var $connection = false; /// the parent connection
3578
3579	/**
3580	 *	private variables
3581	 */
3582	var $_numOfRows = -1;	/** number of rows, or -1 */
3583	var $_numOfFields = -1;	/** number of fields in recordset */
3584	var $_queryID = -1;		/** This variable keeps the result link identifier.	*/
3585	var $_currentRow = -1;	/** This variable keeps the current row in the Recordset.	*/
3586	var $_closed = false;	/** has recordset been closed */
3587	var $_inited = false;	/** Init() should only be called once */
3588	var $_obj;				/** Used by FetchObj */
3589	var $_names;			/** Used by FetchObj */
3590
3591	var $_currentPage = -1;	/** Added by Iván Oliva to implement recordset pagination */
3592	var $_atFirstPage = false;	/** Added by Iván Oliva to implement recordset pagination */
3593	var $_atLastPage = false;	/** Added by Iván Oliva to implement recordset pagination */
3594	var $_lastPageNo = -1;
3595	var $_maxRecordCount = 0;
3596	var $datetime = false;
3597
3598	/**
3599	 * Constructor
3600	 *
3601	 * @param resource|int queryID	this is the queryID returned by ADOConnection->_query()
3602	 *
3603	 */
3604	function __construct($queryID) {
3605		$this->_queryID = $queryID;
3606	}
3607
3608	function __destruct() {
3609		$this->Close();
3610	}
3611
3612	function getIterator() {
3613		return new ADODB_Iterator($this);
3614	}
3615
3616	/* this is experimental - i don't really know what to return... */
3617	function __toString() {
3618		include_once(ADODB_DIR.'/toexport.inc.php');
3619		return _adodb_export($this,',',',',false,true);
3620	}
3621
3622	function Init() {
3623		if ($this->_inited) {
3624			return;
3625		}
3626		$this->_inited = true;
3627		if ($this->_queryID) {
3628			@$this->_initrs();
3629		} else {
3630			$this->_numOfRows = 0;
3631			$this->_numOfFields = 0;
3632		}
3633		if ($this->_numOfRows != 0 && $this->_numOfFields && $this->_currentRow == -1) {
3634			$this->_currentRow = 0;
3635			if ($this->EOF = ($this->_fetch() === false)) {
3636				$this->_numOfRows = 0; // _numOfRows could be -1
3637			}
3638		} else {
3639			$this->EOF = true;
3640		}
3641	}
3642
3643
3644	/**
3645	 * Generate a SELECT tag from a recordset, and return the HTML markup.
3646	 *
3647	 * If the recordset has 2 columns, we treat the first one as the text to
3648	 * display to the user, and the second as the return value. Extra columns
3649	 * are discarded.
3650	 *
3651	 * @param string       $name            Name of SELECT tag
3652	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
3653	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
3654	 *                                      'string' to set its label and 'value:string' to assign a value to it.
3655	 * @param bool         $multiple        True for multi-select list
3656	 * @param int          $size            Number of rows to show (applies to multi-select box only)
3657	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
3658	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
3659	 * @param bool         $compareFirstCol When true (default), $defstr is compared against the value (column 2),
3660	 *                                      while false will compare against the description (column 1).
3661	 *
3662	 * @return string HTML
3663	 */
3664	function getMenu($name, $defstr = '', $blank1stItem = true, $multiple = false,
3665					 $size = 0, $selectAttr = '', $compareFirstCol = true)
3666	{
3667		global $ADODB_INCLUDED_LIB;
3668		if (empty($ADODB_INCLUDED_LIB)) {
3669			include_once(ADODB_DIR.'/adodb-lib.inc.php');
3670		}
3671		return _adodb_getmenu($this, $name, $defstr, $blank1stItem, $multiple,
3672			$size, $selectAttr, $compareFirstCol);
3673	}
3674
3675	/**
3676	 * Generate a SELECT tag with groups from a recordset, and return the HTML markup.
3677	 *
3678	 * The recordset must have 3 columns and be ordered by the 3rd column. The
3679	 * first column contains the text to display to the user, the second is the
3680	 * return value and the third is the option group. Extra columns are discarded.
3681	 * Default strings are compared with the SECOND column.
3682	 *
3683	 * @param string       $name            Name of SELECT tag
3684	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
3685	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
3686	 *                                      'string' to set its label and 'value:string' to assign a value to it.
3687	 * @param bool         $multiple        True for multi-select list
3688	 * @param int          $size            Number of rows to show (applies to multi-select box only)
3689	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
3690	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
3691	 * @param bool         $compareFirstCol When true (default), $defstr is compared against the value (column 2),
3692	 *                                      while false will compare against the description (column 1).
3693	 *
3694	 * @return string HTML
3695	 */
3696	function getMenuGrouped($name, $defstr = '', $blank1stItem = true, $multiple = false,
3697							$size = 0, $selectAttr = '', $compareFirstCol = true)
3698	{
3699		global $ADODB_INCLUDED_LIB;
3700		if (empty($ADODB_INCLUDED_LIB)) {
3701			include_once(ADODB_DIR.'/adodb-lib.inc.php');
3702		}
3703		return _adodb_getmenu_gp($this, $name, $defstr, $blank1stItem, $multiple,
3704			$size, $selectAttr, $compareFirstCol);
3705	}
3706
3707	/**
3708	 * Generate a SELECT tag from a recordset, and return the HTML markup.
3709	 *
3710	 * Same as GetMenu(), except that default strings are compared with the
3711	 * FIRST column (the description).
3712	 *
3713	 * @param string       $name            Name of SELECT tag
3714	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
3715	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
3716	 *                                      'string' to set its label and 'value:string' to assign a value to it.
3717	 * @param bool         $multiple        True for multi-select list
3718	 * @param int          $size            Number of rows to show (applies to multi-select box only)
3719	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
3720	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
3721	 *
3722	 * @return string HTML
3723	 *
3724	 * @deprecated 5.21.0 Use getMenu() with $compareFirstCol = false instead.
3725	 */
3726	function getMenu2($name, $defstr = '', $blank1stItem = true, $multiple = false,
3727					  $size = 0, $selectAttr = '')
3728	{
3729		return $this->getMenu($name, $defstr, $blank1stItem, $multiple,
3730			$size, $selectAttr,false);
3731	}
3732
3733	/**
3734	 * Generate a SELECT tag with groups from a recordset, and return the HTML markup.
3735	 *
3736	 * Same as GetMenuGrouped(), except that default strings are compared with the
3737	 * FIRST column (the description).
3738	 *
3739	 * @param string       $name            Name of SELECT tag
3740	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
3741	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
3742	 *                                      'string' to set its label and 'value:string' to assign a value to it.
3743	 * @param bool         $multiple        True for multi-select list
3744	 * @param int          $size            Number of rows to show (applies to multi-select box only)
3745	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
3746	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
3747	 *
3748	 * @return string HTML
3749	 *
3750	 * @deprecated 5.21.0 Use getMenuGrouped() with $compareFirstCol = false instead.
3751	 */
3752	function getMenu3($name, $defstr = '', $blank1stItem = true, $multiple = false,
3753					  $size = 0, $selectAttr = '')
3754	{
3755		return $this->getMenuGrouped($name, $defstr, $blank1stItem, $multiple,
3756			$size, $selectAttr, false);
3757	}
3758
3759	/**
3760	 * return recordset as a 2-dimensional array.
3761	 *
3762	 * @param int $nRows  Number of rows to return. -1 means every row.
3763	 *
3764	 * @return array indexed by the rows (0-based) from the recordset
3765	 */
3766	function GetArray($nRows = -1) {
3767		$results = array();
3768		$cnt = 0;
3769		while (!$this->EOF && $nRows != $cnt) {
3770			$results[] = $this->fields;
3771			$this->MoveNext();
3772			$cnt++;
3773		}
3774		return $results;
3775	}
3776
3777	function GetAll($nRows = -1) {
3778		return $this->GetArray($nRows);
3779	}
3780
3781	/*
3782	* Some databases allow multiple recordsets to be returned. This function
3783	* will return true if there is a next recordset, or false if no more.
3784	*/
3785	function NextRecordSet() {
3786		return false;
3787	}
3788
3789	/**
3790	 * return recordset as a 2-dimensional array.
3791	 * Helper function for ADOConnection->SelectLimit()
3792	 *
3793	 * @param offset	is the row to start calculations from (1-based)
3794	 * @param [nrows]	is the number of rows to return
3795	 *
3796	 * @return array an array indexed by the rows (0-based) from the recordset
3797	 */
3798	function GetArrayLimit($nrows,$offset=-1) {
3799		if ($offset <= 0) {
3800			return $this->GetArray($nrows);
3801		}
3802
3803		$this->Move($offset);
3804
3805		$results = array();
3806		$cnt = 0;
3807		while (!$this->EOF && $nrows != $cnt) {
3808			$results[$cnt++] = $this->fields;
3809			$this->MoveNext();
3810		}
3811
3812		return $results;
3813	}
3814
3815
3816	/**
3817	 * Synonym for GetArray() for compatibility with ADO.
3818	 *
3819	 * @param [nRows]  is the number of rows to return. -1 means every row.
3820	 *
3821	 * @return array an array indexed by the rows (0-based) from the recordset
3822	 */
3823	function GetRows($nRows = -1) {
3824		return $this->GetArray($nRows);
3825	}
3826
3827	/**
3828	 * return whole recordset as a 2-dimensional associative array if
3829	 * there are more than 2 columns. The first column is treated as the
3830	 * key and is not included in the array. If there is only 2 columns,
3831	 * it will return a 1 dimensional array of key-value pairs unless
3832	 * $force_array == true. This recordset method is currently part of
3833	 * the API, but may not be in later versions of ADOdb. By preference, use
3834	 * ADOconnnection::getAssoc()
3835	 *
3836	 * @param bool	$force_array	(optional) Has only meaning if we have 2 data
3837	 *								columns. If false, a 1 dimensional
3838	 * 								array is returned, otherwise a 2 dimensional
3839	 *								array is returned. If this sounds confusing,
3840	 * 								read the source.
3841	 *
3842	 * @param bool	$first2cols 	(optional) Means if there are more than
3843	 *								2 cols, ignore the remaining cols and
3844	 * 								instead of returning
3845	 *								array[col0] => array(remaining cols),
3846	 *								return array[col0] => col1
3847	 *
3848	 * @return mixed[]|false
3849	 *
3850	 */
3851	function getAssoc($force_array = false, $first2cols = false)
3852	{
3853		/*
3854		* Insufficient rows to show data
3855		*/
3856		if ($this->_numOfFields < 2)
3857			  return;
3858
3859		/*
3860		* Empty recordset
3861		*/
3862		if (!$this->fields) {
3863			return array();
3864		}
3865
3866		/*
3867		* The number of fields is half the actual returned in BOTH mode
3868		*/
3869		$numberOfFields = $this->_numOfFields;
3870
3871		/*
3872		* Get the fetch mode when the call was executed, this may be
3873		* different than ADODB_FETCH_MODE
3874		*/
3875		$fetchMode = $this->connection->fetchMode;
3876		if ($fetchMode == ADODB_FETCH_BOTH) {
3877			/*
3878			* If we are using BOTH, we present the data as if it
3879			* was in ASSOC mode. This could be enhanced by adding
3880			* a BOTH_ASSOC_MODE class property
3881			* We build a template of numeric keys. you could improve the
3882			* speed by caching this, indexed by number of keys
3883			*/
3884			$testKeys = array_fill(0,$numberOfFields,0);
3885		}
3886
3887		$showArrayMethod = 0;
3888
3889		if ($numberOfFields == 2)
3890			/*
3891			* Key is always value of first element
3892			* Value is always value of second element
3893			*/
3894			$showArrayMethod = 1;
3895
3896		if ($force_array)
3897			$showArrayMethod = 0;
3898
3899		if ($first2cols)
3900			$showArrayMethod = 1;
3901
3902		$results  = array();
3903
3904		while (!$this->EOF){
3905
3906			$myFields = $this->fields;
3907
3908			if ($fetchMode == ADODB_FETCH_BOTH) {
3909				/*
3910				* extract the associative keys
3911				*/
3912				$myFields = array_diff_key($myFields,$testKeys);
3913			}
3914
3915			/*
3916			* key is value of first element, rest is data,
3917			* The key is not case processed
3918			*/
3919			$key = array_shift($myFields);
3920
3921			switch ($showArrayMethod) {
3922			case 0:
3923
3924				if ($fetchMode == ADODB_FETCH_ASSOC
3925				||  $fetchMode == ADODB_FETCH_BOTH)
3926				{
3927					/*
3928					* The driver should have already handled the key
3929					* casing, but in case it did not. We will check and force
3930					* this in later versions of ADOdb
3931					*/
3932					if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_UPPER)
3933						$myFields = array_change_key_case($myFields,CASE_UPPER);
3934
3935					elseif (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER)
3936						$myFields = array_change_key_case($myFields,CASE_LOWER);
3937
3938					/*
3939					* We have already shifted the key off
3940					* the front, so the rest is the value
3941					*/
3942					$results[$key] = $myFields;
3943
3944				}
3945				else
3946					/*
3947					 * I want the values in a numeric array,
3948					 * nicely re-indexed from zero
3949					 */
3950					$results[$key] = array_values($myFields);
3951				break;
3952
3953			case 1:
3954
3955				/*
3956				 * Don't care how long the array is,
3957				 * I just want value of second column, and it doesn't
3958				 * matter whether the array is associative or numeric
3959				 */
3960				$results[$key] = array_shift($myFields);
3961				break;
3962			}
3963
3964			$this->MoveNext();
3965		}
3966		/*
3967		 * Done
3968		 */
3969		return $results;
3970	}
3971
3972	/**
3973	 *
3974	 * @param mixed $v		is the character timestamp in YYYY-MM-DD hh:mm:ss format
3975	 * @param string [$fmt]	is the format to apply to it, using date()
3976	 *
3977	 * @return string a timestamp formated as user desires
3978	 */
3979	function UserTimeStamp($v,$fmt='Y-m-d H:i:s') {
3980		if (is_numeric($v) && strlen($v)<14) {
3981			return adodb_date($fmt,$v);
3982		}
3983		$tt = $this->UnixTimeStamp($v);
3984		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
3985		if (($tt === false || $tt == -1) && $v != false) {
3986			return $v;
3987		}
3988		if ($tt === 0) {
3989			return $this->emptyTimeStamp;
3990		}
3991		return adodb_date($fmt,$tt);
3992	}
3993
3994
3995	/**
3996	 * @param mixed $v		is the character date in YYYY-MM-DD format, returned by database
3997	 * @param string $fmt	is the format to apply to it, using date()
3998	 *
3999	 * @return string a date formatted as user desires
4000	 */
4001	function UserDate($v,$fmt='Y-m-d') {
4002		$tt = $this->UnixDate($v);
4003		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
4004		if (($tt === false || $tt == -1) && $v != false) {
4005			return $v;
4006		} else if ($tt == 0) {
4007			return $this->emptyDate;
4008		} else if ($tt == -1) {
4009			// pre-TIMESTAMP_FIRST_YEAR
4010		}
4011		return adodb_date($fmt,$tt);
4012	}
4013
4014
4015	/**
4016	 * @param mixed $v is a date string in YYYY-MM-DD format
4017	 *
4018	 * @return string date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
4019	 */
4020	static function UnixDate($v) {
4021		return ADOConnection::UnixDate($v);
4022	}
4023
4024
4025	/**
4026	 * @param string|object $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
4027	 *
4028	 * @return mixed date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
4029	 */
4030	static function UnixTimeStamp($v) {
4031		return ADOConnection::UnixTimeStamp($v);
4032	}
4033
4034
4035	/**
4036	* PEAR DB Compat - do not use internally
4037	*/
4038	function Free() {
4039		return $this->Close();
4040	}
4041
4042
4043	/**
4044	 * PEAR DB compat, number of rows
4045	 *
4046	 * @return int
4047	 */
4048	function NumRows() {
4049		return $this->_numOfRows;
4050	}
4051
4052
4053	/**
4054	 * PEAR DB compat, number of cols
4055	 *
4056	 * @return int
4057	 */
4058	function NumCols() {
4059		return $this->_numOfFields;
4060	}
4061
4062	/**
4063	 * Fetch a row, returning false if no more rows.
4064	 * This is PEAR DB compat mode.
4065	 *
4066	 * @return mixed[]|false false or array containing the current record
4067	 */
4068	function FetchRow() {
4069		if ($this->EOF) {
4070			return false;
4071		}
4072		$arr = $this->fields;
4073		$this->_currentRow++;
4074		if (!$this->_fetch()) {
4075			$this->EOF = true;
4076		}
4077		return $arr;
4078	}
4079
4080
4081	/**
4082	* Fetch a row, returning PEAR_Error if no more rows.
4083	* This is PEAR DB compat mode.
4084	*
4085	* @param mixed[]|false $arr
4086	*
4087	* @return mixed DB_OK or error object
4088	*/
4089	function FetchInto(&$arr) {
4090		if ($this->EOF) {
4091			return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false;
4092		}
4093		$arr = $this->fields;
4094		$this->MoveNext();
4095		return 1; // DB_OK
4096	}
4097
4098
4099	/**
4100	 * Move to the first row in the recordset. Many databases do NOT support this.
4101	 *
4102	 * @return bool true or false
4103	 */
4104	function MoveFirst() {
4105		if ($this->_currentRow == 0) {
4106			return true;
4107		}
4108		return $this->Move(0);
4109	}
4110
4111
4112	/**
4113	 * Move to the last row in the recordset.
4114	 *
4115	 * @return bool true or false
4116	 */
4117	function MoveLast() {
4118		if ($this->_numOfRows >= 0) {
4119			return $this->Move($this->_numOfRows-1);
4120		}
4121		if ($this->EOF) {
4122			return false;
4123		}
4124		while (!$this->EOF) {
4125			$f = $this->fields;
4126			$this->MoveNext();
4127		}
4128		$this->fields = $f;
4129		$this->EOF = false;
4130		return true;
4131	}
4132
4133
4134	/**
4135	 * Move to next record in the recordset.
4136	 *
4137	 * @return bool true if there still rows available, or false if there are no more rows (EOF).
4138	 */
4139	function MoveNext() {
4140		if (!$this->EOF) {
4141			$this->_currentRow++;
4142			if ($this->_fetch()) {
4143				return true;
4144			}
4145		}
4146		$this->EOF = true;
4147		/* -- tested error handling when scrolling cursor -- seems useless.
4148		$conn = $this->connection;
4149		if ($conn && $conn->raiseErrorFn && ($errno = $conn->ErrorNo())) {
4150			$fn = $conn->raiseErrorFn;
4151			$fn($conn->databaseType,'MOVENEXT',$errno,$conn->ErrorMsg().' ('.$this->sql.')',$conn->host,$conn->database);
4152		}
4153		*/
4154		return false;
4155	}
4156
4157
4158	/**
4159	 * Random access to a specific row in the recordset. Some databases do not support
4160	 * access to previous rows in the databases (no scrolling backwards).
4161	 *
4162	 * @param int $rowNumber is the row to move to (0-based)
4163	 *
4164	 * @return bool true if there still rows available, or false if there are no more rows (EOF).
4165	 */
4166	function Move($rowNumber = 0) {
4167		$this->EOF = false;
4168		if ($rowNumber == $this->_currentRow) {
4169			return true;
4170		}
4171		if ($rowNumber >= $this->_numOfRows) {
4172			if ($this->_numOfRows != -1) {
4173				$rowNumber = $this->_numOfRows-2;
4174			}
4175		}
4176
4177		if ($rowNumber < 0) {
4178			$this->EOF = true;
4179			return false;
4180		}
4181
4182		if ($this->canSeek) {
4183			if ($this->_seek($rowNumber)) {
4184				$this->_currentRow = $rowNumber;
4185				if ($this->_fetch()) {
4186					return true;
4187				}
4188			} else {
4189				$this->EOF = true;
4190				return false;
4191			}
4192		} else {
4193			if ($rowNumber < $this->_currentRow) {
4194				return false;
4195			}
4196			while (! $this->EOF && $this->_currentRow < $rowNumber) {
4197				$this->_currentRow++;
4198
4199				if (!$this->_fetch()) {
4200					$this->EOF = true;
4201				}
4202			}
4203			return !($this->EOF);
4204		}
4205
4206		$this->fields = false;
4207		$this->EOF = true;
4208		return false;
4209	}
4210
4211
4212	/**
4213	 * Get the value of a field in the current row by column name.
4214	 * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM.
4215	 *
4216	 * @param string $colname is the field to access
4217	 *
4218	 * @return mixed the value of $colname column
4219	 */
4220	function Fields($colname) {
4221		return $this->fields[$colname];
4222	}
4223
4224	/**
4225	 * Defines the function to use for table fields case conversion
4226	 * depending on ADODB_ASSOC_CASE
4227	 *
4228	 * @param int [$case]
4229	 *
4230	 * @return string strtolower/strtoupper or false if no conversion needed
4231	 */
4232	protected function AssocCaseConvertFunction($case = ADODB_ASSOC_CASE) {
4233		switch($case) {
4234			case ADODB_ASSOC_CASE_UPPER:
4235				return 'strtoupper';
4236			case ADODB_ASSOC_CASE_LOWER:
4237				return 'strtolower';
4238			case ADODB_ASSOC_CASE_NATIVE:
4239			default:
4240				return false;
4241		}
4242	}
4243
4244	/**
4245	 * Builds the bind array associating keys to recordset fields
4246	 *
4247	 * @param int [$upper] Case for the array keys, defaults to uppercase
4248	 *                   (see ADODB_ASSOC_CASE_xxx constants)
4249	 */
4250	function GetAssocKeys($upper = ADODB_ASSOC_CASE) {
4251		if ($this->bind) {
4252			return;
4253		}
4254		$this->bind = array();
4255
4256		// Define case conversion function for ASSOC fetch mode
4257		$fn_change_case = $this->AssocCaseConvertFunction($upper);
4258
4259		// Build the bind array
4260		for ($i=0; $i < $this->_numOfFields; $i++) {
4261			$o = $this->FetchField($i);
4262
4263			// Set the array's key
4264			if(is_numeric($o->name)) {
4265				// Just use the field ID
4266				$key = $i;
4267			}
4268			elseif( $fn_change_case ) {
4269				// Convert the key's case
4270				$key = $fn_change_case($o->name);
4271			}
4272			else {
4273				$key = $o->name;
4274			}
4275
4276			$this->bind[$key] = $i;
4277		}
4278	}
4279
4280	/**
4281	 * Use associative array to get fields array for databases that do not support
4282	 * associative arrays. Submitted by Paolo S. Asioli paolo.asioli#libero.it
4283	 *
4284	 * @param int [$upper] Case for the array keys, defaults to uppercase
4285	 *                   (see ADODB_ASSOC_CASE_xxx constants)
4286	 */
4287	function GetRowAssoc($upper = ADODB_ASSOC_CASE) {
4288		$record = array();
4289		$this->GetAssocKeys($upper);
4290
4291		foreach($this->bind as $k => $v) {
4292			if( array_key_exists( $v, $this->fields ) ) {
4293				$record[$k] = $this->fields[$v];
4294			} elseif( array_key_exists( $k, $this->fields ) ) {
4295				$record[$k] = $this->fields[$k];
4296			} else {
4297				# This should not happen... trigger error ?
4298				$record[$k] = null;
4299			}
4300		}
4301		return $record;
4302	}
4303
4304	/**
4305	 * Clean up recordset
4306	 *
4307	 * @return bool
4308	 */
4309	function Close() {
4310		// free connection object - this seems to globally free the object
4311		// and not merely the reference, so don't do this...
4312		// $this->connection = false;
4313		if (!$this->_closed) {
4314			$this->_closed = true;
4315			return $this->_close();
4316		} else
4317			return true;
4318	}
4319
4320	/**
4321	 * Synonyms RecordCount and RowCount
4322	 *
4323	 * @return int Number of rows or -1 if this is not supported
4324	 */
4325	function RecordCount() {
4326		return $this->_numOfRows;
4327	}
4328
4329
4330	/**
4331	 * If we are using PageExecute(), this will return the maximum possible rows
4332	 * that can be returned when paging a recordset.
4333	 *
4334	 * @return int
4335	 */
4336	function MaxRecordCount() {
4337		return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->RecordCount();
4338	}
4339
4340	/**
4341	 * synonyms RecordCount and RowCount
4342	 *
4343	 * @return the number of rows or -1 if this is not supported
4344	 */
4345	function RowCount() {
4346		return $this->_numOfRows;
4347	}
4348
4349
4350	 /**
4351	 * Portable RecordCount. Pablo Roca <pabloroca@mvps.org>
4352	 *
4353	 * @return  the number of records from a previous SELECT. All databases support this.
4354	 *
4355	 * But aware possible problems in multiuser environments. For better speed the table
4356	 * must be indexed by the condition. Heavy test this before deploying.
4357	 */
4358	function PO_RecordCount($table="", $condition="") {
4359
4360		$lnumrows = $this->_numOfRows;
4361		// the database doesn't support native recordcount, so we do a workaround
4362		if ($lnumrows == -1 && $this->connection) {
4363			IF ($table) {
4364				if ($condition) {
4365					$condition = " WHERE " . $condition;
4366				}
4367				$resultrows = $this->connection->Execute("SELECT COUNT(*) FROM $table $condition");
4368				if ($resultrows) {
4369					$lnumrows = reset($resultrows->fields);
4370				}
4371			}
4372		}
4373		return $lnumrows;
4374	}
4375
4376
4377	/**
4378	 * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
4379	 */
4380	function CurrentRow() {
4381		return $this->_currentRow;
4382	}
4383
4384	/**
4385	 * synonym for CurrentRow -- for ADO compat
4386	 *
4387	 * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
4388	 */
4389	function AbsolutePosition() {
4390		return $this->_currentRow;
4391	}
4392
4393	/**
4394	 * @return the number of columns in the recordset. Some databases will set this to 0
4395	 * if no records are returned, others will return the number of columns in the query.
4396	 */
4397	function FieldCount() {
4398		return $this->_numOfFields;
4399	}
4400
4401	/**
4402	 * Get the ADOFieldObject of a specific column.
4403	 *
4404	 * @param fieldoffset	is the column position to access(0-based).
4405	 *
4406	 * @return the ADOFieldObject for that column, or false.
4407	 */
4408	function FetchField($fieldoffset = -1) {
4409		// must be defined by child class
4410
4411		return false;
4412	}
4413
4414	/**
4415	 * Get the ADOFieldObjects of all columns in an array.
4416	 *
4417	 */
4418	function FieldTypesArray() {
4419		static $arr = array();
4420		if (empty($arr)) {
4421			for ($i=0, $max=$this->_numOfFields; $i < $max; $i++) {
4422				$arr[] = $this->FetchField($i);
4423			}
4424		}
4425		return $arr;
4426	}
4427
4428	/**
4429	* Return the fields array of the current row as an object for convenience.
4430	* The default case is lowercase field names.
4431	*
4432	* @return the object with the properties set to the fields of the current row
4433	*/
4434	function FetchObj() {
4435		return $this->FetchObject(false);
4436	}
4437
4438	/**
4439	* Return the fields array of the current row as an object for convenience.
4440	* The default case is uppercase.
4441	*
4442	* @param $isupper to set the object property names to uppercase
4443	*
4444	* @return the object with the properties set to the fields of the current row
4445	*/
4446	function FetchObject($isupper=true) {
4447		if (empty($this->_obj)) {
4448			$this->_obj = new ADOFetchObj();
4449			$this->_names = array();
4450			for ($i=0; $i <$this->_numOfFields; $i++) {
4451				$f = $this->FetchField($i);
4452				$this->_names[] = $f->name;
4453			}
4454		}
4455		$i = 0;
4456		$o = clone($this->_obj);
4457
4458		for ($i=0; $i <$this->_numOfFields; $i++) {
4459			$name = $this->_names[$i];
4460			if ($isupper) {
4461				$n = strtoupper($name);
4462			} else {
4463				$n = $name;
4464			}
4465
4466			$o->$n = $this->Fields($name);
4467		}
4468		return $o;
4469	}
4470
4471	/**
4472	* Return the fields array of the current row as an object for convenience.
4473	* The default is lower-case field names.
4474	*
4475	* @return the object with the properties set to the fields of the current row,
4476	*	or false if EOF
4477	*
4478	* Fixed bug reported by tim@orotech.net
4479	*/
4480	function FetchNextObj() {
4481		return $this->FetchNextObject(false);
4482	}
4483
4484
4485	/**
4486	* Return the fields array of the current row as an object for convenience.
4487	* The default is upper case field names.
4488	*
4489	* @param $isupper to set the object property names to uppercase
4490	*
4491	* @return the object with the properties set to the fields of the current row,
4492	*	or false if EOF
4493	*
4494	* Fixed bug reported by tim@orotech.net
4495	*/
4496	function FetchNextObject($isupper=true) {
4497		$o = false;
4498		if ($this->_numOfRows != 0 && !$this->EOF) {
4499			$o = $this->FetchObject($isupper);
4500			$this->_currentRow++;
4501			if ($this->_fetch()) {
4502				return $o;
4503			}
4504		}
4505		$this->EOF = true;
4506		return $o;
4507	}
4508
4509	/**
4510	 * Get the metatype of the column. This is used for formatting. This is because
4511	 * many databases use different names for the same type, so we transform the original
4512	 * type to our standardised version which uses 1 character codes:
4513	 *
4514	 * @param t  is the type passed in. Normally is ADOFieldObject->type.
4515	 * @param len is the maximum length of that field. This is because we treat character
4516	 *	fields bigger than a certain size as a 'B' (blob).
4517	 * @param fieldobj is the field object returned by the database driver. Can hold
4518	 *	additional info (eg. primary_key for mysql).
4519	 *
4520	 * @return the general type of the data:
4521	 *	C for character < 250 chars
4522	 *	X for teXt (>= 250 chars)
4523	 *	B for Binary
4524	 *	N for numeric or floating point
4525	 *	D for date
4526	 *	T for timestamp
4527	 *	L for logical/Boolean
4528	 *	I for integer
4529	 *	R for autoincrement counter/integer
4530	 *
4531	 *
4532	*/
4533	function MetaType($t,$len=-1,$fieldobj=false) {
4534		if (is_object($t)) {
4535			$fieldobj = $t;
4536			$t = $fieldobj->type;
4537			$len = $fieldobj->max_length;
4538		}
4539
4540
4541		// changed in 2.32 to hashing instead of switch stmt for speed...
4542		static $typeMap = array(
4543			'VARCHAR' => 'C',
4544			'VARCHAR2' => 'C',
4545			'CHAR' => 'C',
4546			'C' => 'C',
4547			'STRING' => 'C',
4548			'NCHAR' => 'C',
4549			'NVARCHAR' => 'C',
4550			'VARYING' => 'C',
4551			'BPCHAR' => 'C',
4552			'CHARACTER' => 'C',
4553			'INTERVAL' => 'C',  # Postgres
4554			'MACADDR' => 'C', # postgres
4555			'VAR_STRING' => 'C', # mysql
4556			##
4557			'LONGCHAR' => 'X',
4558			'TEXT' => 'X',
4559			'NTEXT' => 'X',
4560			'M' => 'X',
4561			'X' => 'X',
4562			'CLOB' => 'X',
4563			'NCLOB' => 'X',
4564			'LVARCHAR' => 'X',
4565			##
4566			'BLOB' => 'B',
4567			'IMAGE' => 'B',
4568			'BINARY' => 'B',
4569			'VARBINARY' => 'B',
4570			'LONGBINARY' => 'B',
4571			'B' => 'B',
4572			##
4573			'YEAR' => 'D', // mysql
4574			'DATE' => 'D',
4575			'D' => 'D',
4576			##
4577			'UNIQUEIDENTIFIER' => 'C', # MS SQL Server
4578			##
4579			'SMALLDATETIME' => 'T',
4580			'TIME' => 'T',
4581			'TIMESTAMP' => 'T',
4582			'DATETIME' => 'T',
4583			'DATETIME2' => 'T',
4584			'TIMESTAMPTZ' => 'T',
4585			'T' => 'T',
4586			'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql
4587			##
4588			'BOOL' => 'L',
4589			'BOOLEAN' => 'L',
4590			'BIT' => 'L',
4591			'L' => 'L',
4592			##
4593			'COUNTER' => 'R',
4594			'R' => 'R',
4595			'SERIAL' => 'R', // ifx
4596			'INT IDENTITY' => 'R',
4597			##
4598			'INT' => 'I',
4599			'INT2' => 'I',
4600			'INT4' => 'I',
4601			'INT8' => 'I',
4602			'INTEGER' => 'I',
4603			'INTEGER UNSIGNED' => 'I',
4604			'SHORT' => 'I',
4605			'TINYINT' => 'I',
4606			'SMALLINT' => 'I',
4607			'I' => 'I',
4608			##
4609			'LONG' => 'N', // interbase is numeric, oci8 is blob
4610			'BIGINT' => 'N', // this is bigger than PHP 32-bit integers
4611			'DECIMAL' => 'N',
4612			'DEC' => 'N',
4613			'REAL' => 'N',
4614			'DOUBLE' => 'N',
4615			'DOUBLE PRECISION' => 'N',
4616			'SMALLFLOAT' => 'N',
4617			'FLOAT' => 'N',
4618			'NUMBER' => 'N',
4619			'NUM' => 'N',
4620			'NUMERIC' => 'N',
4621			'MONEY' => 'N',
4622
4623			## informix 9.2
4624			'SQLINT' => 'I',
4625			'SQLSERIAL' => 'I',
4626			'SQLSMINT' => 'I',
4627			'SQLSMFLOAT' => 'N',
4628			'SQLFLOAT' => 'N',
4629			'SQLMONEY' => 'N',
4630			'SQLDECIMAL' => 'N',
4631			'SQLDATE' => 'D',
4632			'SQLVCHAR' => 'C',
4633			'SQLCHAR' => 'C',
4634			'SQLDTIME' => 'T',
4635			'SQLINTERVAL' => 'N',
4636			'SQLBYTES' => 'B',
4637			'SQLTEXT' => 'X',
4638			## informix 10
4639			"SQLINT8" => 'I8',
4640			"SQLSERIAL8" => 'I8',
4641			"SQLNCHAR" => 'C',
4642			"SQLNVCHAR" => 'C',
4643			"SQLLVARCHAR" => 'X',
4644			"SQLBOOL" => 'L'
4645		);
4646
4647
4648		$tmap = false;
4649		$t = strtoupper($t);
4650		$tmap = (isset($typeMap[$t])) ? $typeMap[$t] : ADODB_DEFAULT_METATYPE;
4651		switch ($tmap) {
4652			case 'C':
4653				// is the char field is too long, return as text field...
4654				if ($this->blobSize >= 0) {
4655					if ($len > $this->blobSize) {
4656						return 'X';
4657					}
4658				} else if ($len > 250) {
4659					return 'X';
4660				}
4661				return 'C';
4662
4663			case 'I':
4664				if (!empty($fieldobj->primary_key)) {
4665					return 'R';
4666				}
4667				return 'I';
4668
4669			case false:
4670				return 'N';
4671
4672			case 'B':
4673				if (isset($fieldobj->binary)) {
4674					return ($fieldobj->binary) ? 'B' : 'X';
4675				}
4676				return 'B';
4677
4678			case 'D':
4679				if (!empty($this->connection) && !empty($this->connection->datetime)) {
4680					return 'T';
4681				}
4682				return 'D';
4683
4684			default:
4685				if ($t == 'LONG' && $this->dataProvider == 'oci8') {
4686					return 'B';
4687				}
4688				return $tmap;
4689		}
4690	}
4691
4692	/**
4693	 * Convert case of field names associative array, if needed
4694	 * @return void
4695	 */
4696	protected function _updatefields()
4697	{
4698		if( empty($this->fields)) {
4699			return;
4700		}
4701
4702		// Determine case conversion function
4703		$fn_change_case = $this->AssocCaseConvertFunction();
4704		if(!$fn_change_case) {
4705			// No conversion needed
4706			return;
4707		}
4708
4709		$arr = array();
4710
4711		// Change the case
4712		foreach($this->fields as $k => $v) {
4713			if (!is_integer($k)) {
4714				$k = $fn_change_case($k);
4715			}
4716			$arr[$k] = $v;
4717		}
4718		$this->fields = $arr;
4719	}
4720
4721	function _close() {}
4722
4723	/**
4724	 * set/returns the current recordset page when paginating
4725	 */
4726	function AbsolutePage($page=-1) {
4727		if ($page != -1) {
4728			$this->_currentPage = $page;
4729		}
4730		return $this->_currentPage;
4731	}
4732
4733	/**
4734	 * set/returns the status of the atFirstPage flag when paginating
4735	 */
4736	function AtFirstPage($status=false) {
4737		if ($status != false) {
4738			$this->_atFirstPage = $status;
4739		}
4740		return $this->_atFirstPage;
4741	}
4742
4743	function LastPageNo($page = false) {
4744		if ($page != false) {
4745			$this->_lastPageNo = $page;
4746		}
4747		return $this->_lastPageNo;
4748	}
4749
4750	/**
4751	 * set/returns the status of the atLastPage flag when paginating
4752	 */
4753	function AtLastPage($status=false) {
4754		if ($status != false) {
4755			$this->_atLastPage = $status;
4756		}
4757		return $this->_atLastPage;
4758	}
4759
4760} // end class ADORecordSet
4761
4762	//==============================================================================================
4763	// CLASS ADORecordSet_array
4764	//==============================================================================================
4765
4766	/**
4767	 * This class encapsulates the concept of a recordset created in memory
4768	 * as an array. This is useful for the creation of cached recordsets.
4769	 *
4770	 * Note that the constructor is different from the standard ADORecordSet
4771	 */
4772	class ADORecordSet_array extends ADORecordSet
4773	{
4774		var $databaseType = 'array';
4775
4776		var $_array;	// holds the 2-dimensional data array
4777		var $_types;	// the array of types of each column (C B I L M)
4778		var $_colnames;	// names of each column in array
4779		var $_skiprow1;	// skip 1st row because it holds column names
4780		var $_fieldobjects; // holds array of field objects
4781		var $canSeek = true;
4782		var $affectedrows = false;
4783		var $insertid = false;
4784		var $sql = '';
4785		var $compat = false;
4786
4787		/**
4788		 * Constructor
4789		 */
4790		function __construct($fakeid=1) {
4791			global $ADODB_FETCH_MODE,$ADODB_COMPAT_FETCH;
4792
4793			// fetch() on EOF does not delete $this->fields
4794			$this->compat = !empty($ADODB_COMPAT_FETCH);
4795			parent::__construct($fakeid); // fake queryID
4796			$this->fetchMode = $ADODB_FETCH_MODE;
4797		}
4798
4799		function _transpose($addfieldnames=true) {
4800			global $ADODB_INCLUDED_LIB;
4801
4802			if (empty($ADODB_INCLUDED_LIB)) {
4803				include_once(ADODB_DIR.'/adodb-lib.inc.php');
4804			}
4805			$hdr = true;
4806
4807			$fobjs = $addfieldnames ? $this->_fieldobjects : false;
4808			adodb_transpose($this->_array, $newarr, $hdr, $fobjs);
4809			//adodb_pr($newarr);
4810
4811			$this->_skiprow1 = false;
4812			$this->_array = $newarr;
4813			$this->_colnames = $hdr;
4814
4815			adodb_probetypes($newarr,$this->_types);
4816
4817			$this->_fieldobjects = array();
4818
4819			foreach($hdr as $k => $name) {
4820				$f = new ADOFieldObject();
4821				$f->name = $name;
4822				$f->type = $this->_types[$k];
4823				$f->max_length = -1;
4824				$this->_fieldobjects[] = $f;
4825			}
4826			$this->fields = reset($this->_array);
4827
4828			$this->_initrs();
4829
4830		}
4831
4832		/**
4833		 * Setup the array.
4834		 *
4835		 * @param array		is a 2-dimensional array holding the data.
4836		 *			The first row should hold the column names
4837		 *			unless parameter $colnames is used.
4838		 * @param typearr	holds an array of types. These are the same types
4839		 *			used in MetaTypes (C,B,L,I,N).
4840		 * @param string[]|false [$colnames]	array of column names. If set, then the first row of
4841		 *			$array should not hold the column names.
4842		 */
4843		function InitArray($array,$typearr,$colnames=false) {
4844			$this->_array = $array;
4845			$this->_types = $typearr;
4846			if ($colnames) {
4847				$this->_skiprow1 = false;
4848				$this->_colnames = $colnames;
4849			} else {
4850				$this->_skiprow1 = true;
4851				$this->_colnames = $array[0];
4852			}
4853			$this->Init();
4854		}
4855		/**
4856		 * Setup the Array and datatype file objects
4857		 *
4858		 * @param array $array    2-dimensional array holding the data
4859		 *			The first row should hold the column names
4860		 *			unless parameter $colnames is used.
4861		 * @param array $fieldarr Array of ADOFieldObject's.
4862		 */
4863		function InitArrayFields(&$array,&$fieldarr) {
4864			$this->_array = $array;
4865			$this->_skiprow1= false;
4866			if ($fieldarr) {
4867				$this->_fieldobjects = $fieldarr;
4868			}
4869			$this->Init();
4870		}
4871
4872		/**
4873		 * @param int [$nRows]
4874		 * @return array
4875		 */
4876		function GetArray($nRows=-1) {
4877			if ($nRows == -1 && $this->_currentRow <= 0 && !$this->_skiprow1) {
4878				return $this->_array;
4879			} else {
4880				return ADORecordSet::GetArray($nRows);
4881			}
4882		}
4883
4884		function _initrs() {
4885			$this->_numOfRows =  sizeof($this->_array);
4886			if ($this->_skiprow1) {
4887				$this->_numOfRows -= 1;
4888			}
4889
4890			$this->_numOfFields = (isset($this->_fieldobjects))
4891				? sizeof($this->_fieldobjects)
4892				: sizeof($this->_types);
4893		}
4894
4895		/**
4896		 * Use associative array to get fields array
4897		 *
4898		 * @param string $colname
4899		 * @return mixed
4900		 */
4901		function Fields($colname) {
4902			$mode = isset($this->adodbFetchMode) ? $this->adodbFetchMode : $this->fetchMode;
4903
4904			if ($mode & ADODB_FETCH_ASSOC) {
4905				if (!isset($this->fields[$colname]) && !is_null($this->fields[$colname])) {
4906					$colname = strtolower($colname);
4907				}
4908				return $this->fields[$colname];
4909			}
4910			if (!$this->bind) {
4911				$this->bind = array();
4912				for ($i=0; $i < $this->_numOfFields; $i++) {
4913					$o = $this->FetchField($i);
4914					$this->bind[strtoupper($o->name)] = $i;
4915				}
4916			}
4917			return $this->fields[$this->bind[strtoupper($colname)]];
4918		}
4919
4920		/**
4921		 * @param int [$fieldOffset]
4922		 *
4923		 * @return \ADOFieldObject
4924		 */
4925		function FetchField($fieldOffset = -1) {
4926			if (isset($this->_fieldobjects)) {
4927				return $this->_fieldobjects[$fieldOffset];
4928			}
4929			$o =  new ADOFieldObject();
4930			$o->name = $this->_colnames[$fieldOffset];
4931			$o->type =  $this->_types[$fieldOffset];
4932			$o->max_length = -1; // length not known
4933
4934			return $o;
4935		}
4936
4937		/**
4938		 * @param int $row
4939		 * @return bool
4940		 */
4941		function _seek($row) {
4942			if (sizeof($this->_array) && 0 <= $row && $row < $this->_numOfRows) {
4943				$this->_currentRow = $row;
4944				if ($this->_skiprow1) {
4945					$row += 1;
4946				}
4947				$this->fields = $this->_array[$row];
4948				return true;
4949			}
4950			return false;
4951		}
4952
4953		/**
4954		 * @return bool
4955		 */
4956		function MoveNext() {
4957			if (!$this->EOF) {
4958				$this->_currentRow++;
4959
4960				$pos = $this->_currentRow;
4961
4962				if ($this->_numOfRows <= $pos) {
4963					if (!$this->compat) {
4964						$this->fields = false;
4965					}
4966				} else {
4967					if ($this->_skiprow1) {
4968						$pos += 1;
4969					}
4970					$this->fields = $this->_array[$pos];
4971					return true;
4972				}
4973				$this->EOF = true;
4974			}
4975
4976			return false;
4977		}
4978
4979		/**
4980		 * @return bool
4981		 */
4982		function _fetch() {
4983			$pos = $this->_currentRow;
4984
4985			if ($this->_numOfRows <= $pos) {
4986				if (!$this->compat) {
4987					$this->fields = false;
4988				}
4989				return false;
4990			}
4991			if ($this->_skiprow1) {
4992				$pos += 1;
4993			}
4994			$this->fields = $this->_array[$pos];
4995			return true;
4996		}
4997
4998		function _close() {
4999			return true;
5000		}
5001
5002	} // ADORecordSet_array
5003
5004	//==============================================================================================
5005	// HELPER FUNCTIONS
5006	//==============================================================================================
5007
5008	/**
5009	 * Synonym for ADOLoadCode. Private function. Do not use.
5010	 *
5011	 * @deprecated
5012	 */
5013	function ADOLoadDB($dbType) {
5014		return ADOLoadCode($dbType);
5015	}
5016
5017	/**
5018	 * Load the code for a specific database driver. Private function. Do not use.
5019	 */
5020	function ADOLoadCode($dbType) {
5021		global $ADODB_LASTDB;
5022
5023		if (!$dbType) {
5024			return false;
5025		}
5026		$db = strtolower($dbType);
5027		switch ($db) {
5028			case 'ado':
5029				$db = 'ado5';
5030				$class = 'ado';
5031				break;
5032
5033			case 'ifx':
5034			case 'maxsql':
5035				$class = $db = 'mysqlt';
5036				break;
5037
5038			case 'pgsql':
5039			case 'postgres':
5040				$class = $db = 'postgres9';
5041				break;
5042
5043			case 'mysql':
5044				// mysql driver deprecated since 5.5, removed in 7.0
5045				// automatically switch to mysqli
5046				if(version_compare(PHP_VERSION, '7.0.0', '>=')) {
5047					$db = 'mysqli';
5048				}
5049				$class = $db;
5050				break;
5051
5052			default:
5053				if (substr($db, 0, 4) === 'pdo_') {
5054					ADOConnection::outp("Invalid database type: $db");
5055					return false;
5056				}
5057
5058				$class = $db;
5059				break;
5060		}
5061
5062		$file = "drivers/adodb-$db.inc.php";
5063		@include_once(ADODB_DIR . '/' . $file);
5064		$ADODB_LASTDB = $class;
5065		if (class_exists("ADODB_" . $class)) {
5066			return $class;
5067		}
5068
5069		//ADOConnection::outp(adodb_pr(get_declared_classes(),true));
5070		if (!file_exists($file)) {
5071			ADOConnection::outp("Missing file: $file");
5072		} else {
5073			ADOConnection::outp("Syntax error in file: $file");
5074		}
5075		return false;
5076	}
5077
5078	/**
5079	 * Synonym for ADONewConnection for people like me who cannot remember the correct name
5080	 *
5081	 * @param string [$db]
5082	 *
5083	 * @return ADOConnection|false
5084	 */
5085	function NewADOConnection($db='') {
5086		return ADONewConnection($db);
5087	}
5088
5089	/**
5090	 * Instantiate a new Connection class for a specific database driver.
5091	 *
5092	 * @param string $db Database Connection object to create. If undefined,
5093	 *	use the last database driver that was loaded by ADOLoadCode().
5094	 *
5095	 * @return ADOConnection|false The freshly created instance of the Connection class
5096	 *                             or false in case of error.
5097	 */
5098	function ADONewConnection($db='') {
5099		global $ADODB_NEWCONNECTION, $ADODB_LASTDB;
5100
5101		if (!defined('ADODB_ASSOC_CASE')) {
5102			define('ADODB_ASSOC_CASE', ADODB_ASSOC_CASE_NATIVE);
5103		}
5104
5105		/*
5106		* Are there special characters in the dsn password
5107		* that disrupt parse_url
5108		*/
5109		$needsSpecialCharacterHandling = false;
5110
5111		$errorfn = (defined('ADODB_ERROR_HANDLER')) ? ADODB_ERROR_HANDLER : false;
5112		if (($at = strpos($db,'://')) !== FALSE) {
5113			$origdsn = $db;
5114			$fakedsn = 'fake'.substr($origdsn,$at);
5115			if (($at2 = strpos($origdsn,'@/')) !== FALSE) {
5116				// special handling of oracle, which might not have host
5117				$fakedsn = str_replace('@/','@adodb-fakehost/',$fakedsn);
5118			}
5119
5120			if ((strpos($origdsn, 'sqlite')) !== FALSE && stripos($origdsn, '%2F') === FALSE) {
5121				// special handling for SQLite, it only might have the path to the database file.
5122				// If you try to connect to a SQLite database using a dsn
5123				// like 'sqlite:///path/to/database', the 'parse_url' php function
5124				// will throw you an exception with a message such as "unable to parse url"
5125				list($scheme, $path) = explode('://', $origdsn);
5126				$dsna['scheme'] = $scheme;
5127				if ($qmark = strpos($path,'?')) {
5128					$dsn['query'] = substr($path,$qmark+1);
5129					$path = substr($path,0,$qmark);
5130				}
5131				$dsna['path'] = '/' . urlencode($path);
5132			} else {
5133				/*
5134				* Stop # character breaking parse_url
5135				*/
5136				$cFakedsn = str_replace('#','\035',$fakedsn);
5137				if (strcmp($fakedsn,$cFakedsn) != 0)
5138				{
5139					/*
5140					* There is a # in the string
5141					*/
5142					$needsSpecialCharacterHandling = true;
5143
5144					/*
5145					* This allows us to successfully parse the url
5146					*/
5147					$fakedsn = $cFakedsn;
5148
5149				}
5150
5151				$dsna = parse_url($fakedsn);
5152			}
5153
5154			if (!$dsna) {
5155				return false;
5156			}
5157			$dsna['scheme'] = substr($origdsn,0,$at);
5158			if ($at2 !== FALSE) {
5159				$dsna['host'] = '';
5160			}
5161
5162			if (strncmp($origdsn,'pdo',3) == 0) {
5163				$sch = explode('_',$dsna['scheme']);
5164				if (sizeof($sch)>1) {
5165					$dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : '';
5166					if ($sch[1] == 'sqlite') {
5167						$dsna['host'] = rawurlencode($sch[1].':'.rawurldecode($dsna['host']));
5168					} else {
5169						$dsna['host'] = rawurlencode($sch[1].':host='.rawurldecode($dsna['host']));
5170					}
5171					$dsna['scheme'] = 'pdo';
5172				}
5173			}
5174
5175			$db = @$dsna['scheme'];
5176			if (!$db) {
5177				return false;
5178			}
5179
5180			$dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : '';
5181			$dsna['user'] = isset($dsna['user']) ? rawurldecode($dsna['user']) : '';
5182			$dsna['pass'] = isset($dsna['pass']) ? rawurldecode($dsna['pass']) : '';
5183			$dsna['path'] = isset($dsna['path']) ? rawurldecode(substr($dsna['path'],1)) : ''; # strip off initial /
5184
5185			if ($needsSpecialCharacterHandling)
5186			{
5187				/*
5188				* Revert back to the original string
5189				*/
5190				$dsna = str_replace('\035','#',$dsna);
5191			}
5192
5193			if (isset($dsna['query'])) {
5194				$opt1 = explode('&',$dsna['query']);
5195				foreach($opt1 as $k => $v) {
5196					$arr = explode('=',$v);
5197					$opt[$arr[0]] = isset($arr[1]) ? rawurldecode($arr[1]) : 1;
5198				}
5199			} else {
5200				$opt = array();
5201			}
5202
5203		}
5204	/*
5205	 *  phptype: Database backend used in PHP (mysql, odbc etc.)
5206	 *  dbsyntax: Database used with regards to SQL syntax etc.
5207	 *  protocol: Communication protocol to use (tcp, unix etc.)
5208	 *  hostspec: Host specification (hostname[:port])
5209	 *  database: Database to use on the DBMS server
5210	 *  username: User name for login
5211	 *  password: Password for login
5212	 */
5213		if (!empty($ADODB_NEWCONNECTION)) {
5214			$obj = $ADODB_NEWCONNECTION($db);
5215
5216		}
5217
5218		if(empty($obj)) {
5219
5220			if (!isset($ADODB_LASTDB)) {
5221				$ADODB_LASTDB = '';
5222			}
5223			if (empty($db)) {
5224				$db = $ADODB_LASTDB;
5225			}
5226			if ($db != $ADODB_LASTDB) {
5227				$db = ADOLoadCode($db);
5228			}
5229
5230			if (!$db) {
5231				if (isset($origdsn)) {
5232					$db = $origdsn;
5233				}
5234				if ($errorfn) {
5235					// raise an error
5236					$ignore = false;
5237					$errorfn('ADONewConnection', 'ADONewConnection', -998,
5238							"could not load the database driver for '$db'",
5239							$db,false,$ignore);
5240				} else {
5241					ADOConnection::outp( "<p>ADONewConnection: Unable to load database driver '$db'</p>",false);
5242				}
5243				return false;
5244			}
5245
5246			$cls = 'ADODB_'.$db;
5247			if (!class_exists($cls)) {
5248				adodb_backtrace();
5249				return false;
5250			}
5251
5252			$obj = new $cls();
5253		}
5254
5255		# constructor should not fail
5256		if ($obj) {
5257			if ($errorfn) {
5258				$obj->raiseErrorFn = $errorfn;
5259			}
5260			if (isset($dsna)) {
5261				if (isset($dsna['port'])) {
5262					$obj->port = $dsna['port'];
5263				}
5264				foreach($opt as $k => $v) {
5265					switch(strtolower($k)) {
5266					case 'new':
5267										$nconnect = true; $persist = true; break;
5268					case 'persist':
5269					case 'persistent':	$persist = $v; break;
5270					case 'debug':		$obj->debug = (integer) $v; break;
5271					#ibase
5272					case 'role':		$obj->role = $v; break;
5273					case 'dialect':	$obj->dialect = (integer) $v; break;
5274					case 'charset':		$obj->charset = $v; $obj->charSet=$v; break;
5275					case 'buffers':		$obj->buffers = $v; break;
5276					case 'fetchmode':   $obj->SetFetchMode($v); break;
5277					#ado
5278					case 'charpage':	$obj->charPage = $v; break;
5279					#mysql, mysqli
5280					case 'clientflags': $obj->clientFlags = $v; break;
5281					#mysql, mysqli, postgres
5282					case 'port': $obj->port = $v; break;
5283					#mysqli
5284					case 'socket': $obj->socket = $v; break;
5285					#oci8
5286					case 'nls_date_format': $obj->NLS_DATE_FORMAT = $v; break;
5287					case 'cachesecs': $obj->cacheSecs = $v; break;
5288					case 'memcache':
5289						$varr = explode(':',$v);
5290						$vlen = sizeof($varr);
5291						if ($vlen == 0) {
5292							break;
5293						}
5294						$obj->memCache = true;
5295						$obj->memCacheHost = explode(',',$varr[0]);
5296						if ($vlen == 1) {
5297							break;
5298						}
5299						$obj->memCachePort = $varr[1];
5300						if ($vlen == 2) {
5301							break;
5302						}
5303						$obj->memCacheCompress = $varr[2] ?  true : false;
5304						break;
5305					}
5306				}
5307				if (empty($persist)) {
5308					$ok = $obj->Connect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
5309				} else if (empty($nconnect)) {
5310					$ok = $obj->PConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
5311				} else {
5312					$ok = $obj->NConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
5313				}
5314
5315				if (!$ok) {
5316					return false;
5317				}
5318			}
5319		}
5320		return $obj;
5321	}
5322
5323
5324
5325	// $perf == true means called by NewPerfMonitor(), otherwise for data dictionary
5326	function _adodb_getdriver($provider,$drivername,$perf=false) {
5327		switch ($provider) {
5328			case 'odbtp':
5329				if (strncmp('odbtp_',$drivername,6)==0) {
5330					return substr($drivername,6);
5331				}
5332			case 'odbc' :
5333				if (strncmp('odbc_',$drivername,5)==0) {
5334					return substr($drivername,5);
5335				}
5336			case 'ado'  :
5337				if (strncmp('ado_',$drivername,4)==0) {
5338					return substr($drivername,4);
5339				}
5340			case 'native':
5341				break;
5342			default:
5343				return $provider;
5344		}
5345
5346		switch($drivername) {
5347			case 'mysqlt':
5348			case 'mysqli':
5349				$drivername='mysql';
5350				break;
5351			case 'postgres7':
5352			case 'postgres8':
5353				$drivername = 'postgres';
5354				break;
5355			case 'firebird15':
5356				$drivername = 'firebird';
5357				break;
5358			case 'oracle':
5359				$drivername = 'oci8';
5360				break;
5361			case 'access':
5362				if ($perf) {
5363					$drivername = '';
5364				}
5365				break;
5366			case 'db2'   :
5367			case 'sapdb' :
5368				break;
5369			default:
5370				$drivername = 'generic';
5371				break;
5372		}
5373		return $drivername;
5374	}
5375
5376	function NewPerfMonitor(&$conn) {
5377		$drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType,true);
5378		if (!$drivername || $drivername == 'generic') {
5379			return false;
5380		}
5381		include_once(ADODB_DIR.'/adodb-perf.inc.php');
5382		@include_once(ADODB_DIR."/perf/perf-$drivername.inc.php");
5383		$class = "Perf_$drivername";
5384		if (!class_exists($class)) {
5385			return false;
5386		}
5387
5388		return new $class($conn);
5389	}
5390
5391	function NewDataDictionary(&$conn,$drivername=false) {
5392		if (!$drivername) {
5393			$drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType);
5394		}
5395
5396		include_once(ADODB_DIR.'/adodb-lib.inc.php');
5397		include_once(ADODB_DIR.'/adodb-datadict.inc.php');
5398		$path = ADODB_DIR."/datadict/datadict-$drivername.inc.php";
5399
5400		if (!file_exists($path)) {
5401			ADOConnection::outp("Dictionary driver '$path' not available");
5402			return false;
5403		}
5404		include_once($path);
5405		$class = "ADODB2_$drivername";
5406		$dict = new $class();
5407		$dict->dataProvider = $conn->dataProvider;
5408		$dict->connection = $conn;
5409		$dict->upperName = strtoupper($drivername);
5410		$dict->quote = $conn->nameQuote;
5411		if (!empty($conn->_connectionID)) {
5412			$dict->serverInfo = $conn->ServerInfo();
5413		}
5414
5415		return $dict;
5416	}
5417
5418
5419
5420	/*
5421		Perform a print_r, with pre tags for better formatting.
5422	*/
5423	function adodb_pr($var,$as_string=false) {
5424		if ($as_string) {
5425			ob_start();
5426		}
5427
5428		if (isset($_SERVER['HTTP_USER_AGENT'])) {
5429			echo " <pre>\n";print_r($var);echo "</pre>\n";
5430		} else {
5431			print_r($var);
5432		}
5433
5434		if ($as_string) {
5435			$s = ob_get_contents();
5436			ob_end_clean();
5437			return $s;
5438		}
5439	}
5440
5441	/*
5442		Perform a stack-crawl and pretty print it.
5443
5444		@param printOrArr  Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then).
5445		@param levels Number of levels to display
5446	*/
5447	function adodb_backtrace($printOrArr=true,$levels=9999,$ishtml=null) {
5448		global $ADODB_INCLUDED_LIB;
5449		if (empty($ADODB_INCLUDED_LIB)) {
5450			include_once(ADODB_DIR.'/adodb-lib.inc.php');
5451		}
5452		return _adodb_backtrace($printOrArr,$levels,0,$ishtml);
5453	}
5454
5455}
5456