New Upstream Release - libphp-adodb

Ready changes

Summary

Merged new upstream version: 5.22.6 (was: 5.22.4).

Resulting package

Built on 2023-07-10T00:56 (took 7m47s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases libphp-adodb

Lintian Result

Diff

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..5dad2ea
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,22 @@
+# EditorConfig file for ADOdb
+# https://editorconfig.org/
+
+root = true
+
+# Default file settings
+[*]
+indent_style = tab
+indent_size = 4
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+charset = utf-8
+
+# Python build scripts
+[*.py]
+indent_style = space
+
+# Markdown files
+[*.md]
+indent_style = space
+trim_trailing_whitespace = false
diff --git a/README.md b/README.md
index e432b2f..96ac67b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
 ADOdb Library for PHP
 ======================
 
-[![Join chat on Gitter](https://img.shields.io/gitter/room/form-data/form-data.svg)](https://gitter.im/adodb/adodb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[![Download ADOdb](https://img.shields.io/sourceforge/dm/adodb.svg)](https://sourceforge.net/projects/adodb/files/latest/download)
+[![Join chat on Gitter](https://img.shields.io/gitter/room/adodb/adodb?logo=gitter)](https://gitter.im/adodb/adodb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![SourceForge Downloads Shield](https://img.shields.io/sourceforge/dm/adodb?label=SourceForge&logo=sourceforge&color=informational)](https://sourceforge.net/projects/adodb/files/latest/download)
+[![Packagist Downloads Shield](https://img.shields.io/packagist/dm/ADOdb/ADOdb-php?label=Packagist&logo=packagist&logoColor=white&color=informational)](https://packagist.org/packages/adodb/adodb-php)
 
 (c) 2000-2013 John Lim (jlim@natsoft.com)  
 (c) 2014      Damien Regad, Mark Newnham and the
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..00670f2
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,45 @@
+# ADOdb Security Policy
+
+## Supported Versions
+
+The following releases of the library are currently being supported with
+security updates. Please refer to the [project's home page](https://adodb.org)
+for actual version numbers.
+
+- Stable
+- Legacy
+- Development (Git *master* branch)
+
+Older releases are no longer supported. 
+
+
+## Reporting a Vulnerability
+
+If you discover a vulnerability in ADOdb, please contact 
+the [project's maintainer](https://github.com/dregad)
+
+- by e-mail (look for it in the Git history)
+- via private chat on [Gitter](https://gitter.im/dregad)
+
+Kindly provide the following information in your report:
+
+- Affected ADOdb version(s) or Git revision
+- A clear and detailed description of the issue, including if possible a code 
+  snippet to demonstrate or reproduce the vulnerability
+- A patch for the issue if you have one, preferably in *Git diff* format
+
+### CVE handling
+
+To ensure a comprehensive and detailed declaration of the issue, we generally 
+prefer requesting CVE IDs ourselves, which usually happens after our analysis
+confirms the vulnerability.
+
+In case you have already obtained a CVE ID, do not forget to reference it in 
+your report.
+
+### Credits
+
+Let us know if and how you wish to be credited for the finding. 
+
+Your name, e-mail, company, etc. will be included as specified in the CVE 
+report, as well as in the Git commit message patching the issue.
diff --git a/adodb-active-record.inc.php b/adodb-active-record.inc.php
index 8f3b005..5fbe7b9 100644
--- a/adodb-active-record.inc.php
+++ b/adodb-active-record.inc.php
@@ -281,7 +281,7 @@ class ADODB_Active_Record {
 
 	static function TableBelongsTo($table, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
 	{
-		$ar = new ADOdb_Active_Record($table);
+		$ar = new ADODB_Active_Record($table);
 		$ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
 	}
 
@@ -290,7 +290,7 @@ class ADODB_Active_Record {
 		if (!is_array($tablePKey)) {
 			$tablePKey = array($tablePKey);
 		}
-		$ar = new ADOdb_Active_Record($table, $tablePKey);
+		$ar = new ADODB_Active_Record($table, $tablePKey);
 		$ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
 	}
 
@@ -501,7 +501,8 @@ class ADODB_Active_Record {
 			}
 			break;
 		default:
-			foreach($cols as $name => $fldobj) {
+			foreach($cols as $fldobj) {
+				$name = $fldobj->name;
 
 				if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
 					$this->$name = $fldobj->default_value;
diff --git a/adodb-csvlib.inc.php b/adodb-csvlib.inc.php
index c81254b..87efd94 100644
--- a/adodb-csvlib.inc.php
+++ b/adodb-csvlib.inc.php
@@ -29,11 +29,13 @@ global $ADODB_INCLUDED_CSV;
 $ADODB_INCLUDED_CSV = 1;
 
 	/**
- 	 * convert a recordset into special format
+ 	 * Convert a recordset into special format
 	 *
-	 * @param rs	the recordset
+	 * @param ADORecordSet  $rs the recordset
+	 * @param ADOConnection $conn
+	 * @param string        $sql
 	 *
-	 * @return	the CSV formatted data
+	 * @return string the CSV formatted data
 	 */
 	function _rs2serialize(&$rs,$conn=false,$sql='')
 	{
@@ -74,28 +76,28 @@ $ADODB_INCLUDED_CSV = 1;
 
 		$savefetch = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode;
 		$class = $rs->connection->arrayClass;
-		$rs2 = new $class();
+		/** @var ADORecordSet $rs2 */
+		$rs2 = new $class(ADORecordSet::DUMMY_QUERY_ID);
 		$rs2->timeCreated = $rs->timeCreated; # memcache fix
 		$rs2->sql = $rs->sql;
-		$rs2->oldProvider = $rs->dataProvider;
 		$rs2->InitArrayFields($rows,$flds);
 		$rs2->fetchMode = $savefetch;
 		return $line.serialize($rs2);
 	}
 
-
-/**
-* Open CSV file and convert it into Data.
-*
-* @param url  		file/ftp/http url
-* @param err		returns the error message
-* @param timeout	dispose if recordset has been alive for $timeout secs
-*
-* @return		recordset, or false if error occurred. If no
-*			error occurred in sql INSERT/UPDATE/DELETE,
-*			empty recordset is returned
-*/
-	function csv2rs($url,&$err,$timeout=0, $rsclass='ADORecordSet_array')
+	/**
+	 * Open CSV file and convert it into Data.
+	 *
+	 * @param string $url     file/ftp/http url
+	 * @param string &$err    returns the error message
+	 * @param int $timeout    dispose if recordset has been alive for $timeout secs
+	 * @param string $rsclass RecordSet class to return
+	 *
+	 * @return ADORecordSet|false recordset, or false if error occurred.
+	 *                            If no error occurred in sql INSERT/UPDATE/DELETE,
+	 *                            empty recordset is returned.
+	 */
+	function csv2rs($url, &$err, $timeout=0, $rsclass='ADORecordSet_array')
 	{
 		$false = false;
 		$err = false;
diff --git a/adodb-datadict.inc.php b/adodb-datadict.inc.php
index dbed95d..5156b1f 100644
--- a/adodb-datadict.inc.php
+++ b/adodb-datadict.inc.php
@@ -202,6 +202,12 @@ class ADODB_DataDict {
 	*/
 	public $blobAllowsDefaultValue;
 
+
+	/**
+	 * @var string String to use to quote identifiers and names
+	 */
+	public $quote;
+
 	function getCommentSQL($table,$col)
 	{
 		return false;
@@ -353,7 +359,7 @@ class ADODB_DataDict {
 	function nameQuote($name = NULL,$allowBrackets=false)
 	{
 		if (!is_string($name)) {
-			return FALSE;
+			return false;
 		}
 
 		$name = trim($name);
@@ -427,6 +433,15 @@ class ADODB_DataDict {
 
 	function actualType($meta)
 	{
+		$meta = strtoupper($meta);
+
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+
 		return $meta;
 	}
 
@@ -498,7 +513,7 @@ class ADODB_DataDict {
 	 * @param string $tabname table-name
 	 * @param string $flds column-name and type for the changed column
 	 * @param string $tableflds='' complete definition of the new table, eg. for postgres, default ''
-	 * @param array/string $tableoptions='' options for the new table see createTableSQL, default ''
+	 * @param array|string $tableoptions='' options for the new table see createTableSQL, default ''
 	 * @return array with SQL strings
 	 */
 	function alterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
@@ -553,7 +568,7 @@ class ADODB_DataDict {
 	 * @param string $tabname table-name
 	 * @param string $flds column-name and type for the changed column
 	 * @param string $tableflds='' complete definition of the new table, eg. for postgres, default ''
-	 * @param array/string $tableoptions='' options for the new table see createTableSQL, default ''
+	 * @param array|string $tableoptions='' options for the new table see createTableSQL, default ''
 	 * @return array with SQL strings
 	 */
 	function dropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
@@ -695,16 +710,25 @@ class ADODB_DataDict {
 				case '0':
 				case 'NAME': 	$fname = $v; break;
 				case '1':
-				case 'TYPE': 	$ty = $v; $ftype = $this->actualType(strtoupper($v)); break;
+				case 'TYPE':
+
+					$ty = $v;
+					$ftype = $this->actualType(strtoupper($v));
+					break;
 
 				case 'SIZE':
-								$dotat = strpos($v,'.'); if ($dotat === false) $dotat = strpos($v,',');
-								if ($dotat === false) $fsize = $v;
-								else {
-									$fsize = substr($v,0,$dotat);
-									$fprec = substr($v,$dotat+1);
-								}
-								break;
+					$dotat = strpos($v,'.');
+					if ($dotat === false)
+						$dotat = strpos($v,',');
+					if ($dotat === false)
+						$fsize = $v;
+					else {
+
+						$fsize = substr($v,0,$dotat);
+						$fprec = substr($v,$dotat+1);
+
+					}
+					break;
 				case 'UNSIGNED': $funsigned = true; break;
 				case 'AUTOINCREMENT':
 				case 'AUTO':	$fautoinc = true; $fnotnull = true; break;
@@ -989,11 +1013,17 @@ class ADODB_DataDict {
 	}
 
 	/**
-	"Florian Buzin [ easywe ]" <florian.buzin#easywe.de>
-
-	This function changes/adds new fields to your table. You don't
-	have to know if the col is new or not. It will check on its own.
-	*/
+	 * This function changes/adds new fields to your table.
+	 *
+	 * You don't have to know if the col is new or not. It will check on its own.
+	 *
+	 * @param string   $tablename
+	 * @param string   $flds
+	 * @param string[] $tableoptions
+	 * @param bool     $dropOldFlds
+	 *
+	 * @return string[] Array of SQL Commands
+	 */
 	function changeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFlds=false)
 	{
 	global $ADODB_FETCH_MODE;
@@ -1052,37 +1082,14 @@ class ADODB_DataDict {
 			$flds = $holdflds;
 		}
 
-
-		// already exists, alter table instead
-		list($lines,$pkey,$idxs) = $this->_genFields($flds);
-		// genfields can return FALSE at times
-		if ($lines == null) $lines = array();
-		$alter = 'ALTER TABLE ' . $this->tableName($tablename);
-		$sql = array();
-
-		foreach ( $lines as $id => $v ) {
-			if ( isset($cols[$id]) && is_object($cols[$id]) ) {
-
-				$flds = lens_ParseArgs($v,',');
-
-				//  We are trying to change the size of the field, if not allowed, simply ignore the request.
-				// $flds[1] holds the type, $flds[2] holds the size -postnuke addition
-				if ($flds && in_array(strtoupper(substr($flds[0][1],0,4)),$this->invalidResizeTypes4)
-				 && (isset($flds[0][2]) && is_numeric($flds[0][2]))) {
-					if ($this->debug) ADOConnection::outp(sprintf("<h3>%s cannot be changed to %s currently</h3>", $flds[0][0], $flds[0][1]));
-					#echo "<h3>$this->alterCol cannot be changed to $flds currently</h3>";
-					continue;
-	 			}
-				$sql[] = $alter . $this->alterCol . ' ' . $v;
-			} else {
-				$sql[] = $alter . $this->addCol . ' ' . $v;
-			}
-		}
+		$sql = $this->alterColumnSql($tablename, $flds);
 
 		if ($dropOldFlds) {
-			foreach ( $cols as $id => $v )
-			    if ( !isset($lines[$id]) )
-					$sql[] = $alter . $this->dropCol . ' ' . $v->name;
+			foreach ($cols as $id => $v) {
+				if (!isset($lines[$id])) {
+					$sql[] = $this->dropColumnSQL($tablename, $flds);
+				}
+			}
 		}
 		return $sql;
 	}
diff --git a/adodb-exceptions.inc.php b/adodb-exceptions.inc.php
index 9f1176f..e4fae81 100644
--- a/adodb-exceptions.inc.php
+++ b/adodb-exceptions.inc.php
@@ -30,6 +30,9 @@ var $params = '';
 var $host = '';
 var $database = '';
 
+	/** @var string A message text. */
+	var $msg = '';
+
 	function __construct($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection)
 	{
 		switch($fn) {
@@ -45,6 +48,9 @@ var $database = '';
 			$s = "$dbms error: [$errno: $errmsg] in $fn($p1, '$user', '****', $p2)";
 			break;
 		default:
+			//Prevent PHP warning if $p1 or $p2 are arrays.
+			$p1 = ( is_array($p1) ) ? 'Array' : $p1;
+			$p2 = ( is_array($p2) ) ? 'Array' : $p2;
 			$s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)";
 			break;
 		}
@@ -75,10 +81,18 @@ var $database = '';
 
 function adodb_throw($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection)
 {
-global $ADODB_EXCEPTION;
+	global $ADODB_EXCEPTION;
+
+	// Do not throw if errors are suppressed by @ operator
+	// error_reporting() value for suppressed errors changed in PHP 8.0.0
+	$suppressed = version_compare(PHP_VERSION, '8.0.0', '<')
+		? 0
+		: E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE;
+	if (error_reporting() == $suppressed) {
+		return;
+	}
+
+	$errfn = is_string($ADODB_EXCEPTION) ? $ADODB_EXCEPTION : 'ADODB_EXCEPTION';
 
-	if (error_reporting() == 0) return; // obey @ protocol
-	if (is_string($ADODB_EXCEPTION)) $errfn = $ADODB_EXCEPTION;
-	else $errfn = 'ADODB_EXCEPTION';
 	throw new $errfn($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection);
 }
diff --git a/adodb-lib.inc.php b/adodb-lib.inc.php
index ced5e12..90b8b9d 100644
--- a/adodb-lib.inc.php
+++ b/adodb-lib.inc.php
@@ -27,44 +27,30 @@ if (!defined('ADODB_DIR')) die();
 global $ADODB_INCLUDED_LIB;
 $ADODB_INCLUDED_LIB = 1;
 
+/**
+ * Strip the ORDER BY clause from the outer SELECT.
+ *
+ * @param string $sql
+ *
+ * @return string
+ */
 function adodb_strip_order_by($sql)
 {
-	$rez = preg_match_all('/(\sORDER\s+BY\s(?:[^)](?!LIMIT))*)/is', $sql, $arr);
-	if ($arr) 
-	{
-		$tmp = array_pop($arr);
-		$arr = [1=>array_pop($tmp)];
-	}
-	if ($arr)
-		if (strpos($arr[1], '(') !== false) {
-			$at = strpos($sql, $arr[1]);
-			$cntin = 0;
-			for ($i=$at, $max=strlen($sql); $i < $max; $i++) {
-				$ch = $sql[$i];
-				if ($ch == '(') {
-					$cntin += 1;
-				} elseif($ch == ')') {
-					$cntin -= 1;
-					if ($cntin < 0) {
-						break;
-					}
-				}
-			}
-			$sql = substr($sql,0,$at).substr($sql,$i);
-		} else {
-			$sql = str_replace($arr[1], '', $sql);
+	$num = preg_match_all('/(\sORDER\s+BY\s(?:[^)](?!LIMIT))*)/is', $sql, $matches, PREG_OFFSET_CAPTURE);
+	if ($num) {
+		// Get the last match
+		list($last_order_by, $offset) = array_pop($matches[1]);
+
+		// If we find a ')' after the last order by, then it belongs to a
+		// sub-query, not the outer SQL statement and should not be stripped
+		if (strpos($sql, ')', $offset) === false) {
+			$sql = str_replace($last_order_by, '', $sql);
 		}
-
+	}
 	return $sql;
 }
 
-if (false) {
-	$sql = 'select * from (select a from b order by a(b),b(c) desc)';
-	$sql = '(select * from abc order by 1)';
-	die(adodb_strip_order_by($sql));
-}
-
-function adodb_probetypes(&$array,&$types,$probe=8)
+function adodb_probetypes($array,&$types,$probe=8)
 {
 // probe and guess the type
 	$types = array();
@@ -96,7 +82,7 @@ function adodb_probetypes(&$array,&$types,$probe=8)
 			// that it is not an integer
 				if (strlen($v) == 0) $types[$i] = 'C';
 				if (strpos($v,'.') !== false) $types[$i] = 'N';
-				else  $types[$i] = 'I';
+				else $types[$i] = 'I';
 				continue;
 			}
 
@@ -107,7 +93,7 @@ function adodb_probetypes(&$array,&$types,$probe=8)
 
 }
 
-function  adodb_transpose(&$arr, &$newarr, &$hdr, &$fobjs)
+function adodb_transpose(&$arr, &$newarr, &$hdr, $fobjs)
 {
 	$oldX = sizeof(reset($arr));
 	$oldY = sizeof($arr);
@@ -135,7 +121,7 @@ function  adodb_transpose(&$arr, &$newarr, &$hdr, &$fobjs)
 }
 
 
-function _adodb_replace(&$zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc)
+function _adodb_replace($zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc)
 {
 	// Add Quote around table name to support use of spaces / reserved keywords
 	$table=sprintf('%s%s%s', $zthis->nameQuote,$table,$zthis->nameQuote);
@@ -209,7 +195,7 @@ function _adodb_replace(&$zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_
 	return ($rs) ? 2 : 0;
 }
 
-function _adodb_getmenu(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
+function _adodb_getmenu($zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
 			$size=0, $selectAttr='',$compareFields0=true)
 {
 	global $ADODB_FETCH_MODE;
@@ -242,6 +228,7 @@ function _adodb_getmenu(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=f
 			$value = 'value="' . htmlspecialchars($zval2) . '"';
 		}
 
+		/** @noinspection PhpUndefinedVariableInspection */
 		$s .= _adodb_getmenu_option($defstr, $compareFields0 ? $zval : $zval2, $value, $zval);
 
 		$zthis->MoveNext();
@@ -250,7 +237,7 @@ function _adodb_getmenu(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=f
 	return $s ."\n</select>\n";
 }
 
-function _adodb_getmenu_gp(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
+function _adodb_getmenu_gp($zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
 			$size=0, $selectAttr='',$compareFields0=true)
 {
 	global $ADODB_FETCH_MODE;
@@ -304,6 +291,7 @@ function _adodb_getmenu_gp(&$zthis, $name,$defstr='',$blank1stItem=true,$multipl
 			$s .="\n<optgroup label='". htmlspecialchars($group) ."'>";
 		}
 
+		/** @noinspection PhpUndefinedVariableInspection */
 		$s .= _adodb_getmenu_option($defstr, $compareFields0 ? $zval : $zval2, $value, $zval);
 
 		$zthis->MoveNext();
@@ -349,7 +337,7 @@ function _adodb_getmenu_select($name, $defstr = '', $blank1stItem = true,
 
 	$html = '<select name="' . $name . '"' . $attr . ' ' . $selectAttr . '>';
 	if ($blank1stItem) {
-		if (is_string($blank1stItem))  {
+		if (is_string($blank1stItem)) {
 			$barr = explode(':',$blank1stItem);
 			if (sizeof($barr) == 1) {
 				$barr[] = '';
@@ -400,7 +388,7 @@ function _adodb_getmenu_option($defstr, $compare, $value, $display)
 	$cnt = _adodb_getcount($conn, $sql);
 
 */
-function _adodb_getcount(&$zthis, $sql,$inputarr=false,$secs2cache=0)
+function _adodb_getcount($zthis, $sql,$inputarr=false,$secs2cache=0)
 {
 	$qryRecs = 0;
 
@@ -456,7 +444,7 @@ function _adodb_getcount(&$zthis, $sql,$inputarr=false,$secs2cache=0)
 				continue;
 			}
 			// Exit loop if 'FROM' keyword was found
-			if (strtoupper(substr($sql, $pos, 4)) == 'FROM')  {
+			if (strtoupper(substr($sql, $pos, 4)) == 'FROM') {
 				break;
 			}
 		}
@@ -491,7 +479,7 @@ function _adodb_getcount(&$zthis, $sql,$inputarr=false,$secs2cache=0)
 	if (preg_match('/\s*UNION\s*/is', $sql)) {
 		$rewritesql = $sql;
 	} else {
-		$rewritesql = $rewritesql = adodb_strip_order_by($sql);
+		$rewritesql = adodb_strip_order_by($sql);
 	}
 
 	if (preg_match('/\sLIMIT\s+[0-9]+/i',$sql,$limitarr)) {
@@ -520,37 +508,38 @@ function _adodb_getcount(&$zthis, $sql,$inputarr=false,$secs2cache=0)
 	return $qryRecs;
 }
 
-/*
- 	Code originally from "Cornel G" <conyg@fx.ro>
-
-	This code might not work with SQL that has UNION in it
-
-	Also if you are using CachePageExecute(), there is a strong possibility that
-	data will get out of synch. use CachePageExecute() only with tables that
-	rarely change.
-*/
-function _adodb_pageexecute_all_rows(&$zthis, $sql, $nrows, $page,
-						$inputarr=false, $secs2cache=0)
+/**
+ * Execute query with pagination including record count.
+ *
+ * This code might not work with SQL that has UNION in it.
+ * Also if you are using cachePageExecute(), there is a strong possibility that
+ * data will get out of sync. cachePageExecute() should only be used with
+ * tables that rarely change.
+ *
+ * @param ADOConnection $zthis      Connection
+ * @param string        $sql        Query to execute
+ * @param int           $nrows      Number of rows per page
+ * @param int           $page       Page number to retrieve (1-based)
+ * @param array         $inputarr   Array of bind variables
+ * @param int           $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
+ *
+ * @return ADORecordSet|bool
+ *
+ * @author Cornel G <conyg@fx.ro>
+ */
+function _adodb_pageexecute_all_rows($zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
 {
 	$atfirstpage = false;
 	$atlastpage = false;
-	$lastpageno=1;
 
-	// If an invalid nrows is supplied,
-	// we assume a default value of 10 rows per page
+	// If an invalid nrows is supplied, assume a default value of 10 rows per page
 	if (!isset($nrows) || $nrows <= 0) $nrows = 10;
 
-	$qryRecs = false; //count records for no offset
-
 	$qryRecs = _adodb_getcount($zthis,$sql,$inputarr,$secs2cache);
 	$lastpageno = (int) ceil($qryRecs / $nrows);
-	$zthis->_maxRecordCount = $qryRecs;
-
-
 
-	// ***** Here we check whether $page is the last page or
-	// whether we are trying to retrieve
-	// a page number greater than the last page number.
+	// Check whether $page is the last page or if we are trying to retrieve
+	// a page number greater than the last one.
 	if ($page >= $lastpageno) {
 		$page = $lastpageno;
 		$atlastpage = true;
@@ -582,10 +571,27 @@ function _adodb_pageexecute_all_rows(&$zthis, $sql, $nrows, $page,
 	return $rsreturn;
 }
 
-// Iván Oliva version
-function _adodb_pageexecute_no_last_page(&$zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
+/**
+ * Execute query with pagination without last page information.
+ *
+ * This code might not work with SQL that has UNION in it.
+ * Also if you are using cachePageExecute(), there is a strong possibility that
+ * data will get out of sync. cachePageExecute() should only be used with
+ * tables that rarely change.
+ *
+ * @param ADOConnection $zthis      Connection
+ * @param string        $sql        Query to execute
+ * @param int           $nrows      Number of rows per page
+ * @param int           $page       Page number to retrieve (1-based)
+ * @param array         $inputarr   Array of bind variables
+ * @param int           $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
+ *
+ * @return ADORecordSet|bool
+ *
+ * @author Iván Oliva
+ */
+function _adodb_pageexecute_no_last_page($zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
 {
-
 	$atfirstpage = false;
 	$atlastpage = false;
 
@@ -623,7 +629,6 @@ function _adodb_pageexecute_no_last_page(&$zthis, $sql, $nrows, $page, $inputarr
 		// page and return it. Revert to original method and loop through pages
 		// until we find some data...
 		$pagecounter = $page + 1;
-		$pagecounteroffset = ($pagecounter * $nrows) - $nrows;
 
 		$rstest = $rsreturn;
 		if ($rstest) {
@@ -715,7 +720,7 @@ function _adodb_quote_fieldname($zthis, $fieldName)
 	}
 }
 
-function _adodb_getupdatesql(&$zthis, &$rs, $arrFields, $forceUpdate=false, $force=2)
+function _adodb_getupdatesql(&$zthis, $rs, $arrFields, $forceUpdate=false, $force=2)
 {
 	if (!$rs) {
 		printf(ADODB_BAD_RS,'GetUpdateSQL');
@@ -746,11 +751,11 @@ function _adodb_getupdatesql(&$zthis, &$rs, $arrFields, $forceUpdate=false, $for
 
 			if ($hasnumeric) $val = $rs->fields[$i];
 			else if (isset($rs->fields[$upperfname])) $val = $rs->fields[$upperfname];
-			else if (isset($rs->fields[$field->name])) $val =  $rs->fields[$field->name];
-			else if (isset($rs->fields[strtolower($upperfname)])) $val =  $rs->fields[strtolower($upperfname)];
+			else if (isset($rs->fields[$field->name])) $val = $rs->fields[$field->name];
+			else if (isset($rs->fields[strtolower($upperfname)])) $val = $rs->fields[strtolower($upperfname)];
 			else $val = '';
 
-			if ($forceUpdate || strcmp($val, $arrFields[$upperfname])) {
+			if ($forceUpdate || $val !== $arrFields[$upperfname]) {
 				// Set the counter for the number of fields that will be updated.
 				$fieldUpdatedCount++;
 
@@ -840,7 +845,6 @@ function _adodb_getupdatesql(&$zthis, &$rs, $arrFields, $forceUpdate=false, $for
 		$discard = false;
 		// not a good hack, improvements?
 		if ($whereClause) {
-			#var_dump($whereClause);
 			if (preg_match('/\s(ORDER\s.*)/is', $whereClause[1], $discard));
 			else if (preg_match('/\s(LIMIT\s.*)/is', $whereClause[1], $discard));
 			else if (preg_match('/\s(FOR UPDATE.*)/is', $whereClause[1], $discard));
@@ -863,14 +867,14 @@ function _adodb_getupdatesql(&$zthis, &$rs, $arrFields, $forceUpdate=false, $for
 	}
 }
 
-function adodb_key_exists($key, &$arr,$force=2)
+function adodb_key_exists($key, $arr,$force=2)
 {
 	if ($force<=0) {
 		// the following is the old behaviour where null or empty fields are ignored
 		return (!empty($arr[$key])) || (isset($arr[$key]) && strlen($arr[$key])>0);
 	}
 
-	if (isset($arr[$key])) 
+	if (isset($arr[$key]))
 		return true;
 	## null check below
 	return array_key_exists($key,$arr);
@@ -883,7 +887,7 @@ function adodb_key_exists($key, &$arr,$force=2)
  *
  *
  */
-function _adodb_getinsertsql(&$zthis, &$rs, $arrFields, $force=2)
+function _adodb_getinsertsql(&$zthis, $rs, $arrFields, $force=2)
 {
 static $cacheRS = false;
 static $cacheSig = 0;
@@ -892,7 +896,6 @@ static $cacheCols;
 	$tableName = '';
 	$values = '';
 	$fields = '';
-	$recordSet = null;
 	if (is_array($arrFields))
 		$arrFields = array_change_key_case($arrFields,CASE_UPPER);
 	$fieldInsertedCount = 0;
@@ -906,7 +909,7 @@ static $cacheCols;
 		//because we have to call MetaType.
 		//php can't do a $rsclass::MetaType()
 		$rsclass = $zthis->rsPrefix.$zthis->databaseType;
-		$recordSet = new $rsclass(-1,$zthis->fetchMode);
+		$recordSet = new $rsclass(ADORecordSet::DUMMY_QUERY_ID, $zthis->fetchMode);
 		$recordSet->connection = $zthis;
 
 		if (is_string($cacheRS) && $cacheRS == $rs) {
@@ -920,6 +923,7 @@ static $cacheCols;
 		if (isset($rs->insertSig) && is_integer($cacheRS) && $cacheRS == $rs->insertSig) {
 			$columns = $cacheCols;
 		} else {
+			$columns = [];
 			for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++)
 				$columns[] = $rs->FetchField($i);
 			$cacheRS = $cacheSig;
@@ -936,60 +940,58 @@ static $cacheCols;
 	// Loop through all of the fields in the recordset
 	foreach( $columns as $field ) {
 		$upperfname = strtoupper($field->name);
-		if (adodb_key_exists($upperfname,$arrFields,$force)) {
+		if (adodb_key_exists($upperfname, $arrFields, $force)) {
 			$bad = false;
 			$fnameq = _adodb_quote_fieldname($zthis, $field->name);
 			$type = $recordSet->MetaType($field->type);
 
-            /********************************************************/
-            if (is_null($arrFields[$upperfname])
-                || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
-                || $arrFields[$upperfname] === $zthis->null2null
-				)
-               {
-                    switch ($force) {
+			/********************************************************/
+			if (is_null($arrFields[$upperfname])
+				|| (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
+				|| $arrFields[$upperfname] === $zthis->null2null
+			) {
+				switch ($force) {
 
-                        case ADODB_FORCE_IGNORE: // we must always set null if missing
-							$bad = true;
-							break;
+					case ADODB_FORCE_IGNORE: // we must always set null if missing
+						$bad = true;
+						break;
 
-                        case ADODB_FORCE_NULL:
-                            $values  .= "null, ";
-                        break;
+					case ADODB_FORCE_NULL:
+						$values .= "null, ";
+						break;
 
-                        case ADODB_FORCE_EMPTY:
-                            //Set empty
-                            $arrFields[$upperfname] = "";
-							$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
-                        break;
+					case ADODB_FORCE_EMPTY:
+						//Set empty
+						$arrFields[$upperfname] = "";
+						$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
+						break;
 
-						default:
-                        case ADODB_FORCE_VALUE:
-                            //Set the value that was given in array, so you can give both null and empty values
-							if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
-								$values  .= "null, ";
-							} else {
-								$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
-             				}
-              			break;
+					default:
+					case ADODB_FORCE_VALUE:
+						//Set the value that was given in array, so you can give both null and empty values
+						if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
+							$values .= "null, ";
+						} else {
+							$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
+						}
+						break;
 
-						case ADODB_FORCE_NULL_AND_ZERO:
-							switch ($type)
-							{
-								case 'N':
-								case 'I':
-								case 'L':
-									$values .= '0, ';
-									break;
-								default:
-									$values .= "null, ";
-									break;
-							}
+					case ADODB_FORCE_NULL_AND_ZERO:
+						switch ($type) {
+							case 'N':
+							case 'I':
+							case 'L':
+								$values .= '0, ';
+								break;
+							default:
+								$values .= "null, ";
+								break;
+						}
 						break;
 
-             		} // switch
+				} // switch
 
-            /*********************************************************/
+				/*********************************************************/
 			} else {
 				//we do this so each driver can customize the sql for
 				//DB specific column types.
@@ -998,11 +1000,12 @@ static $cacheCols;
 				$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
 			}
 
-			if ($bad) continue;
+			if ($bad) {
+				continue;
+			}
 			// Set the counter for the number of fields that will be inserted.
 			$fieldInsertedCount++;
 
-
 			// Get the name of the fields to insert
 			$fields .= $fnameq . ", ";
 		}
@@ -1010,7 +1013,7 @@ static $cacheCols;
 
 
 	// If there were any inserted fields then build the rest of the insert query.
-	if ($fieldInsertedCount <= 0)  return false;
+	if ($fieldInsertedCount <= 0) return false;
 
 	// Get the table name from the existing query.
 	if (!$tableName) {
@@ -1048,76 +1051,76 @@ static $cacheCols;
  */
 function _adodb_column_sql_oci8(&$zthis,$action, $type, $fname, $fnameq, $arrFields)
 {
-    $sql = '';
-
-    // Based on the datatype of the field
-    // Format the value properly for the database
-    switch($type) {
-    case 'B':
-        //in order to handle Blobs correctly, we need
-        //to do some magic for Oracle
-
-        //we need to create a new descriptor to handle
-        //this properly
-        if (!empty($zthis->hasReturningInto)) {
-            if ($action == 'I') {
-                $sql = 'empty_blob(), ';
-            } else {
-                $sql = $fnameq. '=empty_blob(), ';
-            }
-            //add the variable to the returning clause array
-            //so the user can build this later in
-            //case they want to add more to it
-            $zthis->_returningArray[$fname] = ':xx'.$fname.'xx';
-        } else if (empty($arrFields[$fname])){
-            if ($action == 'I') {
-                $sql = 'empty_blob(), ';
-            } else {
-                $sql = $fnameq. '=empty_blob(), ';
-            }
-        } else {
-            //this is to maintain compatibility
-            //with older adodb versions.
-			$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
-        }
-        break;
-
-    case "X":
-        //we need to do some more magic here for long variables
-        //to handle these correctly in oracle.
-
-        //create a safe bind var name
-        //to avoid conflicts w/ dupes.
-       if (!empty($zthis->hasReturningInto)) {
-            if ($action == 'I') {
-                $sql = ':xx'.$fname.'xx, ';
-            } else {
-                $sql = $fnameq.'=:xx'.$fname.'xx, ';
-            }
-            //add the variable to the returning clause array
-            //so the user can build this later in
-            //case they want to add more to it
-            $zthis->_returningArray[$fname] = ':xx'.$fname.'xx';
-        } else {
-            //this is to maintain compatibility
-            //with older adodb versions.
-			$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
-        }
-        break;
+	// Based on the datatype of the field
+	// Format the value properly for the database
+	switch ($type) {
+		case 'B':
+			//in order to handle Blobs correctly, we need
+			//to do some magic for Oracle
+
+			//we need to create a new descriptor to handle
+			//this properly
+			if (!empty($zthis->hasReturningInto)) {
+				if ($action == 'I') {
+					$sql = 'empty_blob(), ';
+				} else {
+					$sql = $fnameq . '=empty_blob(), ';
+				}
+				//add the variable to the returning clause array
+				//so the user can build this later in
+				//case they want to add more to it
+				$zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
+			} else {
+				if (empty($arrFields[$fname])) {
+					if ($action == 'I') {
+						$sql = 'empty_blob(), ';
+					} else {
+						$sql = $fnameq . '=empty_blob(), ';
+					}
+				} else {
+					//this is to maintain compatibility
+					//with older adodb versions.
+					$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
+				}
+			}
+			break;
+
+		case "X":
+			//we need to do some more magic here for long variables
+			//to handle these correctly in oracle.
+
+			//create a safe bind var name
+			//to avoid conflicts w/ dupes.
+			if (!empty($zthis->hasReturningInto)) {
+				if ($action == 'I') {
+					$sql = ':xx' . $fname . 'xx, ';
+				} else {
+					$sql = $fnameq . '=:xx' . $fname . 'xx, ';
+				}
+				//add the variable to the returning clause array
+				//so the user can build this later in
+				//case they want to add more to it
+				$zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
+			} else {
+				//this is to maintain compatibility
+				//with older adodb versions.
+				$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
+			}
+			break;
 
-    default:
-		$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq,  $arrFields, false);
-        break;
-    }
+		default:
+			$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
+			break;
+	}
 
-    return $sql;
+	return $sql;
 }
 
 function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields, $recurse=true)
 {
 
 	if ($recurse) {
-		switch($zthis->dataProvider)  {
+		switch($zthis->dataProvider) {
 		case 'postgres':
 			if ($type == 'L') $type = 'C';
 			break;
@@ -1143,15 +1146,15 @@ function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields,
 			break;
 
 		case "N":
-		    $val = $arrFields[$fname];
+			$val = $arrFields[$fname];
 			if (!is_numeric($val)) $val = str_replace(',', '.', (float)$val);
-		    break;
+			break;
 
 		case "I":
 		case "R":
-		    $val = $arrFields[$fname];
+			$val = $arrFields[$fname];
 			if (!is_numeric($val)) $val = (integer) $val;
-		    break;
+			break;
 
 		default:
 			$val = str_replace(array("'"," ","("),"",$arrFields[$fname]); // basic sql injection defence
@@ -1161,178 +1164,225 @@ function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields,
 
 	if ($action == 'I') return $val . ", ";
 
-	return $fnameq . "=" . $val  . ", ";
+	return $fnameq . "=" . $val . ", ";
 }
 
 
-
-function _adodb_debug_execute(&$zthis, $sql, $inputarr)
+/**
+* Replaces standard _execute when debug mode is enabled
+*
+* @param ADOConnection   $zthis    An ADOConnection object
+* @param string|string[] $sql      A string or array of SQL statements
+* @param string[]|null   $inputarr An optional array of bind parameters
+*
+* @return  handle|void A handle to the executed query
+*/
+function _adodb_debug_execute($zthis, $sql, $inputarr)
 {
+	// Unpack the bind parameters
 	$ss = '';
 	if ($inputarr) {
-		foreach($inputarr as $kk=>$vv) {
-			if (is_string($vv) && strlen($vv)>64) $vv = substr($vv,0,64).'...';
-			if (is_null($vv)) $ss .= "($kk=>null) ";
-			else 
-			{
-				if (is_array($vv))
-				{
-					$vv = sprintf("Array Of Values: [%s]", implode(',',$vv));
+		foreach ($inputarr as $kk => $vv) {
+			if (is_string($vv) && strlen($vv) > 64) {
+				$vv = substr($vv, 0, 64) . '...';
+			}
+			if (is_null($vv)) {
+				$ss .= "($kk=>null) ";
+			} else {
+				if (is_array($vv)) {
+					$vv = sprintf("Array Of Values: [%s]", implode(',', $vv));
 				}
 				$ss .= "($kk=>'$vv') ";
 			}
 		}
-		
 		$ss = "[ $ss ]";
 	}
+
 	$sqlTxt = is_array($sql) ? $sql[0] : $sql;
-	/*str_replace(', ','##1#__^LF',is_array($sql) ? $sql[0] : $sql);
-	$sqlTxt = str_replace(',',', ',$sqlTxt);
-	$sqlTxt = str_replace('##1#__^LF', ', ' ,$sqlTxt);
-	*/
+
+	// Remove newlines and tabs, compress repeating spaces
+	$sqlTxt = preg_replace('/\s+/', ' ', $sqlTxt);
+
 	// check if running from browser or command-line
 	$inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
 
-	$dbt = $zthis->databaseType;
-	if (isset($zthis->dsnType)) $dbt .= '-'.$zthis->dsnType;
+	$myDatabaseType = $zthis->databaseType;
+	if (!isset($zthis->dsnType)) {
+		// Append the PDO driver name
+		$myDatabaseType .= '-' . $zthis->dsnType;
+	}
+
 	if ($inBrowser) {
 		if ($ss) {
-			$ss = '<code>'.htmlspecialchars($ss).'</code>';
+			// Default formatting for passed parameter
+			$ss = sprintf('<code class="adodb-debug">%s</code>', htmlspecialchars($ss));
+		}
+		if ($zthis->debug === -1) {
+			$outString = "<br class='adodb-debug'>(%s):  %s &nbsp; %s<br class='adodb-debug'>";
+			ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
+		} elseif ($zthis->debug !== -99) {
+			$outString = "<hr class='adodb-debug'>(%s):  %s &nbsp; %s<hr class='adodb-debug'>";
+			ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
 		}
-		if ($zthis->debug === -1)
-			ADOConnection::outp( "<br>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<br>\n",false);
-		else if ($zthis->debug !== -99)
-			ADOConnection::outp( "<hr>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<hr>\n",false);
 	} else {
-		$ss = "\n   ".$ss;
-		if ($zthis->debug !== -99)
-			ADOConnection::outp("-----<hr>\n($dbt): ".$sqlTxt." $ss\n-----<hr>\n",false);
+		// CLI output
+		if ($zthis->debug !== -99) {
+			$outString = sprintf("%s\n%s\n    %s %s \n%s\n", str_repeat('-', 78), $myDatabaseType, $sqlTxt, $ss, str_repeat('-', 78));
+			ADOConnection::outp($outString, false);
+		}
 	}
 
-	$qID = $zthis->_query($sql,$inputarr);
+	// Now execute the query
+	$qID = $zthis->_query($sql, $inputarr);
 
-	/*
-		Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
-		because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion
-	*/
+	// Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
+	// because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion
 	if ($zthis->databaseType == 'mssql') {
-	// ErrorNo is a slow function call in mssql, and not reliable in PHP 4.0.6
-
-		if($emsg = $zthis->ErrorMsg()) {
+		// ErrorNo is a slow function call in mssql
+		if ($emsg = $zthis->ErrorMsg()) {
 			if ($err = $zthis->ErrorNo()) {
-				if ($zthis->debug === -99)
-					ADOConnection::outp( "<hr>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<hr>\n",false);
+				if ($zthis->debug === -99) {
+					ADOConnection::outp("<hr>\n($myDatabaseType): " . htmlspecialchars($sqlTxt) . " &nbsp; $ss\n<hr>\n", false);
+				}
 
-				ADOConnection::outp($err.': '.$emsg);
+				ADOConnection::outp($err . ': ' . $emsg);
 			}
 		}
-	} else if (!$qID) {
-
-		if ($zthis->debug === -99)
-				if ($inBrowser) ADOConnection::outp( "<hr>\n($dbt): ".htmlspecialchars($sqlTxt)." &nbsp; $ss\n<hr>\n",false);
-				else ADOConnection::outp("-----<hr>\n($dbt): ".$sqlTxt."$ss\n-----<hr>\n",false);
+	} else {
+		if (!$qID) {
+			// Statement execution has failed
+			if ($zthis->debug === -99) {
+				if ($inBrowser) {
+					$outString = "<hr class='adodb-debug'>(%s):  %s &nbsp; %s<hr class='adodb-debug'>";
+					ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
+				} else {
+					$outString = sprintf("%s\n%s\n    %s %s \n%s\n",str_repeat('-',78),$myDatabaseType,$sqlTxt,$ss,str_repeat('-',78));
+					ADOConnection::outp($outString, false);
+				}
+			}
 
-		ADOConnection::outp($zthis->ErrorNo() .': '. $zthis->ErrorMsg());
+			// Send last error to output
+			$errno = $zthis->ErrorNo();
+			if ($errno) {
+				ADOConnection::outp($errno . ': ' . $zthis->ErrorMsg());
+			}
+		}
 	}
 
-	if ($zthis->debug === 99) _adodb_backtrace(true,9999,2);
+	if ($qID === false || $zthis->debug === 99) {
+		_adodb_backtrace();
+	}
 	return $qID;
 }
 
-# pretty print the debug_backtrace function
-function _adodb_backtrace($printOrArr=true,$levels=9999,$skippy=0,$ishtml=null)
+/**
+ * Pretty print the debug_backtrace function
+ *
+ * @param string[]|bool $printOrArr       Whether to print the result directly or return the result
+ * @param int           $maximumDepth     The maximum depth of the array to traverse
+ * @param int           $elementsToIgnore The backtrace array indexes to ignore
+ * @param null|bool     $ishtml           True if we are in a CGI environment, false for CLI,
+ *                                        null to auto detect
+ *
+ * @return string Formatted backtrace
+ */
+function _adodb_backtrace($printOrArr=true, $maximumDepth=9999, $elementsToIgnore=0, $ishtml=null)
 {
-	if (!function_exists('debug_backtrace')) return '';
+	if (!function_exists('debug_backtrace')) {
+		return '';
+	}
 
-	if ($ishtml === null) $html =  (isset($_SERVER['HTTP_USER_AGENT']));
-	else $html = $ishtml;
+	if ($ishtml === null) {
+		// Auto determine if we in a CGI enviroment
+		$html = (isset($_SERVER['HTTP_USER_AGENT']));
+	} else {
+		$html = $ishtml;
+	}
 
-	$fmt =  ($html) ? "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>" : "%% line %4d, file: %s";
+	$cgiString = "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>";
+	$cliString = "%% line %4d, file: %s";
+	$fmt = ($html) ? $cgiString : $cliString;
 
 	$MAXSTRLEN = 128;
 
 	$s = ($html) ? '<pre align=left>' : '';
 
-	if (is_array($printOrArr)) $traceArr = $printOrArr;
-	else $traceArr = debug_backtrace();
+	if (is_array($printOrArr)) {
+		$traceArr = $printOrArr;
+	} else {
+		$traceArr = debug_backtrace();
+	}
+
+	// Remove first 2 elements that just show calls to adodb_backtrace
 	array_shift($traceArr);
 	array_shift($traceArr);
-	$tabs = sizeof($traceArr)-2;
+
+	// We want last element to have no indent
+	$tabs = sizeof($traceArr) - 1;
 
 	foreach ($traceArr as $arr) {
-		if ($skippy) {$skippy -= 1; continue;}
-		$levels -= 1;
-		if ($levels < 0) break;
+		if ($elementsToIgnore) {
+			// Ignore array element at start of array
+			$elementsToIgnore--;
+			$tabs--;
+			continue;
+		}
+		$maximumDepth--;
+		if ($maximumDepth < 0) {
+			break;
+		}
 
 		$args = array();
-		for ($i=0; $i < $tabs; $i++) $s .=  ($html) ? ' &nbsp; ' : "\t";
-		$tabs -= 1;
-		if ($html) $s .= '<font face="Courier New,Courier">';
-		if (isset($arr['class'])) $s .= $arr['class'].'.';
-		if (isset($arr['args']))
-		 foreach($arr['args'] as $v) {
-			if (is_null($v)) $args[] = 'null';
-			else if (is_array($v)) $args[] = 'Array['.sizeof($v).']';
-			else if (is_object($v)) $args[] = 'Object:'.get_class($v);
-			else if (is_bool($v)) $args[] = $v ? 'true' : 'false';
-			else {
-				$v = (string) @$v;
-				$str = htmlspecialchars(str_replace(array("\r","\n"),' ',substr($v,0,$MAXSTRLEN)));
-				if (strlen($v) > $MAXSTRLEN) $str .= '...';
-				$args[] = $str;
-			}
+
+		if ($tabs) {
+			$s .= str_repeat($html ? ' &nbsp; ' : "\t", $tabs);
+			$tabs--;
+		}
+		if ($html) {
+			$s .= '<font face="Courier New,Courier">';
 		}
-		$s .= $arr['function'].'('.implode(', ',$args).')';
 
+		if (isset($arr['class'])) {
+			$s .= $arr['class'] . '.';
+		}
 
-		$s .= @sprintf($fmt, $arr['line'],$arr['file'],basename($arr['file']));
+		if (isset($arr['args'])) {
+			foreach ($arr['args'] as $v) {
+				if (is_null($v)) {
+					$args[] = 'null';
+				} elseif (is_array($v)) {
+					$args[] = 'Array[' . sizeof($v) . ']';
+				} elseif (is_object($v)) {
+					$args[] = 'Object:' . get_class($v);
+				} elseif (is_bool($v)) {
+					$args[] = $v ? 'true' : 'false';
+				} else {
+					$v = (string)@$v;
+					// Truncate
+					$v = substr($v, 0, $MAXSTRLEN);
+					// Remove newlines and tabs, compress repeating spaces
+					$v = preg_replace('/\s+/', ' ', $v);
+					// Convert htmlchars (not sure why we do this in CLI)
+					$str = htmlspecialchars($v);
+
+					if (strlen($v) > $MAXSTRLEN) {
+						$str .= '...';
+					}
 
+					$args[] = $str;
+				}
+			}
+		}
+		$s .= $arr['function'] . '(' . implode(', ', $args) . ')';
+		$s .= @sprintf($fmt, $arr['line'], $arr['file'], basename($arr['file']));
 		$s .= "\n";
 	}
-	if ($html) $s .= '</pre>';
-	if ($printOrArr) print $s;
+	if ($html) {
+		$s .= '</pre>';
+	}
+	if ($printOrArr) {
+		print $s;
+	}
 
 	return $s;
 }
-/*
-function _adodb_find_from($sql)
-{
-
-	$sql = str_replace(array("\n","\r"), ' ', $sql);
-	$charCount = strlen($sql);
-
-	$inString = false;
-	$quote = '';
-	$parentheseCount = 0;
-	$prevChars = '';
-	$nextChars = '';
-
-
-	for($i = 0; $i < $charCount; $i++) {
-
-    	$char = substr($sql,$i,1);
-	    $prevChars = substr($sql,0,$i);
-    	$nextChars = substr($sql,$i+1);
-
-		if((($char == "'" || $char == '"' || $char == '`') && substr($prevChars,-1,1) != '\\') && $inString === false) {
-			$quote = $char;
-			$inString = true;
-		}
-
-		elseif((($char == "'" || $char == '"' || $char == '`') && substr($prevChars,-1,1) != '\\') && $inString === true && $quote == $char) {
-			$quote = "";
-			$inString = false;
-		}
-
-		elseif($char == "(" && $inString === false)
-			$parentheseCount++;
-
-		elseif($char == ")" && $inString === false && $parentheseCount > 0)
-			$parentheseCount--;
-
-		elseif($parentheseCount <= 0 && $inString === false && $char == " " && strtoupper(substr($prevChars,-5,5)) == " FROM")
-			return $i;
-
-	}
-}
-*/
diff --git a/adodb-loadbalancer.inc.php b/adodb-loadbalancer.inc.php
index 2e4f392..de06780 100644
--- a/adodb-loadbalancer.inc.php
+++ b/adodb-loadbalancer.inc.php
@@ -38,17 +38,17 @@ class ADOdbLoadBalancer
     /**
      * @var bool|array    All connections to each database.
      */
-    protected $connections = false;
+    protected $connections = [];
 
     /**
      * @var bool|array    Just connections to the write capable database.
      */
-    protected $connections_write = false;
+    protected $connections_write = [];
 
     /**
      * @var bool|array    Just connections to the readonly database.
      */
-    protected $connections_readonly = false;
+    protected $connections_readonly = [];
 
     /**
      * @var array    Counts of all connections and their types.
@@ -73,12 +73,12 @@ class ADOdbLoadBalancer
     /**
      * @var bool    Session variables that must be maintained across all connections, ie: SET TIME ZONE.
      */
-    protected $session_variables = false;
+    protected $session_variables = [];
 
     /**
      * @var bool    Called immediately after connecting to any DB.
      */
-    protected $user_defined_session_init_sql = false;
+    protected $user_defined_session_init_sql = [];
 
 
     /**
@@ -174,7 +174,7 @@ class ADOdbLoadBalancer
      * @param  string $type Type of database connection, either: 'write' capable or 'readonly'
      * @return bool|int|string
      */
-    private function getConnectionByWeight($type)
+    public function getConnectionByWeight($type)
     {
         if ($type == 'readonly') {
             $total_weight = $this->total_connection_weights['all'];
@@ -233,7 +233,7 @@ class ADOdbLoadBalancer
      * @return bool|ADOConnection
      * @throws Exception
      */
-    private function _getConnection($connection_id)
+    public function getConnectionById($connection_id)
     {
         if (isset($this->connections[$connection_id])) {
             $connection_obj = $this->connections[$connection_id];
@@ -261,6 +261,15 @@ class ADOdbLoadBalancer
                     throw $e; // No connections left, reThrow exception so application can catch it.
                 }
 
+                // Check to see if a connection test callback was defined, and if so execute it.
+                // This is useful for testing replication lag and such to ensure the connection is suitable to be used.
+                $test_connection_callback = $connection_obj->getConnectionTestCallback();
+                if (is_callable($test_connection_callback)
+                    && $test_connection_callback($connection_obj, $adodb_obj) !== TRUE
+                ) {
+                    return false;
+                }
+
                 if (is_array($this->user_defined_session_init_sql)) {
                     foreach ($this->user_defined_session_init_sql as $session_init_sql) {
                         $adodb_obj->Execute($session_init_sql);
@@ -298,9 +307,12 @@ class ADOdbLoadBalancer
 
             if ($connection_id !== false) {
                 try {
-                    $adodb_obj = $this->_getConnection($connection_id);
-                    // $connection_obj = $this->connections[$connection_id];
-                    break;
+                    $adodb_obj = $this->getConnectionById($connection_id);
+                    if (is_object($adodb_obj)) {
+                        break; //Found valid connection, continue with it.
+                    } else {
+                        throw new Exception('ADODB Connection Object does not exist. Perhaps LoadBalancer Database Connection Test Failed?');
+                    }
                 } catch (Exception $e) {
                     // Connection error, see if there are other connections to try still.
                     $this->removeConnection($connection_id);
@@ -315,6 +327,10 @@ class ADOdbLoadBalancer
             }
         }
 
+        if (!isset($connection_id)) {
+            throw new Exception('No connection available to use at this time! Type: ' . $type);
+        }
+
         $this->last_connection_id[$type] = $connection_id;
 
         if ($pin_connection === true) {
@@ -387,7 +403,7 @@ class ADOdbLoadBalancer
      */
     private function executeSessionVariables($adodb_obj = false)
     {
-        if (is_array($this->session_variables)) {
+        if (is_array($this->session_variables) && count($this->session_variables) > 0) {
             $sql = '';
             foreach ($this->session_variables as $name => $value) {
                 // $sql .= 'SET SESSION '. $name .' '. $value;
@@ -432,7 +448,7 @@ class ADOdbLoadBalancer
                         && $connection_obj->getADOdbObject()->_connectionID !== false
                     )
                 ) {
-                    $adodb_obj = $this->_getConnection($key);
+                    $adodb_obj = $this->getConnectionById($key);
                     if (is_object($adodb_obj)) {
                         $result_arr[] = $adodb_obj->Execute($sql, $inputarr);
                     }
@@ -595,6 +611,7 @@ class ADOdbLoadBalancer
             case 'binddate':
             case 'bindtimestamp':
             case 'setfetchmode':
+            case 'setcustommetatype':
                   $type = false; // No connection necessary.
                 break;
 
@@ -686,6 +703,11 @@ class ADOdbLoadBalancerConnection
      */
     protected $adodb_obj = false;
 
+    /**
+     * @var callable    Closure
+     */
+    protected $connection_test_callback = NULL;
+
     /**
      * @var string    Type of connection, either 'write' capable or 'readonly'
      */
@@ -761,6 +783,24 @@ class ADOdbLoadBalancerConnection
         return true;
     }
 
+    /**
+     * Anonymous function that is called and must return TRUE for the connection to be usable.*
+     *   The first argument is the type of connection to test.
+     *   Useful to check things like replication lag.
+     * @param callable $callback
+     * @return void
+     */
+    function setConnectionTestCallback($callback) {
+        $this->connection_test_callback = $callback;
+    }
+
+    /**
+     * @return callable|null
+     */
+    function getConnectionTestCallback() {
+        return $this->connection_test_callback;
+    }
+
     /**
      * Returns the ADODB object for this connection.
      *
diff --git a/adodb-memcache.lib.inc.php b/adodb-memcache.lib.inc.php
index 7f110e7..a251c9c 100644
--- a/adodb-memcache.lib.inc.php
+++ b/adodb-memcache.lib.inc.php
@@ -17,6 +17,8 @@
  *
  * @copyright 2000-2013 John Lim
  * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
+ *
+ * @noinspection PhpUnused
  */
 
 // security - hide paths
@@ -26,183 +28,393 @@ global $ADODB_INCLUDED_MEMCACHE;
 $ADODB_INCLUDED_MEMCACHE = 1;
 
 global $ADODB_INCLUDED_CSV;
-if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
-
-	class ADODB_Cache_MemCache {
-		var $createdir = false; // create caching directory structure?
-
-		// $library will be populated with the proper library on connect
-		// and is used later when there are differences in specific calls
-		// between memcache and memcached
-		var $library = false;
-
-		//-----------------------------
-		// memcache specific variables
-
-		var $hosts;	// array of hosts
-		var $port = 11211;
-		var $compress = false; // memcache compression with zlib
-
-		var $_connected = false;
-		var $_memcache = false;
-
-		function __construct(&$obj)
-		{
-			$this->hosts = $obj->memCacheHost;
-			$this->port = $obj->memCachePort;
-			$this->compress = $obj->memCacheCompress;
-		}
-
-		// implement as lazy connection. The connection only occurs on CacheExecute call
-		function connect(&$err)
-		{
-			// do we have memcache or memcached?
-			if (class_exists('Memcache')) {
-				$this->library='Memcache';
-				$memcache = new MemCache;
-			} elseif (class_exists('Memcached')) {
-				$this->library='Memcached';
-				$memcache = new MemCached;
-			} else {
-				$err = 'Neither the Memcache nor Memcached PECL extensions were found!';
-				return false;
-			}
+if (empty($ADODB_INCLUDED_CSV)) {
+	include_once(ADODB_DIR . '/adodb-csvlib.inc.php');
+}
 
-			if (!is_array($this->hosts)) $this->hosts = array($this->hosts);
+class ADODB_Cache_MemCache
+{
+	/**
+	 * @var bool Prevents parent class calling non-existant function
+	 */
+	public $createdir = false;
 
-			$failcnt = 0;
-			foreach($this->hosts as $host) {
-				if (!@$memcache->addServer($host,$this->port)) {
-					$failcnt += 1;
-				}
-			}
-			if ($failcnt == sizeof($this->hosts)) {
-				$err = 'Can\'t connect to any memcache server';
+	/**
+	 * @var array of hosts
+	 */
+	private $hosts;
+
+	/**
+	 * @var int Connection Port, uses default
+	 */
+	private $port;
+
+	/**
+	 * @var bool memcache compression with zlib
+	 */
+	private $compress;
+
+	/**
+	 * @var array of options for memcached only
+	 */
+	private $options;
+
+	/**
+	 * @var bool Internal flag indicating successful connection
+	 */
+	private $isConnected = false;
+
+	/**
+	 * @var Memcache|Memcached Handle for the Memcache library
+	 *
+	 * Populated with the proper library on connect, used later when
+	 * there are differences in specific calls between memcache and memcached
+	 */
+	private $memcacheLibrary = false;
+
+	/**
+	 * @var array New server feature controller lists available servers
+	 */
+	private $serverControllers = array();
+
+	/**
+	 * @var array New server feature template uses granular server controller
+	 */
+	private $serverControllerTemplate = array(
+		'host' => '',
+		'port' => 11211,
+		'weight' => 0,
+	);
+
+	/**
+	 * An integer index into the libraries
+	 * @see $libraries
+	 */
+	const MCLIB = 1;
+	const MCLIBD = 2;
+
+	/**
+	 * @var array Xrefs the library flag to the actual class name
+	 */
+	private $libraries = array(
+		self::MCLIB => 'Memcache',
+		self::MCLIBD => 'Memcached'
+	);
+
+	/**
+	 * @var int An indicator of which library we are using
+	 */
+	private $libraryFlag;
+
+	/**
+	 * Class Constructor.
+	 *
+	 * @param ADOConnection $db
+	 */
+	public function __construct($db)
+	{
+		$this->hosts = $db->memCacheHost;
+		$this->port = $this->serverControllerTemplate['port'] = $db->memCachePort;
+		$this->compress = $db->memCacheCompress;
+		$this->options = $db->memCacheOptions;
+	}
+
+	/**
+	 * Return true if the current library is Memcached.
+	 * @return bool
+	 */
+	public function isLibMemcached(): bool
+	{
+		return $this->libraryFlag == self::MCLIBD;
+	}
+
+	/**
+	 * Lazy connection.
+	 *
+	 * The connection only occurs on CacheExecute call.
+	 *
+	 * @param string $err
+	 *
+	 * @return bool success of connecting to a server
+	 */
+	public function connect(&$err)
+	{
+		// do we have memcache or memcached? see the note at adodb.org on memcache
+		if (class_exists('Memcache')) {
+			$this->libraryFlag = self::MCLIB;
+		} elseif (class_exists('Memcached')) {
+			$this->libraryFlag = self::MCLIBD;
+		} else {
+			$err = 'Neither the Memcache nor Memcached PECL extensions were found!';
+			return false;
+		}
+
+		$usedLibrary = $this->libraries[$this->libraryFlag];
+
+		/** @var Memcache|Memcached $memCache */
+		$memCache = new $usedLibrary;
+		if (!$memCache) {
+			$err = 'Memcache library failed to initialize';
+			return false;
+		}
+
+		// Convert simple compression flag for memcached
+		if ($this->isLibMemcached()) {
+			$this->options[Memcached::OPT_COMPRESSION] = $this->compress;
+		}
+
+		// Are there any options available for memcached
+		if ($this->isLibMemcached() && count($this->options) > 0) {
+			$optionSuccess = $memCache->setOptions($this->options);
+			if (!$optionSuccess) {
+				$err = 'Invalid option parameters passed to Memcached';
 				return false;
 			}
-			$this->_connected = true;
-			$this->_memcache = $memcache;
-			return true;
 		}
 
-		// returns true or false. true if successful save
-		function writecache($filename, $contents, $debug, $secs2cache)
-		{
-			if (!$this->_connected) {
-				$err = '';
-				if (!$this->connect($err) && $debug) ADOConnection::outp($err);
-			}
-			if (!$this->_memcache) return false;
+		// Have we passed a controller array
+		if (!is_array($this->hosts)) {
+			$this->hosts = array($this->hosts);
+		}
 
-			$failed=false;
-			switch ($this->library) {
-				case 'Memcache':
-					if (!$this->_memcache->set($filename, $contents, $this->compress ? MEMCACHE_COMPRESSED : 0, $secs2cache)) {
-						$failed=true;
-					}
-					break;
-				case 'Memcached':
-					if (!$this->_memcache->set($filename, $contents, $secs2cache)) {
-						$failed=true;
-					}
-					break;
-				default:
-					$failed=true;
-					break;
+		if (!is_array($this->hosts[0])) {
+			// Old way, convert to controller
+			foreach ($this->hosts as $ipAddress) {
+				$connector = $this->serverControllerTemplate;
+				$connector['host'] = $ipAddress;
+				$connector['port'] = $this->port;
+
+				$this->serverControllers[] = $connector;
 			}
+		} else {
+			// New way, must validate port, etc
+			foreach ($this->hosts as $controller) {
+				$connector = array_merge($this->serverControllerTemplate, $controller);
+				if ($this->isLibMemcached()) {
+					$connector['weight'] = (int)$connector['weight'];
+				} else {
+					// Cannot use weight in memcache, simply discard
+					$connector['weight'] = 0;
+				}
 
-			if($failed) {
-				if ($debug) ADOConnection::outp(" Failed to save data at the memcache server!<br>\n");
-				return false;
+				$this->serverControllers[] = $connector;
 			}
+		}
 
+		// Checks for existing connections ( but only for memcached )
+		if ($this->isLibMemcached() && !empty($memCache->getServerList())) {
+			// Use the existing configuration
+			$this->isConnected = true;
+			$this->memcacheLibrary = $memCache;
 			return true;
 		}
 
-		// returns a recordset
-		function readcache($filename, &$err, $secs2cache, $rsClass)
-		{
-			$false = false;
-			if (!$this->_connected) $this->connect($err);
-			if (!$this->_memcache) return $false;
-
-			$rs = $this->_memcache->get($filename);
-			if (!$rs) {
-				$err = 'Item with such key doesn\'t exist on the memcache server.';
-				return $false;
+		$failcnt = 0;
+		foreach ($this->serverControllers as $controller) {
+			if ($this->isLibMemcached()) {
+				if (!@$memCache->addServer($controller['host'], $controller['port'], $controller['weight'])) {
+					$failcnt++;
+				}
+			} else {
+				if (!@$memCache->addServer($controller['host'], $controller['port'])) {
+					$failcnt++;
+				}
 			}
+		}
+		if ($failcnt == sizeof($this->serverControllers)) {
+			$err = 'Can\'t connect to any memcache server';
+			return false;
+		}
+
+		$this->memcacheLibrary = $memCache;
 
-			// hack, should actually use _csv2rs
-			$rs = explode("\n", $rs);
-            unset($rs[0]);
-            $rs = join("\n", $rs);
- 			$rs = unserialize($rs);
-			if (! is_object($rs)) {
-				$err = 'Unable to unserialize $rs';
-				return $false;
+		// A valid memcache connection is available
+		$this->isConnected = true;
+		return true;
+	}
+
+	/**
+	 * Writes a cached query to the server
+	 *
+	 * @param string $filename The MD5 of the query to cache
+	 * @param string $contents The query results
+	 * @param bool $debug
+	 * @param int $secs2cache
+	 *
+	 * @return bool true or false. true if successful save
+	 */
+	public function writeCache($filename, $contents, $debug, $secs2cache)
+	{
+		$err = '';
+		if (!$this->isConnected && $debug) {
+			// Call to writeCache() before connect(), try to connect
+			if (!$this->connect($err)) {
+				ADOConnection::outp($err);
 			}
-			if ($rs->timeCreated == 0) return $rs; // apparently have been reports that timeCreated was set to 0 somewhere
-
-			$tdiff = intval($rs->timeCreated+$secs2cache - time());
-			if ($tdiff <= 2) {
-				switch($tdiff) {
-					case 2:
-						if ((rand() & 15) == 0) {
-							$err = "Timeout 2";
-							return $false;
-						}
-						break;
-					case 1:
-						if ((rand() & 3) == 0) {
-							$err = "Timeout 1";
-							return $false;
-						}
-						break;
-					default:
-						$err = "Timeout 0";
-						return $false;
-				}
+		} else {
+			if (!$this->isConnected) {
+				$this->connect($err);
 			}
-			return $rs;
 		}
 
-		function flushall($debug=false)
-		{
-			if (!$this->_connected) {
-				$err = '';
-				if (!$this->connect($err) && $debug) ADOConnection::outp($err);
+		if (!$this->memcacheLibrary) {
+			return false;
+		}
+
+		$failed = false;
+		switch ($this->libraryFlag) {
+			case self::MCLIB:
+				if (!$this->memcacheLibrary->set($filename, $contents, $this->compress ? MEMCACHE_COMPRESSED : 0,
+					$secs2cache)) {
+					$failed = true;
+				}
+				break;
+			case self::MCLIBD:
+				if (!$this->memcacheLibrary->set($filename, $contents, $secs2cache)) {
+					$failed = true;
+				}
+				break;
+			default:
+				$failed = true;
+				break;
+		}
+
+		if ($failed) {
+			if ($debug) {
+				ADOConnection::outp(" Failed to save data at the memcache server!<br>\n");
 			}
-			if (!$this->_memcache) return false;
+			return false;
+		}
 
-			$del = $this->_memcache->flush();
+		return true;
+	}
 
-			if ($debug)
-				if (!$del) ADOConnection::outp("flushall: failed!<br>\n");
-				else ADOConnection::outp("flushall: succeeded!<br>\n");
+	/**
+	 * Reads a cached query from the server.
+	 *
+	 * @param string $filename The MD5 of the query to read
+	 * @param string $err The query results
+	 * @param int $secs2cache
+	 * @param object $rsClass **UNUSED**
+	 *
+	 * @return object|bool record or false.
+	 *
+	 * @noinspection PhpUnusedParameterInspection
+	 */
+	public function readCache($filename, &$err, $secs2cache, $rsClass)
+	{
+		if (!$this->isConnected) {
+			$this->connect($err);
+		}
+		if (!$this->memcacheLibrary) {
+			return false;
+		}
+
+		$rs = $this->memcacheLibrary->get($filename);
+		if (!$rs) {
+			$err = 'Item with such key doesn\'t exist on the memcache server.';
+			return false;
+		}
+
+		// hack, should actually use _csv2rs
+		$rs = explode("\n", $rs);
+		unset($rs[0]);
+		$rs = join("\n", $rs);
+		$rs = unserialize($rs);
+		if (!is_object($rs)) {
+			$err = 'Unable to unserialize $rs';
+			return false;
+		}
+		if ($rs->timeCreated == 0) {
+			return $rs;
+		} // apparently have been reports that timeCreated was set to 0 somewhere
 
-			return $del;
+		$tdiff = intval($rs->timeCreated + $secs2cache - time());
+		if ($tdiff <= 2) {
+			switch ($tdiff) {
+				case 2:
+					if ((rand() & 15) == 0) {
+						$err = "Timeout 2";
+						return false;
+					}
+					break;
+				case 1:
+					if ((rand() & 3) == 0) {
+						$err = "Timeout 1";
+						return false;
+					}
+					break;
+				default:
+					$err = "Timeout 0";
+					return false;
+			}
 		}
+		return $rs;
+	}
 
-		function flushcache($filename, $debug=false)
-		{
-			if (!$this->_connected) {
-  				$err = '';
-  				if (!$this->connect($err) && $debug) ADOConnection::outp($err);
+	/**
+	 * Flushes all of the stored memcache data
+	 *
+	 * @param bool $debug
+	 *
+	 * @return bool The response from the memcache server
+	 */
+	public function flushAll($debug = false)
+	{
+		if (!$this->isConnected) {
+			$err = '';
+			if (!$this->connect($err) && $debug) {
+				ADOConnection::outp($err);
 			}
-			if (!$this->_memcache) return false;
+		}
+		if (!$this->memcacheLibrary) {
+			return false;
+		}
 
-			$del = $this->_memcache->delete($filename);
+		$del = $this->memcacheLibrary->flush();
+
+		if ($debug) {
+			if (!$del) {
+				ADOConnection::outp("flushall: failed!<br>\n");
+			} else {
+				ADOConnection::outp("flushall: succeeded!<br>\n");
+			}
+		}
 
-			if ($debug)
-				if (!$del) ADOConnection::outp("flushcache: $key entry doesn't exist on memcache server!<br>\n");
-				else ADOConnection::outp("flushcache: $key entry flushed from memcache server!<br>\n");
+		return $del;
+	}
 
-			return $del;
+	/**
+	 * Flushes the contents of a specified query
+	 *
+	 * @param string $filename The MD5 of the query to flush
+	 * @param bool $debug
+	 *
+	 * @return bool The response from the memcache server
+	 */
+	public function flushCache($filename, $debug = false)
+	{
+		if (!$this->isConnected) {
+			$err = '';
+			if (!$this->connect($err) && $debug) {
+				ADOConnection::outp($err);
+			}
+		}
+		if (!$this->memcacheLibrary) {
+			return false;
 		}
 
-		// not used for memcache
-		function createdir($dir, $hash)
-		{
-			return true;
+		$del = $this->memcacheLibrary->delete($filename);
+
+		if ($debug) {
+			if (!$del) {
+				ADOConnection::outp("flushcache: $filename entry doesn't exist on memcache server!<br>\n");
+			} else {
+				ADOConnection::outp("flushcache: $filename entry flushed from memcache server!<br>\n");
+			}
 		}
+
+		return $del;
 	}
+
+}
diff --git a/adodb-perf.inc.php b/adodb-perf.inc.php
index d9d8a99..9161053 100644
--- a/adodb-perf.inc.php
+++ b/adodb-perf.inc.php
@@ -237,6 +237,9 @@ class adodb_perf {
 	var $createTableSQL = false;
 	var $maxLength = 2000;
 
+	/** @var array Settings data. */
+	var $settings = [];
+
     // Sets the tablename to be used
     static function table($newtable = false)
     {
@@ -1014,7 +1017,7 @@ Committed_AS:   348732 kB
      *      <code>ADODB_OPT_LOW</code> for CPU-less optimization
      *      Default is LOW <code>ADODB_OPT_LOW</code>
      * @author Markus Staab
-     * @return Returns <code>true</code> on success and <code>false</code> on error
+     * @return bool true on success, false on error
      */
     function OptimizeTables()
     {
@@ -1045,7 +1048,7 @@ Committed_AS:   348732 kB
      *      <code>ADODB_OPT_LOW</code> for CPU-less optimization
      *      Default is LOW <code>ADODB_OPT_LOW</code>
      * @author Markus Staab
-     * @return Returns <code>true</code> on success and <code>false</code> on error
+	 * @return bool true on success, false on error
      */
     function OptimizeTable( $table, $mode = ADODB_OPT_LOW)
     {
@@ -1059,7 +1062,7 @@ Committed_AS:   348732 kB
      * optimize each using <code>optmizeTable()</code>
      *
      * @author Markus Staab
-     * @return Returns <code>true</code> on success and <code>false</code> on error
+	 * @return bool true on success, false on error
      */
     function optimizeDatabase()
     {
diff --git a/adodb-time.inc.php b/adodb-time.inc.php
index cfbdc6a..0c3dd11 100644
--- a/adodb-time.inc.php
+++ b/adodb-time.inc.php
@@ -2,6 +2,8 @@
 /**
  * ADOdb Date Library.
  *
+ * @deprecated 5.22.6 Use 64-bit PHP native functions instead.
+ *
  * PHP native date functions use integer timestamps for computations.
  * Because of this, dates are restricted to the years 1901-2038 on Unix
  * and 1970-2038 on Windows due to integer overflow for dates beyond
diff --git a/adodb-xmlschema.inc.php b/adodb-xmlschema.inc.php
index 58e3aff..662e2aa 100644
--- a/adodb-xmlschema.inc.php
+++ b/adodb-xmlschema.inc.php
@@ -26,133 +26,119 @@
  * @author Dan Cech
  */
 
-function _file_get_contents($file)
-{
- 	if (function_exists('file_get_contents')) return file_get_contents($file);
-
-	$f = fopen($file,'r');
-	if (!$f) return '';
-	$t = '';
-
-	while ($s = fread($f,100000)) $t .= $s;
-	fclose($f);
-	return $t;
-}
-
-
 /**
-* Debug on or off
-*/
+ * Debug on or off
+ */
 if( !defined( 'XMLS_DEBUG' ) ) {
 	define( 'XMLS_DEBUG', FALSE );
 }
 
 /**
-* Default prefix key
-*/
+ * Default prefix key
+ */
 if( !defined( 'XMLS_PREFIX' ) ) {
 	define( 'XMLS_PREFIX', '%%P' );
 }
 
 /**
-* Maximum length allowed for object prefix
-*/
+ * Maximum length allowed for object prefix
+ */
 if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
 	define( 'XMLS_PREFIX_MAXLEN', 10 );
 }
 
 /**
-* Execute SQL inline as it is generated
-*/
+ * Execute SQL inline as it is generated
+ */
 if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
 	define( 'XMLS_EXECUTE_INLINE', FALSE );
 }
 
 /**
-* Continue SQL Execution if an error occurs?
-*/
+ * Continue SQL Execution if an error occurs?
+ */
 if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
 	define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
 }
 
 /**
-* Current Schema Version
-*/
+ * Current Schema Version
+ */
 if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
 	define( 'XMLS_SCHEMA_VERSION', '0.2' );
 }
 
 /**
-* Default Schema Version.  Used for Schemas without an explicit version set.
-*/
+ * Default Schema Version.  Used for Schemas without an explicit version set.
+ */
 if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
 	define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
 }
 
 /**
-* Default Schema Version.  Used for Schemas without an explicit version set.
-*/
+ * Default Schema Version.  Used for Schemas without an explicit version set.
+ */
 if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
 	define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
 }
 
 /**
-* Include the main ADODB library
-*/
+ * Include the main ADODB library
+ */
 if( !defined( '_ADODB_LAYER' ) ) {
 	require( 'adodb.inc.php' );
 	require( 'adodb-datadict.inc.php' );
 }
 
 /**
-* Abstract DB Object. This class provides basic methods for database objects, such
-* as tables and indexes.
-*
-* @package axmls
-* @access private
-*/
+ * Abstract DB Object. This class provides basic methods for database objects, such
+ * as tables and indexes.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbObject {
 
 	/**
-	* var object Parent
-	*/
+	 * var object Parent
+	 */
 	var $parent;
 
 	/**
-	* var string current element
-	*/
+	 * var string current element
+	 */
 	var $currentElement;
 
 	/**
-	* NOP
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * NOP
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * @access private
+	 */
 	function _tag_open( &$parser, $tag, $attributes ) {
 
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( &$parser, $cdata ) {
 
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( &$parser, $tag ) {
 
 	}
@@ -162,110 +148,119 @@ class dbObject {
 	}
 
 	/**
-	* Destroys the object
-	*/
+	 * Destroys the object
+	 */
 	function destroy() {
 	}
 
 	/**
-	* Checks whether the specified RDBMS is supported by the current
-	* database object or its ranking ancestor.
-	*
-	* @param string $platform RDBMS platform name (from ADODB platform list).
-	* @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
-	*/
+	 * Checks whether the specified RDBMS is supported by the current
+	 * database object or its ranking ancestor.
+	 *
+	 * @param string $platform RDBMS platform name (from ADODB platform list).
+	 * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
+	 */
 	function supportedPlatform( $platform = NULL ) {
 		return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
 	}
 
 	/**
-	* Returns the prefix set by the ranking ancestor of the database object.
-	*
-	* @param string $name Prefix string.
-	* @return string Prefix.
-	*/
+	 * Returns the prefix set by the ranking ancestor of the database object.
+	 *
+	 * @param string $name Prefix string.
+	 * @return string Prefix.
+	 */
 	function prefix( $name = '' ) {
 		return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
 	}
 
 	/**
-	* Extracts a field ID from the specified field.
-	*
-	* @param string $field Field.
-	* @return string Field ID.
-	*/
+	 * Extracts a field ID from the specified field.
+	 *
+	 * @param string $field Field.
+	 * @return string Field ID.
+	 */
 	function FieldID( $field ) {
 		return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
 	}
 }
 
 /**
-* Creates a table object in ADOdb's datadict format
-*
-* This class stores information about a database table. As charactaristics
-* of the table are loaded from the external source, methods and properties
-* of this class are used to build up the table description in ADOdb's
-* datadict format.
-*
-* @package axmls
-* @access private
-*/
+ * Creates a table object in ADOdb's datadict format
+ *
+ * This class stores information about a database table. As charactaristics
+ * of the table are loaded from the external source, methods and properties
+ * of this class are used to build up the table description in ADOdb's
+ * datadict format.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbTable extends dbObject {
 
 	/**
-	* @var string Table name
-	*/
+	 * @var string Table name
+	 */
 	var $name;
 
 	/**
-	* @var array Field specifier: Meta-information about each field
-	*/
+	 * @var array Field specifier: Meta-information about each field
+	 */
 	var $fields = array();
 
 	/**
-	* @var array List of table indexes.
-	*/
+	 * @var array List of table indexes.
+	 */
 	var $indexes = array();
 
 	/**
-	* @var array Table options: Table-level options
-	*/
+	 * @var array Table options: Table-level options
+	 */
 	var $opts = array();
 
 	/**
-	* @var string Field index: Keeps track of which field is currently being processed
-	*/
+	 * @var string Field index: Keeps track of which field is currently being processed
+	 */
 	var $current_field;
 
 	/**
-	* @var boolean Mark table for destruction
-	* @access private
-	*/
+	 * @var boolean Mark table for destruction
+	 * @access private
+	 */
 	var $drop_table;
 
 	/**
-	* @var boolean Mark field for destruction (not yet implemented)
-	* @access private
-	*/
+	 * @var boolean Mark field for destruction (not yet implemented)
+	 * @access private
+	 */
 	var $drop_field = array();
 
 	/**
-	* Iniitializes a new table object.
-	*
-	* @param string $prefix DB Object prefix
-	* @param array $attributes Array of table attributes.
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * @var array Platform-specific options
+	 * @access private
+	 */
+	var $currentPlatform = true;
+
+	/** @var dbData Stores information about table data. */
+	var $data;
+
+	/**
+	 * Iniitializes a new table object.
+	 *
+	 * @param string $prefix DB Object prefix
+	 * @param array $attributes Array of table attributes.
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 		$this->name = $this->prefix($attributes['NAME']);
 	}
 
 	/**
-	* XML Callback to process start elements. Elements currently
-	* processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements. Elements currently
+	 * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
+	 *
+	 * @access private
+	 */
 	function _tag_open( &$parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -321,10 +316,10 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( &$parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Table constraint
@@ -345,10 +340,10 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( &$parser, $tag ) {
 		$this->currentElement = '';
 
@@ -366,11 +361,11 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds an index to a table object
-	*
-	* @param array $attributes Index attributes
-	* @return object dbIndex object
-	*/
+	 * Adds an index to a table object
+	 *
+	 * @param array $attributes Index attributes
+	 * @return object dbIndex object
+	 */
 	function addIndex( $attributes ) {
 		$name = strtoupper( $attributes['NAME'] );
 		$this->indexes[$name] = new dbIndex( $this, $attributes );
@@ -378,11 +373,11 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds data to a table object
-	*
-	* @param array $attributes Data attributes
-	* @return object dbData object
-	*/
+	 * Adds data to a table object
+	 *
+	 * @param array $attributes Data attributes
+	 * @return object dbData object
+	 */
 	function addData( $attributes ) {
 		if( !isset( $this->data ) ) {
 			$this->data = new dbData( $this, $attributes );
@@ -391,34 +386,34 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds a field to a table object
-	*
-	* $name is the name of the table to which the field should be added.
-	* $type is an ADODB datadict field type. The following field types
-	* are supported as of ADODB 3.40:
-	* 	- C:  varchar
-	*	- X:  CLOB (character large object) or largest varchar size
-	*	   if CLOB is not supported
-	*	- C2: Multibyte varchar
-	*	- X2: Multibyte CLOB
-	*	- B:  BLOB (binary large object)
-	*	- D:  Date (some databases do not support this, and we return a datetime type)
-	*	- T:  Datetime or Timestamp
-	*	- L:  Integer field suitable for storing booleans (0 or 1)
-	*	- I:  Integer (mapped to I4)
-	*	- I1: 1-byte integer
-	*	- I2: 2-byte integer
-	*	- I4: 4-byte integer
-	*	- I8: 8-byte integer
-	*	- F:  Floating point number
-	*	- N:  Numeric or decimal number
-	*
-	* @param string $name Name of the table to which the field will be added.
-	* @param string $type	ADODB datadict field type.
-	* @param string $size	Field size
-	* @param array $opts	Field options array
-	* @return array Field specifier array
-	*/
+	 * Adds a field to a table object
+	 *
+	 * $name is the name of the table to which the field should be added.
+	 * $type is an ADODB datadict field type. The following field types
+	 * are supported as of ADODB 3.40:
+	 * 	- C:  varchar
+	 *	- X:  CLOB (character large object) or largest varchar size
+	 *	   if CLOB is not supported
+	 *	- C2: Multibyte varchar
+	 *	- X2: Multibyte CLOB
+	 *	- B:  BLOB (binary large object)
+	 *	- D:  Date (some databases do not support this, and we return a datetime type)
+	 *	- T:  Datetime or Timestamp
+	 *	- L:  Integer field suitable for storing booleans (0 or 1)
+	 *	- I:  Integer (mapped to I4)
+	 *	- I1: 1-byte integer
+	 *	- I2: 2-byte integer
+	 *	- I4: 4-byte integer
+	 *	- I8: 8-byte integer
+	 *	- F:  Floating point number
+	 *	- N:  Numeric or decimal number
+	 *
+	 * @param string $name Name of the table to which the field will be added.
+	 * @param string $type	ADODB datadict field type.
+	 * @param string $size	Field size
+	 * @param array $opts	Field options array
+	 * @return void
+	 */
 	function addField( $name, $type, $size = NULL, $opts = NULL ) {
 		$field_id = $this->FieldID( $name );
 
@@ -443,16 +438,16 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds a field option to the current field specifier
-	*
-	* This method adds a field option allowed by the ADOdb datadict
-	* and appends it to the given field.
-	*
-	* @param string $field	Field name
-	* @param string $opt ADOdb field option
-	* @param mixed $value Field option value
-	* @return array Field specifier array
-	*/
+	 * Adds a field option to the current field specifier
+	 *
+	 * This method adds a field option allowed by the ADOdb datadict
+	 * and appends it to the given field.
+	 *
+	 * @param string $field	Field name
+	 * @param string $opt ADOdb field option
+	 * @param mixed $value Field option value
+	 * @return void
+	 */
 	function addFieldOpt( $field, $opt, $value = NULL ) {
 		if( !isset( $value ) ) {
 			$this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
@@ -463,14 +458,14 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds an option to the table
-	*
-	* This method takes a comma-separated list of table-level options
-	* and appends them to the table object.
-	*
-	* @param string $opt Table option
-	* @return array Options
-	*/
+	 * Adds an option to the table
+	 *
+	 * This method takes a comma-separated list of table-level options
+	 * and appends them to the table object.
+	 *
+	 * @param string $opt Table option
+	 * @return array Options
+	 */
 	function addTableOpt( $opt ) {
 		if(isset($this->currentPlatform)) {
 			$this->opts[$this->parent->db->databaseType] = $opt;
@@ -480,11 +475,11 @@ class dbTable extends dbObject {
 
 
 	/**
-	* Generates the SQL that will create the table in the database
-	*
-	* @param object $xmls adoSchema object
-	* @return array Array containing table creation SQL
-	*/
+	 * Generates the SQL that will create the table in the database
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Array containing table creation SQL
+	 */
 	function create( &$xmls ) {
 		$sql = array();
 
@@ -592,8 +587,8 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Marks a field or table for destruction
-	*/
+	 * Marks a field or table for destruction
+	 */
 	function drop() {
 		if( isset( $this->current_field ) ) {
 			// Drop the current field
@@ -610,61 +605,61 @@ class dbTable extends dbObject {
 }
 
 /**
-* Creates an index object in ADOdb's datadict format
-*
-* This class stores information about a database index. As charactaristics
-* of the index are loaded from the external source, methods and properties
-* of this class are used to build up the index description in ADOdb's
-* datadict format.
-*
-* @package axmls
-* @access private
-*/
+ * Creates an index object in ADOdb's datadict format
+ *
+ * This class stores information about a database index. As charactaristics
+ * of the index are loaded from the external source, methods and properties
+ * of this class are used to build up the index description in ADOdb's
+ * datadict format.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbIndex extends dbObject {
 
 	/**
-	* @var string	Index name
-	*/
+	 * @var string	Index name
+	 */
 	var $name;
 
 	/**
-	* @var array	Index options: Index-level options
-	*/
+	 * @var array	Index options: Index-level options
+	 */
 	var $opts = array();
 
 	/**
-	* @var array	Indexed fields: Table columns included in this index
-	*/
+	 * @var array	Indexed fields: Table columns included in this index
+	 */
 	var $columns = array();
 
 	/**
-	* @var boolean Mark index for destruction
-	* @access private
-	*/
+	 * @var boolean Mark index for destruction
+	 * @access private
+	 */
 	var $drop = FALSE;
 
 	/**
-	* Initializes the new dbIndex object.
-	*
-	* @param object $parent Parent object
-	* @param array $attributes Attributes
-	*
-	* @internal
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * Initializes the new dbIndex object.
+	 *
+	 * @param object $parent Parent object
+	 * @param array $attributes Attributes
+	 *
+	 * @internal
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 
 		$this->name = $this->prefix ($attributes['NAME']);
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* Processes XML opening tags.
-	* Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * Processes XML opening tags.
+	 * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
+	 *
+	 * @access private
+	 */
 	function _tag_open( &$parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -686,12 +681,12 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* Processes XML cdata.
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * Processes XML cdata.
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( &$parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Index field name
@@ -704,10 +699,10 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( &$parser, $tag ) {
 		$this->currentElement = '';
 
@@ -719,11 +714,11 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Adds a field to the index
-	*
-	* @param string $name Field name
-	* @return string Field list
-	*/
+	 * Adds a field to the index
+	 *
+	 * @param string $name Field name
+	 * @return string Field list
+	 */
 	function addField( $name ) {
 		$this->columns[$this->FieldID( $name )] = $name;
 
@@ -732,11 +727,11 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Adds options to the index
-	*
-	* @param string $opt Comma-separated list of index options.
-	* @return string Option list
-	*/
+	 * Adds options to the index
+	 *
+	 * @param string $opt Comma-separated list of index options.
+	 * @return string Option list
+	 */
 	function addIndexOpt( $opt ) {
 		$this->opts[] = $opt;
 
@@ -745,11 +740,11 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Generates the SQL that will create the index in the database
-	*
-	* @param object $xmls adoSchema object
-	* @return array Array containing index creation SQL
-	*/
+	 * Generates the SQL that will create the index in the database
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Array containing index creation SQL
+	 */
 	function create( &$xmls ) {
 		if( $this->drop ) {
 			return NULL;
@@ -766,47 +761,50 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Marks an index for destruction
-	*/
+	 * Marks an index for destruction
+	 */
 	function drop() {
 		$this->drop = TRUE;
 	}
 }
 
 /**
-* Creates a data object in ADOdb's datadict format
-*
-* This class stores information about table data.
-*
-* @package axmls
-* @access private
-*/
+ * Creates a data object in ADOdb's datadict format
+ *
+ * This class stores information about table data.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbData extends dbObject {
 
 	var $data = array();
 
 	var $row;
 
+	/** @var string Field name */
+	var $current_field;
+
 	/**
-	* Initializes the new dbIndex object.
-	*
-	* @param object $parent Parent object
-	* @param array $attributes Attributes
-	*
-	* @internal
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * Initializes the new dbIndex object.
+	 *
+	 * @param object $parent Parent object
+	 * @param array $attributes Attributes
+	 *
+	 * @internal
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* Processes XML opening tags.
-	* Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * Processes XML opening tags.
+	 * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
+	 *
+	 * @access private
+	 */
 	function _tag_open( &$parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -823,12 +821,12 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* Processes XML cdata.
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * Processes XML cdata.
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( &$parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Index field name
@@ -841,10 +839,10 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( &$parser, $tag ) {
 		$this->currentElement = '';
 
@@ -856,11 +854,11 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* Adds a field to the index
-	*
-	* @param string $name Field name
-	* @return string Field list
-	*/
+	 * Adds a field to the index
+	 *
+	 * @param string $name Field name
+	 * @return string Field list
+	 */
 	function addField( $attributes ) {
 		if( isset( $attributes['NAME'] ) ) {
 			$name = $attributes['NAME'];
@@ -873,11 +871,11 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* Adds options to the index
-	*
-	* @param string $opt Comma-separated list of index options.
-	* @return string Option list
-	*/
+	 * Adds options to the index
+	 *
+	 * @param string $opt Comma-separated list of index options.
+	 * @return string Option list
+	 */
 	function addData( $cdata ) {
 		if( !isset( $this->data[$this->row] ) ) {
 			$this->data[$this->row] = array();
@@ -891,11 +889,11 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* Generates the SQL that will create the index in the database
-	*
-	* @param object $xmls adoSchema object
-	* @return array Array containing index creation SQL
-	*/
+	 * Generates the SQL that will create the index in the database
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Array containing index creation SQL
+	 */
 	function create( &$xmls ) {
 		$table = $xmls->dict->TableName($this->parent->name);
 		$table_field_count = count($this->parent->fields);
@@ -909,7 +907,8 @@ class dbData extends dbObject {
 			foreach( $row as $field_id => $field_data ) {
 				if( !array_key_exists( $field_id, $table_fields ) ) {
 					if( is_numeric( $field_id ) ) {
-						$field_id = reset( array_keys( $table_fields ) );
+						$keys = array_keys($table_fields);
+						$field_id = reset($keys);
 					} else {
 						continue;
 					}
@@ -961,40 +960,40 @@ class dbData extends dbObject {
 }
 
 /**
-* Creates the SQL to execute a list of provided SQL queries
-*
-* @package axmls
-* @access private
-*/
+ * Creates the SQL to execute a list of provided SQL queries
+ *
+ * @package axmls
+ * @access private
+ */
 class dbQuerySet extends dbObject {
 
 	/**
-	* @var array	List of SQL queries
-	*/
+	 * @var array	List of SQL queries
+	 */
 	var $queries = array();
 
 	/**
-	* @var string	String used to build of a query line by line
-	*/
+	 * @var string	String used to build of a query line by line
+	 */
 	var $query;
 
 	/**
-	* @var string	Query prefix key
-	*/
+	 * @var string	Query prefix key
+	 */
 	var $prefixKey = '';
 
 	/**
-	* @var boolean	Auto prefix enable (TRUE)
-	*/
+	 * @var boolean	Auto prefix enable (TRUE)
+	 */
 	var $prefixMethod = 'AUTO';
 
 	/**
-	* Initializes the query set.
-	*
-	* @param object $parent Parent object
-	* @param array $attributes Attributes
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * Initializes the query set.
+	 *
+	 * @param object $parent Parent object
+	 * @param array $attributes Attributes
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 
 		// Overrides the manual prefix key
@@ -1019,11 +1018,11 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* XML Callback to process start elements. Elements currently
-	* processed are: QUERY.
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements. Elements currently
+	 * processed are: QUERY.
+	 *
+	 * @access private
+	 */
 	function _tag_open( &$parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -1044,8 +1043,8 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*/
+	 * XML Callback to process CDATA elements
+	 */
 	function _tag_cdata( &$parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Line of queryset SQL data
@@ -1058,10 +1057,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( &$parser, $tag ) {
 		$this->currentElement = '';
 
@@ -1081,10 +1080,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Re-initializes the query.
-	*
-	* @return boolean TRUE
-	*/
+	 * Re-initializes the query.
+	 *
+	 * @return boolean TRUE
+	 */
 	function newQuery() {
 		$this->query = '';
 
@@ -1092,10 +1091,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Discards the existing query.
-	*
-	* @return boolean TRUE
-	*/
+	 * Discards the existing query.
+	 *
+	 * @return boolean TRUE
+	 */
 	function discardQuery() {
 		unset( $this->query );
 
@@ -1103,11 +1102,11 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Appends a line to a query that is being built line by line
-	*
-	* @param string $data Line of SQL data or NULL to initialize a new query
-	* @return string SQL query string.
-	*/
+	 * Appends a line to a query that is being built line by line
+	 *
+	 * @param string $data Line of SQL data or NULL to initialize a new query
+	 * @return string SQL query string.
+	 */
 	function buildQuery( $sql = NULL ) {
 		if( !isset( $this->query ) OR empty( $sql ) ) {
 			return FALSE;
@@ -1119,10 +1118,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Adds a completed query to the query list
-	*
-	* @return string	SQL of added query
-	*/
+	 * Adds a completed query to the query list
+	 *
+	 * @return string	SQL of added query
+	 */
 	function addQuery() {
 		if( !isset( $this->query ) ) {
 			return FALSE;
@@ -1136,11 +1135,11 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Creates and returns the current query set
-	*
-	* @param object $xmls adoSchema object
-	* @return array Query set
-	*/
+	 * Creates and returns the current query set
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Query set
+	 */
 	function create( &$xmls ) {
 		foreach( $this->queries as $id => $query ) {
 			switch( $this->prefixMethod ) {
@@ -1176,13 +1175,13 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Rebuilds the query with the prefix attached to any objects
-	*
-	* @param string $regex Regex used to add prefix
-	* @param string $query SQL query string
-	* @param string $prefix Prefix to be appended to tables, indices, etc.
-	* @return string Prefixed SQL query string.
-	*/
+	 * Rebuilds the query with the prefix attached to any objects
+	 *
+	 * @param string $regex Regex used to add prefix
+	 * @param string $query SQL query string
+	 * @param string $prefix Prefix to be appended to tables, indices, etc.
+	 * @return string Prefixed SQL query string.
+	 */
 	function prefixQuery( $regex, $query, $prefix = NULL ) {
 		if( !isset( $prefix ) ) {
 			return $query;
@@ -1212,98 +1211,101 @@ class dbQuerySet extends dbObject {
 }
 
 /**
-* Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
-*
-* This class is used to load and parse the XML file, to create an array of SQL statements
-* that can be used to build a database, and to build the database using the SQL array.
-*
-* @tutorial getting_started.pkg
-*
-* @author Richard Tango-Lowy & Dan Cech
-* @version $Revision: 1.12 $
-*
-* @package axmls
-*/
+ * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
+ *
+ * This class is used to load and parse the XML file, to create an array of SQL statements
+ * that can be used to build a database, and to build the database using the SQL array.
+ *
+ * @tutorial getting_started.pkg
+ *
+ * @author Richard Tango-Lowy & Dan Cech
+ * @version 1.12
+ *
+ * @package axmls
+ */
 class adoSchema {
 
 	/**
-	* @var array	Array containing SQL queries to generate all objects
-	* @access private
-	*/
+	 * @var array	Array containing SQL queries to generate all objects
+	 * @access private
+	 */
 	var $sqlArray;
 
 	/**
-	* @var object	ADOdb connection object
-	* @access private
-	*/
+	 * @var object	ADOdb connection object
+	 * @access private
+	 */
 	var $db;
 
 	/**
-	* @var object	ADOdb Data Dictionary
-	* @access private
-	*/
+	 * @var object	ADOdb Data Dictionary
+	 * @access private
+	 */
 	var $dict;
 
 	/**
-	* @var string Current XML element
-	* @access private
-	*/
+	 * @var string Current XML element
+	 * @access private
+	 */
 	var $currentElement = '';
 
 	/**
-	* @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
-	* @access private
-	*/
+	 * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
+	 * @access private
+	 */
 	var $upgrade = '';
 
 	/**
-	* @var string Optional object prefix
-	* @access private
-	*/
+	 * @var string Optional object prefix
+	 * @access private
+	 */
 	var $objectPrefix = '';
 
 	/**
-	* @var long	System debug
-	* @access private
-	*/
+	 * @var long	System debug
+	 * @access private
+	 */
 	var $debug;
 
 	/**
-	* @var string Regular expression to find schema version
-	* @access private
-	*/
+	 * @var string Regular expression to find schema version
+	 * @access private
+	 */
 	var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
 
 	/**
-	* @var string Current schema version
-	* @access private
-	*/
+	 * @var string Current schema version
+	 * @access private
+	 */
 	var $schemaVersion;
 
 	/**
-	* @var int	Success of last Schema execution
-	*/
+	 * @var int	Success of last Schema execution
+	 */
 	var $success;
 
 	/**
-	* @var bool	Execute SQL inline as it is generated
-	*/
+	 * @var bool	Execute SQL inline as it is generated
+	 */
 	var $executeInline;
 
 	/**
-	* @var bool	Continue SQL execution if errors occur
-	*/
+	 * @var bool	Continue SQL execution if errors occur
+	 */
 	var $continueOnError;
 
+	/** @var dbTable A table object. */
+	var $obj;
+
 	/**
-	* Creates an adoSchema object
-	*
-	* Creating an adoSchema object is the first step in processing an XML schema.
-	* The only parameter is an ADOdb database connection object, which must already
-	* have been created.
-	*
-	* @param object $db ADOdb database connection object.
-	*/
+	 * Creates an adoSchema object
+	 *
+	 * Creating an adoSchema object is the first step in processing an XML schema.
+	 * The only parameter is an ADOdb database connection object, which must already
+	 * have been created.
+	 *
+	 * @param object $db ADOdb database connection object.
+	 */
 	function __construct( $db ) {
 		$this->db = $db;
 		$this->debug = $this->db->debug;
@@ -1316,21 +1318,21 @@ class adoSchema {
 	}
 
 	/**
-	* Sets the method to be used for upgrading an existing database
-	*
-	* Use this method to specify how existing database objects should be upgraded.
-	* The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
-	* alter each database object directly, REPLACE attempts to rebuild each object
-	* from scratch, BEST attempts to determine the best upgrade method for each
-	* object, and NONE disables upgrading.
-	*
-	* This method is not yet used by AXMLS, but exists for backward compatibility.
-	* The ALTER method is automatically assumed when the adoSchema object is
-	* instantiated; other upgrade methods are not currently supported.
-	*
-	* @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
-	* @returns string Upgrade method used
-	*/
+	 * Sets the method to be used for upgrading an existing database
+	 *
+	 * Use this method to specify how existing database objects should be upgraded.
+	 * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
+	 * alter each database object directly, REPLACE attempts to rebuild each object
+	 * from scratch, BEST attempts to determine the best upgrade method for each
+	 * object, and NONE disables upgrading.
+	 *
+	 * This method is not yet used by AXMLS, but exists for backward compatibility.
+	 * The ALTER method is automatically assumed when the adoSchema object is
+	 * instantiated; other upgrade methods are not currently supported.
+	 *
+	 * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
+	 * @returns string Upgrade method used
+	 */
 	function SetUpgradeMethod( $method = '' ) {
 		if( !is_string( $method ) ) {
 			return FALSE;
@@ -1361,18 +1363,19 @@ class adoSchema {
 	}
 
 	/**
-	* Enables/disables inline SQL execution.
-	*
-	* Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
-	* AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
-	* is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
-	* to apply the schema to the database.
-	*
-	* @param bool $mode execute
-	* @return bool current execution mode
-	*
-	* @see ParseSchema(), ExecuteSchema()
-	*/
+	 * Enables/disables inline SQL execution.
+	 *
+	 * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
+	 * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
+	 * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
+	 * to apply the schema to the database.
+	 *
+	 * @param bool $mode execute
+	 * @return bool current execution mode
+	 *
+	 * @see ParseSchema()
+	 * @see ExecuteSchema()
+	 */
 	function ExecuteInline( $mode = NULL ) {
 		if( is_bool( $mode ) ) {
 			$this->executeInline = $mode;
@@ -1382,18 +1385,19 @@ class adoSchema {
 	}
 
 	/**
-	* Enables/disables SQL continue on error.
-	*
-	* Call this method to enable or disable continuation of SQL execution if an error occurs.
-	* If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
-	* If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
-	* of the schema will continue.
-	*
-	* @param bool $mode execute
-	* @return bool current continueOnError mode
-	*
-	* @see addSQL(), ExecuteSchema()
-	*/
+	 * Enables/disables SQL continue on error.
+	 *
+	 * Call this method to enable or disable continuation of SQL execution if an error occurs.
+	 * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
+	 * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
+	 * of the schema will continue.
+	 *
+	 * @param bool $mode execute
+	 * @return bool current continueOnError mode
+	 *
+	 * @see addSQL()
+	 * @see ExecuteSchema()
+	 */
 	function ContinueOnError( $mode = NULL ) {
 		if( is_bool( $mode ) ) {
 			$this->continueOnError = $mode;
@@ -1403,33 +1407,34 @@ class adoSchema {
 	}
 
 	/**
-	* Loads an XML schema from a file and converts it to SQL.
-	*
-	* Call this method to load the specified schema (see the DTD for the proper format) from
-	* the filesystem and generate the SQL necessary to create the database described.
-	* @see ParseSchemaString()
-	*
-	* @param string $file Name of XML schema file.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute
-	*/
+	 * Loads an XML schema from a file and converts it to SQL.
+	 *
+	 * Call this method to load the specified schema (see the DTD for the proper format) from
+	 * the filesystem and generate the SQL necessary to create the database described.
+	 * @see ParseSchemaString()
+	 *
+	 * @param string $file Name of XML schema file.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute
+	 */
 	function ParseSchema( $filename, $returnSchema = FALSE ) {
 		return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
 	}
 
 	/**
-	* Loads an XML schema from a file and converts it to SQL.
-	*
-	* Call this method to load the specified schema from a file (see the DTD for the proper format)
-	* and generate the SQL necessary to create the database described by the schema.
-	*
-	* @param string $file Name of XML schema file.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute.
-	*
-	* @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
-	* @see ParseSchema(), ParseSchemaString()
-	*/
+	 * Loads an XML schema from a file and converts it to SQL.
+	 *
+	 * Call this method to load the specified schema from a file (see the DTD for the proper format)
+	 * and generate the SQL necessary to create the database described by the schema.
+	 *
+	 * @param string $file Name of XML schema file.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute.
+	 *
+	 * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
+	 * @see ParseSchema()
+	 * @see ParseSchemaString()
+	 */
 	function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
 		// Open the file
 		if( !($fp = fopen( $filename, 'r' )) ) {
@@ -1472,16 +1477,16 @@ class adoSchema {
 	}
 
 	/**
-	* Converts an XML schema string to SQL.
-	*
-	* Call this method to parse a string containing an XML schema (see the DTD for the proper format)
-	* and generate the SQL necessary to create the database described by the schema.
-	* @see ParseSchema()
-	*
-	* @param string $xmlstring XML schema string.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute.
-	*/
+	 * Converts an XML schema string to SQL.
+	 *
+	 * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+	 * and generate the SQL necessary to create the database described by the schema.
+	 * @see ParseSchema()
+	 *
+	 * @param string $xmlstring XML schema string.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute.
+	 */
 	function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
 		if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
 			return FALSE;
@@ -1515,31 +1520,31 @@ class adoSchema {
 	}
 
 	/**
-	* Loads an XML schema from a file and converts it to uninstallation SQL.
-	*
-	* Call this method to load the specified schema (see the DTD for the proper format) from
-	* the filesystem and generate the SQL necessary to remove the database described.
-	* @see RemoveSchemaString()
-	*
-	* @param string $file Name of XML schema file.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute
-	*/
+	 * Loads an XML schema from a file and converts it to uninstallation SQL.
+	 *
+	 * Call this method to load the specified schema (see the DTD for the proper format) from
+	 * the filesystem and generate the SQL necessary to remove the database described.
+	 * @see RemoveSchemaString()
+	 *
+	 * @param string $file Name of XML schema file.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute
+	 */
 	function RemoveSchema( $filename, $returnSchema = FALSE ) {
 		return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
 	}
 
 	/**
-	* Converts an XML schema string to uninstallation SQL.
-	*
-	* Call this method to parse a string containing an XML schema (see the DTD for the proper format)
-	* and generate the SQL necessary to uninstall the database described by the schema.
-	* @see RemoveSchema()
-	*
-	* @param string $schema XML schema string.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute.
-	*/
+	 * Converts an XML schema string to uninstallation SQL.
+	 *
+	 * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+	 * and generate the SQL necessary to uninstall the database described by the schema.
+	 * @see RemoveSchema()
+	 *
+	 * @param string $schema XML schema string.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute.
+	 */
 	function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
 
 		// grab current version
@@ -1551,18 +1556,20 @@ class adoSchema {
 	}
 
 	/**
-	* Applies the current XML schema to the database (post execution).
-	*
-	* Call this method to apply the current schema (generally created by calling
-	* ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes,
-	* and executing other SQL specified in the schema) after parsing.
-	* @see ParseSchema(), ParseSchemaString(), ExecuteInline()
-	*
-	* @param array $sqlArray Array of SQL statements that will be applied rather than
-	*		the current schema.
-	* @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
-	* @returns integer 0 if failure, 1 if errors, 2 if successful.
-	*/
+	 * Applies the current XML schema to the database (post execution).
+	 *
+	 * Call this method to apply the current schema (generally created by calling
+	 * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes,
+	 * and executing other SQL specified in the schema) after parsing.
+	 * @see ParseSchema()
+	 * @see ParseSchemaString()
+	 * @see ExecuteInline()
+	 *
+	 * @param array $sqlArray Array of SQL statements that will be applied rather than
+	 *		the current schema.
+	 * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
+	 * @returns integer 0 if failure, 1 if errors, 2 if successful.
+	 */
 	function ExecuteSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
 		if( !is_bool( $continueOnErr ) ) {
 			$continueOnErr = $this->ContinueOnError();
@@ -1582,28 +1589,28 @@ class adoSchema {
 	}
 
 	/**
-	* Returns the current SQL array.
-	*
-	* Call this method to fetch the array of SQL queries resulting from
-	* ParseSchema() or ParseSchemaString().
-	*
-	* @param string $format Format: HTML, TEXT, or NONE (PHP array)
-	* @return array Array of SQL statements or FALSE if an error occurs
-	*/
+	 * Returns the current SQL array.
+	 *
+	 * Call this method to fetch the array of SQL queries resulting from
+	 * ParseSchema() or ParseSchemaString().
+	 *
+	 * @param string $format Format: HTML, TEXT, or NONE (PHP array)
+	 * @return array Array of SQL statements or FALSE if an error occurs
+	 */
 	function PrintSQL( $format = 'NONE' ) {
 		$sqlArray = null;
 		return $this->getSQL( $format, $sqlArray );
 	}
 
 	/**
-	* Saves the current SQL array to the local filesystem as a list of SQL queries.
-	*
-	* Call this method to save the array of SQL queries (generally resulting from a
-	* parsed XML schema) to the filesystem.
-	*
-	* @param string $filename Path and name where the file should be saved.
-	* @return boolean TRUE if save is successful, else FALSE.
-	*/
+	 * Saves the current SQL array to the local filesystem as a list of SQL queries.
+	 *
+	 * Call this method to save the array of SQL queries (generally resulting from a
+	 * parsed XML schema) to the filesystem.
+	 *
+	 * @param string $filename Path and name where the file should be saved.
+	 * @return boolean TRUE if save is successful, else FALSE.
+	 */
 	function SaveSQL( $filename = './schema.sql' ) {
 
 		if( !isset( $sqlArray ) ) {
@@ -1622,12 +1629,12 @@ class adoSchema {
 	}
 
 	/**
-	* Create an xml parser
-	*
-	* @return object PHP XML parser object
-	*
-	* @access private
-	*/
+	 * Create an xml parser
+	 *
+	 * @return object PHP XML parser object
+	 *
+	 * @access private
+	 */
 	function create_parser() {
 		// Create the parser
 		$xmlParser = xml_parser_create();
@@ -1641,10 +1648,10 @@ class adoSchema {
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * @access private
+	 */
 	function _tag_open( &$parser, $tag, $attributes ) {
 		switch( strtoupper( $tag ) ) {
 			case 'TABLE':
@@ -1664,39 +1671,39 @@ class adoSchema {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( &$parser, $cdata ) {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	* @internal
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 * @internal
+	 */
 	function _tag_close( &$parser, $tag ) {
 
 	}
 
 	/**
-	* Converts an XML schema string to the specified DTD version.
-	*
-	* Call this method to convert a string containing an XML schema to a different AXMLS
-	* DTD version. For instance, to convert a schema created for an pre-1.0 version for
-	* AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
-	* parameter is specified, the schema will be converted to the current DTD version.
-	* If the newFile parameter is provided, the converted schema will be written to the specified
-	* file.
-	* @see ConvertSchemaFile()
-	*
-	* @param string $schema String containing XML schema that will be converted.
-	* @param string $newVersion DTD version to convert to.
-	* @param string $newFile File name of (converted) output file.
-	* @return string Converted XML schema or FALSE if an error occurs.
-	*/
+	 * Converts an XML schema string to the specified DTD version.
+	 *
+	 * Call this method to convert a string containing an XML schema to a different AXMLS
+	 * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+	 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+	 * parameter is specified, the schema will be converted to the current DTD version.
+	 * If the newFile parameter is provided, the converted schema will be written to the specified
+	 * file.
+	 * @see ConvertSchemaFile()
+	 *
+	 * @param string $schema String containing XML schema that will be converted.
+	 * @param string $newVersion DTD version to convert to.
+	 * @param string $newFile File name of (converted) output file.
+	 * @return string Converted XML schema or FALSE if an error occurs.
+	 */
 	function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
 
 		// grab current version
@@ -1722,29 +1729,22 @@ class adoSchema {
 		return $result;
 	}
 
-	// compat for pre-4.3 - jlim
-	function _file_get_contents($path)
-	{
-		if (function_exists('file_get_contents')) return file_get_contents($path);
-		return join('',file($path));
-	}
-
-	/**
-	* Converts an XML schema file to the specified DTD version.
-	*
-	* Call this method to convert the specified XML schema file to a different AXMLS
-	* DTD version. For instance, to convert a schema created for an pre-1.0 version for
-	* AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
-	* parameter is specified, the schema will be converted to the current DTD version.
-	* If the newFile parameter is provided, the converted schema will be written to the specified
-	* file.
-	* @see ConvertSchemaString()
-	*
-	* @param string $filename Name of XML schema file that will be converted.
-	* @param string $newVersion DTD version to convert to.
-	* @param string $newFile File name of (converted) output file.
-	* @return string Converted XML schema or FALSE if an error occurs.
-	*/
+	/**
+	 * Converts an XML schema file to the specified DTD version.
+	 *
+	 * Call this method to convert the specified XML schema file to a different AXMLS
+	 * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+	 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+	 * parameter is specified, the schema will be converted to the current DTD version.
+	 * If the newFile parameter is provided, the converted schema will be written to the specified
+	 * file.
+	 * @see ConvertSchemaString()
+	 *
+	 * @param string $filename Name of XML schema file that will be converted.
+	 * @param string $newVersion DTD version to convert to.
+	 * @param string $newFile File name of (converted) output file.
+	 * @return string Converted XML schema or FALSE if an error occurs.
+	 */
 	function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
 
 		// grab current version
@@ -1757,7 +1757,7 @@ class adoSchema {
 		}
 
 		if( $version == $newVersion ) {
-			$result = _file_get_contents( $filename );
+			$result = file_get_contents( $filename );
 
 			// remove unicode BOM if present
 			if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
@@ -1796,7 +1796,7 @@ class adoSchema {
 					return FALSE;
 				}
 
-				$schema = _file_get_contents( $schema );
+				$schema = file_get_contents( $schema );
 				break;
 			case 'string':
 			default:
@@ -1807,14 +1807,14 @@ class adoSchema {
 
 		$arguments = array (
 			'/_xml' => $schema,
-			'/_xsl' => _file_get_contents( $xsl_file )
+			'/_xsl' => file_get_contents( $xsl_file )
 		);
 
 		// create an XSLT processor
 		$xh = xslt_create ();
 
 		// set error handler
-		xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
+		xslt_set_error_handler ($xh, array ($this, 'xslt_error_handler'));
 
 		// process the schema
 		$result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
@@ -1825,15 +1825,15 @@ class adoSchema {
 	}
 
 	/**
-	* Processes XSLT transformation errors
-	*
-	* @param object $parser XML parser object
-	* @param integer $errno Error number
-	* @param integer $level Error level
-	* @param array $fields Error information fields
-	*
-	* @access private
-	*/
+	 * Processes XSLT transformation errors
+	 *
+	 * @param object $parser XML parser object
+	 * @param integer $errno Error number
+	 * @param integer $level Error level
+	 * @param array $fields Error information fields
+	 *
+	 * @access private
+	 */
 	function xslt_error_handler( $parser, $errno, $level, $fields ) {
 		if( is_array( $fields ) ) {
 			$msg = array(
@@ -1878,14 +1878,14 @@ class adoSchema {
 	}
 
 	/**
-	* Returns the AXMLS Schema Version of the requested XML schema file.
-	*
-	* Call this method to obtain the AXMLS DTD version of the requested XML schema file.
-	* @see SchemaStringVersion()
-	*
-	* @param string $filename AXMLS schema file
-	* @return string Schema version number or FALSE on error
-	*/
+	 * Returns the AXMLS Schema Version of the requested XML schema file.
+	 *
+	 * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
+	 * @see SchemaStringVersion()
+	 *
+	 * @param string $filename AXMLS schema file
+	 * @return string Schema version number or FALSE on error
+	 */
 	function SchemaFileVersion( $filename ) {
 		// Open the file
 		if( !($fp = fopen( $filename, 'r' )) ) {
@@ -1904,14 +1904,14 @@ class adoSchema {
 	}
 
 	/**
-	* Returns the AXMLS Schema Version of the provided XML schema string.
-	*
-	* Call this method to obtain the AXMLS DTD version of the provided XML schema string.
-	* @see SchemaFileVersion()
-	*
-	* @param string $xmlstring XML schema string
-	* @return string Schema version number or FALSE on error
-	*/
+	 * Returns the AXMLS Schema Version of the provided XML schema string.
+	 *
+	 * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
+	 * @see SchemaFileVersion()
+	 *
+	 * @param string $xmlstring XML schema string
+	 * @return string Schema version number or FALSE on error
+	 */
 	function SchemaStringVersion( $xmlstring ) {
 		if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
 			return FALSE;
@@ -1925,15 +1925,15 @@ class adoSchema {
 	}
 
 	/**
-	* Extracts an XML schema from an existing database.
-	*
-	* Call this method to create an XML schema string from an existing database.
-	* If the data parameter is set to TRUE, AXMLS will include the data from the database
-	* in the schema.
-	*
-	* @param boolean $data Include data in schema dump
-	* @return string Generated XML schema
-	*/
+	 * Extracts an XML schema from an existing database.
+	 *
+	 * Call this method to create an XML schema string from an existing database.
+	 * If the data parameter is set to TRUE, AXMLS will include the data from the database
+	 * in the schema.
+	 *
+	 * @param boolean $data Include data in schema dump
+	 * @return string Generated XML schema
+	 */
 	function ExtractSchema( $data = FALSE ) {
 		$old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
 
@@ -2032,15 +2032,15 @@ class adoSchema {
 	}
 
 	/**
-	* Sets a prefix for database objects
-	*
-	* Call this method to set a standard prefix that will be prepended to all database tables
-	* and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
-	*
-	* @param string $prefix Prefix that will be prepended.
-	* @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
-	* @return boolean TRUE if successful, else FALSE
-	*/
+	 * Sets a prefix for database objects
+	 *
+	 * Call this method to set a standard prefix that will be prepended to all database tables
+	 * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
+	 *
+	 * @param string $prefix Prefix that will be prepended.
+	 * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
+	 * @return boolean TRUE if successful, else FALSE
+	 */
 	function SetPrefix( $prefix = '', $underscore = TRUE ) {
 		switch( TRUE ) {
 			// clear prefix
@@ -2067,13 +2067,13 @@ class adoSchema {
 	}
 
 	/**
-	* Returns an object name with the current prefix prepended.
-	*
-	* @param string	$name Name
-	* @return string	Prefixed name
-	*
-	* @access private
-	*/
+	 * Returns an object name with the current prefix prepended.
+	 *
+	 * @param string	$name Name
+	 * @return string	Prefixed name
+	 *
+	 * @access private
+	 */
 	function prefix( $name = '' ) {
 		// if prefix is set
 		if( !empty( $this->objectPrefix ) ) {
@@ -2087,13 +2087,13 @@ class adoSchema {
 	}
 
 	/**
-	* Checks if element references a specific platform
-	*
-	* @param string $platform Requested platform
-	* @returns boolean TRUE if platform check succeeds
-	*
-	* @access private
-	*/
+	 * Checks if element references a specific platform
+	 *
+	 * @param string $platform Requested platform
+	 * @returns boolean TRUE if platform check succeeds
+	 *
+	 * @access private
+	 */
 	function supportedPlatform( $platform = NULL ) {
 		$regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/';
 
@@ -2107,22 +2107,22 @@ class adoSchema {
 	}
 
 	/**
-	* Clears the array of generated SQL.
-	*
-	* @access private
-	*/
+	 * Clears the array of generated SQL.
+	 *
+	 * @access private
+	 */
 	function clearSQL() {
 		$this->sqlArray = array();
 	}
 
 	/**
-	* Adds SQL into the SQL array.
-	*
-	* @param mixed $sql SQL to Add
-	* @return boolean TRUE if successful, else FALSE.
-	*
-	* @access private
-	*/
+	 * Adds SQL into the SQL array.
+	 *
+	 * @param mixed $sql SQL to Add
+	 * @return boolean TRUE if successful, else FALSE.
+	 *
+	 * @access private
+	 */
 	function addSQL( $sql = NULL ) {
 		if( is_array( $sql ) ) {
 			foreach( $sql as $line ) {
@@ -2158,13 +2158,13 @@ class adoSchema {
 	}
 
 	/**
-	* Gets the SQL array in the specified format.
-	*
-	* @param string $format Format
-	* @return mixed SQL
-	*
-	* @access private
-	*/
+	 * Gets the SQL array in the specified format.
+	 *
+	 * @param string $format Format
+	 * @return mixed SQL
+	 *
+	 * @access private
+	 */
 	function getSQL( $format = NULL, $sqlArray = NULL ) {
 		if( !is_array( $sqlArray ) ) {
 			$sqlArray = $this->sqlArray;
@@ -2186,19 +2186,19 @@ class adoSchema {
 	}
 
 	/**
-	* Destroys an adoSchema object.
-	*
-	* Call this method to clean up after an adoSchema object that is no longer in use.
-	* @deprecated adoSchema now cleans up automatically.
-	*/
+	 * Destroys an adoSchema object.
+	 *
+	 * Call this method to clean up after an adoSchema object that is no longer in use.
+	 * @deprecated adoSchema now cleans up automatically.
+	 */
 	function Destroy() {}
 }
 
 /**
-* Message logging function
-*
-* @access private
-*/
+ * Message logging function
+ *
+ * @access private
+ */
 function logMsg( $msg, $title = NULL, $force = FALSE ) {
 	if( XMLS_DEBUG or $force ) {
 		echo '<pre>';
diff --git a/adodb-xmlschema03.inc.php b/adodb-xmlschema03.inc.php
index de1ea26..3c8bce5 100644
--- a/adodb-xmlschema03.inc.php
+++ b/adodb-xmlschema03.inc.php
@@ -26,74 +26,60 @@
  * @author Dan Cech
  */
 
-function _file_get_contents($file)
-{
- 	if (function_exists('file_get_contents')) return file_get_contents($file);
-
-	$f = fopen($file,'r');
-	if (!$f) return '';
-	$t = '';
-
-	while ($s = fread($f,100000)) $t .= $s;
-	fclose($f);
-	return $t;
-}
-
-
 /**
-* Debug on or off
-*/
+ * Debug on or off
+ */
 if( !defined( 'XMLS_DEBUG' ) ) {
 	define( 'XMLS_DEBUG', FALSE );
 }
 
 /**
-* Default prefix key
-*/
+ * Default prefix key
+ */
 if( !defined( 'XMLS_PREFIX' ) ) {
 	define( 'XMLS_PREFIX', '%%P' );
 }
 
 /**
-* Maximum length allowed for object prefix
-*/
+ * Maximum length allowed for object prefix
+ */
 if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
 	define( 'XMLS_PREFIX_MAXLEN', 10 );
 }
 
 /**
-* Execute SQL inline as it is generated
-*/
+ * Execute SQL inline as it is generated
+ */
 if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
 	define( 'XMLS_EXECUTE_INLINE', FALSE );
 }
 
 /**
-* Continue SQL Execution if an error occurs?
-*/
+ * Continue SQL Execution if an error occurs?
+ */
 if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
 	define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
 }
 
 /**
-* Current Schema Version
-*/
+ * Current Schema Version
+ */
 if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
 	define( 'XMLS_SCHEMA_VERSION', '0.3' );
 }
 
 /**
-* Default Schema Version.  Used for Schemas without an explicit version set.
-*/
+ * Default Schema Version.  Used for Schemas without an explicit version set.
+ */
 if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
 	define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
 }
 
 /**
-* How to handle data rows that already exist in a database during and upgrade.
-* Options are INSERT (attempts to insert duplicate rows), UPDATE (updates existing
-* rows) and IGNORE (ignores existing rows).
-*/
+ * How to handle data rows that already exist in a database during and upgrade.
+ * Options are INSERT (attempts to insert duplicate rows), UPDATE (updates existing
+ * rows) and IGNORE (ignores existing rows).
+ */
 if( !defined( 'XMLS_MODE_INSERT' ) ) {
 	define( 'XMLS_MODE_INSERT', 0 );
 }
@@ -108,69 +94,69 @@ if( !defined( 'XMLS_EXISTING_DATA' ) ) {
 }
 
 /**
-* Default Schema Version.  Used for Schemas without an explicit version set.
-*/
+ * Default Schema Version.  Used for Schemas without an explicit version set.
+ */
 if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
 	define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
 }
 
 /**
-* Include the main ADODB library
-*/
+ * Include the main ADODB library
+ */
 if( !defined( '_ADODB_LAYER' ) ) {
 	require( 'adodb.inc.php' );
 	require( 'adodb-datadict.inc.php' );
 }
 
 /**
-* Abstract DB Object. This class provides basic methods for database objects, such
-* as tables and indexes.
-*
-* @package axmls
-* @access private
-*/
+ * Abstract DB Object. This class provides basic methods for database objects, such
+ * as tables and indexes.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbObject {
 
 	/**
-	* var object Parent
-	*/
+	 * var object Parent
+	 */
 	var $parent;
 
 	/**
-	* var string current element
-	*/
+	 * var string current element
+	 */
 	var $currentElement;
 
 	/**
-	* NOP
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * NOP
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * @access private
+	 */
 	function _tag_open( $parser, $tag, $attributes ) {
 
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( $parser, $cdata ) {
 
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( $parser, $tag ) {
 
 	}
@@ -180,117 +166,119 @@ class dbObject {
 	}
 
 	/**
-	* Destroys the object
-	*/
+	 * Destroys the object
+	 */
 	function destroy() {
 	}
 
 	/**
-	* Checks whether the specified RDBMS is supported by the current
-	* database object or its ranking ancestor.
-	*
-	* @param string $platform RDBMS platform name (from ADODB platform list).
-	* @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
-	*/
+	 * Checks whether the specified RDBMS is supported by the current
+	 * database object or its ranking ancestor.
+	 *
+	 * @param string $platform RDBMS platform name (from ADODB platform list).
+	 * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
+	 */
 	function supportedPlatform( $platform = NULL ) {
 		return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
 	}
 
 	/**
-	* Returns the prefix set by the ranking ancestor of the database object.
-	*
-	* @param string $name Prefix string.
-	* @return string Prefix.
-	*/
+	 * Returns the prefix set by the ranking ancestor of the database object.
+	 *
+	 * @param string $name Prefix string.
+	 * @return string Prefix.
+	 */
 	function prefix( $name = '' ) {
 		return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
 	}
 
 	/**
-	* Extracts a field ID from the specified field.
-	*
-	* @param string $field Field.
-	* @return string Field ID.
-	*/
+	 * Extracts a field ID from the specified field.
+	 *
+	 * @param string $field Field.
+	 * @return string Field ID.
+	 */
 	function fieldID( $field ) {
 		return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
 	}
 }
 
 /**
-* Creates a table object in ADOdb's datadict format
-*
-* This class stores information about a database table. As charactaristics
-* of the table are loaded from the external source, methods and properties
-* of this class are used to build up the table description in ADOdb's
-* datadict format.
-*
-* @package axmls
-* @access private
-*/
+ * Creates a table object in ADOdb's datadict format
+ *
+ * This class stores information about a database table. As charactaristics
+ * of the table are loaded from the external source, methods and properties
+ * of this class are used to build up the table description in ADOdb's
+ * datadict format.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbTable extends dbObject {
 
 	/**
-	* @var string Table name
-	*/
+	 * @var string Table name
+	 */
 	var $name;
 
 	/**
-	* @var array Field specifier: Meta-information about each field
-	*/
+	 * @var array Field specifier: Meta-information about each field
+	 */
 	var $fields = array();
 
 	/**
-	* @var array List of table indexes.
-	*/
+	 * @var array List of table indexes.
+	 */
 	var $indexes = array();
 
 	/**
-	* @var array Table options: Table-level options
-	*/
+	 * @var array Table options: Table-level options
+	 */
 	var $opts = array();
 
 	/**
-	* @var string Field index: Keeps track of which field is currently being processed
-	*/
+	 * @var string Field index: Keeps track of which field is currently being processed
+	 */
 	var $current_field;
 
 	/**
-	* @var boolean Mark table for destruction
-	* @access private
-	*/
+	 * @var boolean Mark table for destruction
+	 * @access private
+	 */
 	var $drop_table;
 
 	/**
-	* @var boolean Mark field for destruction (not yet implemented)
-	* @access private
-	*/
+	 * @var boolean Mark field for destruction (not yet implemented)
+	 * @access private
+	 */
 	var $drop_field = array();
 
 	/**
-	* @var array Platform-specific options
-	* @access private
-	*/
+	 * @var array Platform-specific options
+	 * @access private
+	 */
 	var $currentPlatform = true;
 
+	/** @var dbData Stores information about table data. */
+	var $data;
 
 	/**
-	* Iniitializes a new table object.
-	*
-	* @param string $prefix DB Object prefix
-	* @param array $attributes Array of table attributes.
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * Iniitializes a new table object.
+	 *
+	 * @param string $prefix DB Object prefix
+	 * @param array $attributes Array of table attributes.
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 		$this->name = $this->prefix($attributes['NAME']);
 	}
 
 	/**
-	* XML Callback to process start elements. Elements currently
-	* processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements. Elements currently
+	 * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
+	 *
+	 * @access private
+	 */
 	function _tag_open( $parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -349,10 +337,10 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( $parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Table or field comment
@@ -385,10 +373,10 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( $parser, $tag ) {
 		$this->currentElement = '';
 
@@ -411,11 +399,11 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds an index to a table object
-	*
-	* @param array $attributes Index attributes
-	* @return object dbIndex object
-	*/
+	 * Adds an index to a table object
+	 *
+	 * @param array $attributes Index attributes
+	 * @return object dbIndex object
+	 */
 	function addIndex( $attributes ) {
 		$name = strtoupper( $attributes['NAME'] );
 		$this->indexes[$name] = new dbIndex( $this, $attributes );
@@ -423,11 +411,11 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds data to a table object
-	*
-	* @param array $attributes Data attributes
-	* @return object dbData object
-	*/
+	 * Adds data to a table object
+	 *
+	 * @param array $attributes Data attributes
+	 * @return object dbData object
+	 */
 	function addData( $attributes ) {
 		if( !isset( $this->data ) ) {
 			$this->data = new dbData( $this, $attributes );
@@ -436,34 +424,34 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds a field to a table object
-	*
-	* $name is the name of the table to which the field should be added.
-	* $type is an ADODB datadict field type. The following field types
-	* are supported as of ADODB 3.40:
-	* 	- C:  varchar
-	*	- X:  CLOB (character large object) or largest varchar size
-	*	   if CLOB is not supported
-	*	- C2: Multibyte varchar
-	*	- X2: Multibyte CLOB
-	*	- B:  BLOB (binary large object)
-	*	- D:  Date (some databases do not support this, and we return a datetime type)
-	*	- T:  Datetime or Timestamp
-	*	- L:  Integer field suitable for storing booleans (0 or 1)
-	*	- I:  Integer (mapped to I4)
-	*	- I1: 1-byte integer
-	*	- I2: 2-byte integer
-	*	- I4: 4-byte integer
-	*	- I8: 8-byte integer
-	*	- F:  Floating point number
-	*	- N:  Numeric or decimal number
-	*
-	* @param string $name Name of the table to which the field will be added.
-	* @param string $type	ADODB datadict field type.
-	* @param string $size	Field size
-	* @param array $opts	Field options array
-	* @return array Field specifier array
-	*/
+	 * Adds a field to a table object
+	 *
+	 * $name is the name of the table to which the field should be added.
+	 * $type is an ADODB datadict field type. The following field types
+	 * are supported as of ADODB 3.40:
+	 * 	- C:  varchar
+	 *	- X:  CLOB (character large object) or largest varchar size
+	 *	   if CLOB is not supported
+	 *	- C2: Multibyte varchar
+	 *	- X2: Multibyte CLOB
+	 *	- B:  BLOB (binary large object)
+	 *	- D:  Date (some databases do not support this, and we return a datetime type)
+	 *	- T:  Datetime or Timestamp
+	 *	- L:  Integer field suitable for storing booleans (0 or 1)
+	 *	- I:  Integer (mapped to I4)
+	 *	- I1: 1-byte integer
+	 *	- I2: 2-byte integer
+	 *	- I4: 4-byte integer
+	 *	- I8: 8-byte integer
+	 *	- F:  Floating point number
+	 *	- N:  Numeric or decimal number
+	 *
+	 * @param string $name Name of the table to which the field will be added.
+	 * @param string $type	ADODB datadict field type.
+	 * @param string $size	Field size
+	 * @param array $opts	Field options array
+	 * @return void
+	 */
 	function addField( $name, $type, $size = NULL, $opts = NULL ) {
 		$field_id = $this->fieldID( $name );
 
@@ -490,16 +478,16 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds a field option to the current field specifier
-	*
-	* This method adds a field option allowed by the ADOdb datadict
-	* and appends it to the given field.
-	*
-	* @param string $field	Field name
-	* @param string $opt ADOdb field option
-	* @param mixed $value Field option value
-	* @return array Field specifier array
-	*/
+	 * Adds a field option to the current field specifier
+	 *
+	 * This method adds a field option allowed by the ADOdb datadict
+	 * and appends it to the given field.
+	 *
+	 * @param string $field	Field name
+	 * @param string $opt ADOdb field option
+	 * @param mixed $value Field option value
+	 * @return void
+	 */
 	function addFieldOpt( $field, $opt, $value = NULL ) {
 		if( $this->currentPlatform ) {
 		if( !isset( $value ) ) {
@@ -512,14 +500,14 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Adds an option to the table
-	*
-	* This method takes a comma-separated list of table-level options
-	* and appends them to the table object.
-	*
-	* @param string $opt Table option
-	* @return array Options
-	*/
+	 * Adds an option to the table
+	 *
+	 * This method takes a comma-separated list of table-level options
+	 * and appends them to the table object.
+	 *
+	 * @param string $opt Table option
+	 * @return array Options
+	 */
 	function addTableOpt( $opt ) {
 		if(isset($this->currentPlatform)) {
 			$this->opts[$this->parent->db->dataProvider] = $opt;
@@ -533,11 +521,11 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Generates the SQL that will create the table in the database
-	*
-	* @param object $xmls adoSchema object
-	* @return array Array containing table creation SQL
-	*/
+	 * Generates the SQL that will create the table in the database
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Array containing table creation SQL
+	 */
 	function create( &$xmls ) {
 		$sql = array();
 
@@ -599,8 +587,11 @@ class dbTable extends dbObject {
 					// Option has an argument.
 					if( is_array( $opt ) ) {
 						$key = key( $opt );
-						$value = $opt[key( $opt )];
-						@$fldarray[$field_id][$key] .= $value;
+						$value = $opt[$key];
+						if(!isset($fldarray[$field_id][$key])) {
+							$fldarray[$field_id][$key] = "";
+						}
+						$fldarray[$field_id][$key] .= $value;
 					// Option doesn't have arguments
 					} else {
 						$fldarray[$field_id][$opt] = $opt;
@@ -645,8 +636,8 @@ class dbTable extends dbObject {
 	}
 
 	/**
-	* Marks a field or table for destruction
-	*/
+	 * Marks a field or table for destruction
+	 */
 	function drop() {
 		if( isset( $this->current_field ) ) {
 			// Drop the current field
@@ -663,61 +654,61 @@ class dbTable extends dbObject {
 }
 
 /**
-* Creates an index object in ADOdb's datadict format
-*
-* This class stores information about a database index. As charactaristics
-* of the index are loaded from the external source, methods and properties
-* of this class are used to build up the index description in ADOdb's
-* datadict format.
-*
-* @package axmls
-* @access private
-*/
+ * Creates an index object in ADOdb's datadict format
+ *
+ * This class stores information about a database index. As charactaristics
+ * of the index are loaded from the external source, methods and properties
+ * of this class are used to build up the index description in ADOdb's
+ * datadict format.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbIndex extends dbObject {
 
 	/**
-	* @var string	Index name
-	*/
+	 * @var string	Index name
+	 */
 	var $name;
 
 	/**
-	* @var array	Index options: Index-level options
-	*/
+	 * @var array	Index options: Index-level options
+	 */
 	var $opts = array();
 
 	/**
-	* @var array	Indexed fields: Table columns included in this index
-	*/
+	 * @var array	Indexed fields: Table columns included in this index
+	 */
 	var $columns = array();
 
 	/**
-	* @var boolean Mark index for destruction
-	* @access private
-	*/
+	 * @var boolean Mark index for destruction
+	 * @access private
+	 */
 	var $drop = FALSE;
 
 	/**
-	* Initializes the new dbIndex object.
-	*
-	* @param object $parent Parent object
-	* @param array $attributes Attributes
-	*
-	* @internal
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * Initializes the new dbIndex object.
+	 *
+	 * @param object $parent Parent object
+	 * @param array $attributes Attributes
+	 *
+	 * @internal
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 
 		$this->name = $this->prefix ($attributes['NAME']);
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* Processes XML opening tags.
-	* Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * Processes XML opening tags.
+	 * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
+	 *
+	 * @access private
+	 */
 	function _tag_open( $parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -739,12 +730,12 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* Processes XML cdata.
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * Processes XML cdata.
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( $parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Index field name
@@ -757,10 +748,10 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( $parser, $tag ) {
 		$this->currentElement = '';
 
@@ -772,11 +763,11 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Adds a field to the index
-	*
-	* @param string $name Field name
-	* @return string Field list
-	*/
+	 * Adds a field to the index
+	 *
+	 * @param string $name Field name
+	 * @return string[] Field list
+	 */
 	function addField( $name ) {
 		$this->columns[$this->fieldID( $name )] = $name;
 
@@ -785,11 +776,11 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Adds options to the index
-	*
-	* @param string $opt Comma-separated list of index options.
-	* @return string Option list
-	*/
+	 * Adds options to the index
+	 *
+	 * @param string $opt Comma-separated list of index options.
+	 * @return string[] Option list
+	 */
 	function addIndexOpt( $opt ) {
 		$this->opts[] = $opt;
 
@@ -798,11 +789,11 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Generates the SQL that will create the index in the database
-	*
-	* @param object $xmls adoSchema object
-	* @return array Array containing index creation SQL
-	*/
+	 * Generates the SQL that will create the index in the database
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Array containing index creation SQL
+	 */
 	function create( &$xmls ) {
 		if( $this->drop ) {
 			return NULL;
@@ -819,48 +810,51 @@ class dbIndex extends dbObject {
 	}
 
 	/**
-	* Marks an index for destruction
-	*/
+	 * Marks an index for destruction
+	 */
 	function drop() {
 		$this->drop = TRUE;
 	}
 }
 
 /**
-* Creates a data object in ADOdb's datadict format
-*
-* This class stores information about table data, and is called
-* when we need to load field data into a table.
-*
-* @package axmls
-* @access private
-*/
+ * Creates a data object in ADOdb's datadict format
+ *
+ * This class stores information about table data, and is called
+ * when we need to load field data into a table.
+ *
+ * @package axmls
+ * @access private
+ */
 class dbData extends dbObject {
 
 	var $data = array();
 
 	var $row;
 
+	/** @var string Field name */
+	var $current_field;
+
 	/**
-	* Initializes the new dbData object.
-	*
-	* @param object $parent Parent object
-	* @param array $attributes Attributes
-	*
-	* @internal
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * Initializes the new dbData object.
+	 *
+	 * @param object $parent Parent object
+	 * @param array $attributes Attributes
+	 *
+	 * @internal
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* Processes XML opening tags.
-	* Elements currently processed are: ROW and F (field).
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * Processes XML opening tags.
+	 * Elements currently processed are: ROW and F (field).
+	 *
+	 * @access private
+	 */
 	function _tag_open( $parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -877,12 +871,12 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* Processes XML cdata.
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * Processes XML cdata.
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( $parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Index field name
@@ -895,10 +889,10 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( $parser, $tag ) {
 		$this->currentElement = '';
 
@@ -910,11 +904,11 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* Adds a field to the insert
-	*
-	* @param string $name Field name
-	* @return string Field list
-	*/
+	 * Adds a field to the insert
+	 *
+	 * @param string $name Field name
+	 * @return string Field list
+	 */
 	function addField( $attributes ) {
 		// check we're in a valid row
 		if( !isset( $this->row ) || !isset( $this->data[$this->row] ) ) {
@@ -935,11 +929,11 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* Adds options to the index
-	*
-	* @param string $opt Comma-separated list of index options.
-	* @return string Option list
-	*/
+	 * Adds data.
+	 *
+	 * @param string $cdata Data to add
+	 * @return void
+	 */
 	function addData( $cdata ) {
 		// check we're in a valid field
 		if ( isset( $this->data[$this->row][$this->current_field] ) ) {
@@ -949,11 +943,11 @@ class dbData extends dbObject {
 	}
 
 	/**
-	* Generates the SQL that will add/update the data in the database
-	*
-	* @param object $xmls adoSchema object
-	* @return array Array containing index creation SQL
-	*/
+	 * Generates the SQL that will add/update the data in the database
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Array containing index creation SQL
+	 */
 	function create( &$xmls ) {
 		$table = $xmls->dict->tableName($this->parent->name);
 		$table_field_count = count($this->parent->fields);
@@ -976,7 +970,8 @@ class dbData extends dbObject {
 			foreach( $row as $field_id => $field_data ) {
 				if( !array_key_exists( $field_id, $table_fields ) ) {
 					if( is_numeric( $field_id ) ) {
-						$field_id = reset( array_keys( $table_fields ) );
+						$keys = array_keys($table_fields);
+						$field_id = reset($keys);
 					} else {
 						continue;
 					}
@@ -1072,40 +1067,40 @@ class dbData extends dbObject {
 }
 
 /**
-* Creates the SQL to execute a list of provided SQL queries
-*
-* @package axmls
-* @access private
-*/
+ * Creates the SQL to execute a list of provided SQL queries
+ *
+ * @package axmls
+ * @access private
+ */
 class dbQuerySet extends dbObject {
 
 	/**
-	* @var array	List of SQL queries
-	*/
+	 * @var array	List of SQL queries
+	 */
 	var $queries = array();
 
 	/**
-	* @var string	String used to build of a query line by line
-	*/
+	 * @var string	String used to build of a query line by line
+	 */
 	var $query;
 
 	/**
-	* @var string	Query prefix key
-	*/
+	 * @var string	Query prefix key
+	 */
 	var $prefixKey = '';
 
 	/**
-	* @var boolean	Auto prefix enable (TRUE)
-	*/
+	 * @var boolean	Auto prefix enable (TRUE)
+	 */
 	var $prefixMethod = 'AUTO';
 
 	/**
-	* Initializes the query set.
-	*
-	* @param object $parent Parent object
-	* @param array $attributes Attributes
-	*/
-	function __construct( &$parent, $attributes = NULL ) {
+	 * Initializes the query set.
+	 *
+	 * @param object $parent Parent object
+	 * @param array $attributes Attributes
+	 */
+	function __construct( $parent, $attributes = NULL ) {
 		$this->parent = $parent;
 
 		// Overrides the manual prefix key
@@ -1130,11 +1125,11 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* XML Callback to process start elements. Elements currently
-	* processed are: QUERY.
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements. Elements currently
+	 * processed are: QUERY.
+	 *
+	 * @access private
+	 */
 	function _tag_open( $parser, $tag, $attributes ) {
 		$this->currentElement = strtoupper( $tag );
 
@@ -1155,8 +1150,8 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*/
+	 * XML Callback to process CDATA elements
+	 */
 	function _tag_cdata( $parser, $cdata ) {
 		switch( $this->currentElement ) {
 			// Line of queryset SQL data
@@ -1169,10 +1164,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 */
 	function _tag_close( $parser, $tag ) {
 		$this->currentElement = '';
 
@@ -1192,10 +1187,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Re-initializes the query.
-	*
-	* @return boolean TRUE
-	*/
+	 * Re-initializes the query.
+	 *
+	 * @return boolean TRUE
+	 */
 	function newQuery() {
 		$this->query = '';
 
@@ -1203,10 +1198,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Discards the existing query.
-	*
-	* @return boolean TRUE
-	*/
+	 * Discards the existing query.
+	 *
+	 * @return boolean TRUE
+	 */
 	function discardQuery() {
 		unset( $this->query );
 
@@ -1214,11 +1209,11 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Appends a line to a query that is being built line by line
-	*
-	* @param string $data Line of SQL data or NULL to initialize a new query
-	* @return string SQL query string.
-	*/
+	 * Appends a line to a query that is being built line by line
+	 *
+	 * @param string $data Line of SQL data or NULL to initialize a new query
+	 * @return string SQL query string.
+	 */
 	function buildQuery( $sql = NULL ) {
 		if( !isset( $this->query ) OR empty( $sql ) ) {
 			return FALSE;
@@ -1230,10 +1225,10 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Adds a completed query to the query list
-	*
-	* @return string	SQL of added query
-	*/
+	 * Adds a completed query to the query list
+	 *
+	 * @return string	SQL of added query
+	 */
 	function addQuery() {
 		if( !isset( $this->query ) ) {
 			return FALSE;
@@ -1247,11 +1242,11 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Creates and returns the current query set
-	*
-	* @param object $xmls adoSchema object
-	* @return array Query set
-	*/
+	 * Creates and returns the current query set
+	 *
+	 * @param object $xmls adoSchema object
+	 * @return array Query set
+	 */
 	function create( &$xmls ) {
 		foreach( $this->queries as $id => $query ) {
 			switch( $this->prefixMethod ) {
@@ -1287,13 +1282,13 @@ class dbQuerySet extends dbObject {
 	}
 
 	/**
-	* Rebuilds the query with the prefix attached to any objects
-	*
-	* @param string $regex Regex used to add prefix
-	* @param string $query SQL query string
-	* @param string $prefix Prefix to be appended to tables, indices, etc.
-	* @return string Prefixed SQL query string.
-	*/
+	 * Rebuilds the query with the prefix attached to any objects
+	 *
+	 * @param string $regex Regex used to add prefix
+	 * @param string $query SQL query string
+	 * @param string $prefix Prefix to be appended to tables, indices, etc.
+	 * @return string Prefixed SQL query string.
+	 */
 	function prefixQuery( $regex, $query, $prefix = NULL ) {
 		if( !isset( $prefix ) ) {
 			return $query;
@@ -1323,103 +1318,106 @@ class dbQuerySet extends dbObject {
 }
 
 /**
-* Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
-*
-* This class is used to load and parse the XML file, to create an array of SQL statements
-* that can be used to build a database, and to build the database using the SQL array.
-*
-* @tutorial getting_started.pkg
-*
-* @author Richard Tango-Lowy & Dan Cech
-* @version $Revision: 1.62 $
-*
-* @package axmls
-*/
+ * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
+ *
+ * This class is used to load and parse the XML file, to create an array of SQL statements
+ * that can be used to build a database, and to build the database using the SQL array.
+ *
+ * @tutorial getting_started.pkg
+ *
+ * @author Richard Tango-Lowy & Dan Cech
+ * @version 1.62
+ *
+ * @package axmls
+ */
 class adoSchema {
 
 	/**
-	* @var array	Array containing SQL queries to generate all objects
-	* @access private
-	*/
+	 * @var array	Array containing SQL queries to generate all objects
+	 * @access private
+	 */
 	var $sqlArray;
 
 	/**
-	* @var object	ADOdb connection object
-	* @access private
-	*/
+	 * @var object	ADOdb connection object
+	 * @access private
+	 */
 	var $db;
 
 	/**
-	* @var object	ADOdb Data Dictionary
-	* @access private
-	*/
+	 * @var object	ADOdb Data Dictionary
+	 * @access private
+	 */
 	var $dict;
 
 	/**
-	* @var string Current XML element
-	* @access private
-	*/
+	 * @var string Current XML element
+	 * @access private
+	 */
 	var $currentElement = '';
 
 	/**
-	* @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
-	* @access private
-	*/
+	 * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
+	 * @access private
+	 */
 	var $upgrade = '';
 
 	/**
-	* @var string Optional object prefix
-	* @access private
-	*/
+	 * @var string Optional object prefix
+	 * @access private
+	 */
 	var $objectPrefix = '';
 
 	/**
-	* @var long	System debug
-	* @access private
-	*/
+	 * @var long	System debug
+	 * @access private
+	 */
 	var $debug;
 
 	/**
-	* @var string Regular expression to find schema version
-	* @access private
-	*/
+	 * @var string Regular expression to find schema version
+	 * @access private
+	 */
 	var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
 
 	/**
-	* @var string Current schema version
-	* @access private
-	*/
+	 * @var string Current schema version
+	 * @access private
+	 */
 	var $schemaVersion;
 
 	/**
-	* @var int	Success of last Schema execution
-	*/
+	 * @var int	Success of last Schema execution
+	 */
 	var $success;
 
 	/**
-	* @var bool	Execute SQL inline as it is generated
-	*/
+	 * @var bool	Execute SQL inline as it is generated
+	 */
 	var $executeInline;
 
 	/**
-	* @var bool	Continue SQL execution if errors occur
-	*/
+	 * @var bool	Continue SQL execution if errors occur
+	 */
 	var $continueOnError;
 
 	/**
-	* @var int	How to handle existing data rows (insert, update, or ignore)
-	*/
+	 * @var int	How to handle existing data rows (insert, update, or ignore)
+	 */
 	var $existingData;
 
+	/**  @var dbTable A table object. */
+	var $obj;
+
 	/**
-	* Creates an adoSchema object
-	*
-	* Creating an adoSchema object is the first step in processing an XML schema.
-	* The only parameter is an ADOdb database connection object, which must already
-	* have been created.
-	*
-	* @param object $db ADOdb database connection object.
-	*/
+	 * Creates an adoSchema object
+	 *
+	 * Creating an adoSchema object is the first step in processing an XML schema.
+	 * The only parameter is an ADOdb database connection object, which must already
+	 * have been created.
+	 *
+	 * @param object $db ADOdb database connection object.
+	 */
 	function __construct( $db ) {
 		$this->db = $db;
 		$this->debug = $this->db->debug;
@@ -1433,21 +1431,21 @@ class adoSchema {
 	}
 
 	/**
-	* Sets the method to be used for upgrading an existing database
-	*
-	* Use this method to specify how existing database objects should be upgraded.
-	* The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
-	* alter each database object directly, REPLACE attempts to rebuild each object
-	* from scratch, BEST attempts to determine the best upgrade method for each
-	* object, and NONE disables upgrading.
-	*
-	* This method is not yet used by AXMLS, but exists for backward compatibility.
-	* The ALTER method is automatically assumed when the adoSchema object is
-	* instantiated; other upgrade methods are not currently supported.
-	*
-	* @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
-	* @returns string Upgrade method used
-	*/
+	 * Sets the method to be used for upgrading an existing database
+	 *
+	 * Use this method to specify how existing database objects should be upgraded.
+	 * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
+	 * alter each database object directly, REPLACE attempts to rebuild each object
+	 * from scratch, BEST attempts to determine the best upgrade method for each
+	 * object, and NONE disables upgrading.
+	 *
+	 * This method is not yet used by AXMLS, but exists for backward compatibility.
+	 * The ALTER method is automatically assumed when the adoSchema object is
+	 * instantiated; other upgrade methods are not currently supported.
+	 *
+	 * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
+	 * @returns string Upgrade method used
+	 */
 	function setUpgradeMethod( $method = '' ) {
 		if( !is_string( $method ) ) {
 			return FALSE;
@@ -1478,24 +1476,24 @@ class adoSchema {
 	}
 
 	/**
-	* Specifies how to handle existing data row when there is a unique key conflict.
-	*
-	* The existingData setting specifies how the parser should handle existing rows
-	* when a unique key violation occurs during the insert. This can happen when inserting
-	* data into an existing table with one or more primary keys or unique indexes.
-	* The existingData method takes one of three options: XMLS_MODE_INSERT attempts
-	* to always insert the data as a new row. In the event of a unique key violation,
-	* the database will generate an error.  XMLS_MODE_UPDATE attempts to update the
-	* any existing rows with the new data based upon primary or unique key fields in
-	* the schema. If the data row in the schema specifies no unique fields, the row
-	* data will be inserted as a new row. XMLS_MODE_IGNORE specifies that any data rows
-	* that would result in a unique key violation be ignored; no inserts or updates will
-	* take place. For backward compatibility, the default setting is XMLS_MODE_INSERT,
-	* but XMLS_MODE_UPDATE will generally be the most appropriate setting.
-	*
-	* @param int $mode XMLS_MODE_INSERT, XMLS_MODE_UPDATE, or XMLS_MODE_IGNORE
-	* @return int current mode
-	*/
+	 * Specifies how to handle existing data row when there is a unique key conflict.
+	 *
+	 * The existingData setting specifies how the parser should handle existing rows
+	 * when a unique key violation occurs during the insert. This can happen when inserting
+	 * data into an existing table with one or more primary keys or unique indexes.
+	 * The existingData method takes one of three options: XMLS_MODE_INSERT attempts
+	 * to always insert the data as a new row. In the event of a unique key violation,
+	 * the database will generate an error.  XMLS_MODE_UPDATE attempts to update the
+	 * any existing rows with the new data based upon primary or unique key fields in
+	 * the schema. If the data row in the schema specifies no unique fields, the row
+	 * data will be inserted as a new row. XMLS_MODE_IGNORE specifies that any data rows
+	 * that would result in a unique key violation be ignored; no inserts or updates will
+	 * take place. For backward compatibility, the default setting is XMLS_MODE_INSERT,
+	 * but XMLS_MODE_UPDATE will generally be the most appropriate setting.
+	 *
+	 * @param int $mode XMLS_MODE_INSERT, XMLS_MODE_UPDATE, or XMLS_MODE_IGNORE
+	 * @return int current mode
+	 */
 	function existingData( $mode = NULL ) {
 		if( is_int( $mode ) ) {
 			switch( $mode ) {
@@ -1519,18 +1517,19 @@ class adoSchema {
 	}
 
 	/**
-	* Enables/disables inline SQL execution.
-	*
-	* Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
-	* AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
-	* is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
-	* to apply the schema to the database.
-	*
-	* @param bool $mode execute
-	* @return bool current execution mode
-	*
-	* @see ParseSchema(), ExecuteSchema()
-	*/
+	 * Enables/disables inline SQL execution.
+	 *
+	 * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
+	 * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
+	 * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
+	 * to apply the schema to the database.
+	 *
+	 * @param bool $mode execute
+	 * @return bool current execution mode
+	 *
+	 * @see ParseSchema()
+	 * @see ExecuteSchema()
+	 */
 	function executeInline( $mode = NULL ) {
 		if( is_bool( $mode ) ) {
 			$this->executeInline = $mode;
@@ -1540,18 +1539,19 @@ class adoSchema {
 	}
 
 	/**
-	* Enables/disables SQL continue on error.
-	*
-	* Call this method to enable or disable continuation of SQL execution if an error occurs.
-	* If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
-	* If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
-	* of the schema will continue.
-	*
-	* @param bool $mode execute
-	* @return bool current continueOnError mode
-	*
-	* @see addSQL(), ExecuteSchema()
-	*/
+	 * Enables/disables SQL continue on error.
+	 *
+	 * Call this method to enable or disable continuation of SQL execution if an error occurs.
+	 * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
+	 * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
+	 * of the schema will continue.
+	 *
+	 * @param bool $mode execute
+	 * @return bool current continueOnError mode
+	 *
+	 * @see addSQL()
+	 * @see ExecuteSchema()
+	 */
 	function continueOnError( $mode = NULL ) {
 		if( is_bool( $mode ) ) {
 			$this->continueOnError = $mode;
@@ -1561,43 +1561,44 @@ class adoSchema {
 	}
 
 	/**
-	* Loads an XML schema from a file and converts it to SQL.
-	*
-	* Call this method to load the specified schema (see the DTD for the proper format) from
-	* the filesystem and generate the SQL necessary to create the database
-	* described. This method automatically converts the schema to the latest
-	* axmls schema version.
-	* @see ParseSchemaString()
-	*
-	* @param string $file Name of XML schema file.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute
-	*/
+	 * Loads an XML schema from a file and converts it to SQL.
+	 *
+	 * Call this method to load the specified schema (see the DTD for the proper format) from
+	 * the filesystem and generate the SQL necessary to create the database
+	 * described. This method automatically converts the schema to the latest
+	 * axmls schema version.
+	 * @see ParseSchemaString()
+	 *
+	 * @param string $file Name of XML schema file.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute
+	 */
 	function parseSchema( $filename, $returnSchema = FALSE ) {
 		return $this->parseSchemaString( $this->convertSchemaFile( $filename ), $returnSchema );
 	}
 
 	/**
-	* Loads an XML schema from a file and converts it to SQL.
-	*
-	* Call this method to load the specified schema directly from a file (see
-	* the DTD for the proper format) and generate the SQL necessary to create
-	* the database described by the schema. Use this method when you are dealing
-	* with large schema files. Otherwise, parseSchema() is faster.
-	* This method does not automatically convert the schema to the latest axmls
-	* schema version. You must convert the schema manually using either the
-	* convertSchemaFile() or convertSchemaString() method.
-	* @see parseSchema()
-	* @see convertSchemaFile()
-	* @see convertSchemaString()
-	*
-	* @param string $file Name of XML schema file.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute.
-	*
-	* @deprecated Replaced by adoSchema::parseSchema() and adoSchema::parseSchemaString()
-	* @see parseSchema(), parseSchemaString()
-	*/
+	 * Loads an XML schema from a file and converts it to SQL.
+	 *
+	 * Call this method to load the specified schema directly from a file (see
+	 * the DTD for the proper format) and generate the SQL necessary to create
+	 * the database described by the schema. Use this method when you are dealing
+	 * with large schema files. Otherwise, parseSchema() is faster.
+	 * This method does not automatically convert the schema to the latest axmls
+	 * schema version. You must convert the schema manually using either the
+	 * convertSchemaFile() or convertSchemaString() method.
+	 * @see parseSchema()
+	 * @see convertSchemaFile()
+	 * @see convertSchemaString()
+	 *
+	 * @param string $file Name of XML schema file.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute.
+	 *
+	 * @deprecated Replaced by adoSchema::parseSchema() and adoSchema::parseSchemaString()
+	 * @see parseSchema()
+	 * @see parseSchemaString()
+	 */
 	function parseSchemaFile( $filename, $returnSchema = FALSE ) {
 		// Open the file
 		if( !($fp = fopen( $filename, 'r' )) ) {
@@ -1640,16 +1641,16 @@ class adoSchema {
 	}
 
 	/**
-	* Converts an XML schema string to SQL.
-	*
-	* Call this method to parse a string containing an XML schema (see the DTD for the proper format)
-	* and generate the SQL necessary to create the database described by the schema.
-	* @see parseSchema()
-	*
-	* @param string $xmlstring XML schema string.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute.
-	*/
+	 * Converts an XML schema string to SQL.
+	 *
+	 * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+	 * and generate the SQL necessary to create the database described by the schema.
+	 * @see parseSchema()
+	 *
+	 * @param string $xmlstring XML schema string.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute.
+	 */
 	function parseSchemaString( $xmlstring, $returnSchema = FALSE ) {
 		if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
 			logMsg( 'Empty or Invalid Schema' );
@@ -1684,31 +1685,31 @@ class adoSchema {
 	}
 
 	/**
-	* Loads an XML schema from a file and converts it to uninstallation SQL.
-	*
-	* Call this method to load the specified schema (see the DTD for the proper format) from
-	* the filesystem and generate the SQL necessary to remove the database described.
-	* @see RemoveSchemaString()
-	*
-	* @param string $file Name of XML schema file.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute
-	*/
+	 * Loads an XML schema from a file and converts it to uninstallation SQL.
+	 *
+	 * Call this method to load the specified schema (see the DTD for the proper format) from
+	 * the filesystem and generate the SQL necessary to remove the database described.
+	 * @see RemoveSchemaString()
+	 *
+	 * @param string $file Name of XML schema file.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute
+	 */
 	function removeSchema( $filename, $returnSchema = FALSE ) {
 		return $this->removeSchemaString( $this->convertSchemaFile( $filename ), $returnSchema );
 	}
 
 	/**
-	* Converts an XML schema string to uninstallation SQL.
-	*
-	* Call this method to parse a string containing an XML schema (see the DTD for the proper format)
-	* and generate the SQL necessary to uninstall the database described by the schema.
-	* @see removeSchema()
-	*
-	* @param string $schema XML schema string.
-	* @param bool $returnSchema Return schema rather than parsing.
-	* @return array Array of SQL queries, ready to execute.
-	*/
+	 * Converts an XML schema string to uninstallation SQL.
+	 *
+	 * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
+	 * and generate the SQL necessary to uninstall the database described by the schema.
+	 * @see removeSchema()
+	 *
+	 * @param string $schema XML schema string.
+	 * @param bool $returnSchema Return schema rather than parsing.
+	 * @return array Array of SQL queries, ready to execute.
+	 */
 	function removeSchemaString( $schema, $returnSchema = FALSE ) {
 
 		// grab current version
@@ -1720,18 +1721,20 @@ class adoSchema {
 	}
 
 	/**
-	* Applies the current XML schema to the database (post execution).
-	*
-	* Call this method to apply the current schema (generally created by calling
-	* parseSchema() or parseSchemaString() ) to the database (creating the tables, indexes,
-	* and executing other SQL specified in the schema) after parsing.
-	* @see parseSchema(), parseSchemaString(), executeInline()
-	*
-	* @param array $sqlArray Array of SQL statements that will be applied rather than
-	*		the current schema.
-	* @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
-	* @returns integer 0 if failure, 1 if errors, 2 if successful.
-	*/
+	 * Applies the current XML schema to the database (post execution).
+	 *
+	 * Call this method to apply the current schema (generally created by calling
+	 * parseSchema() or parseSchemaString() ) to the database (creating the tables, indexes,
+	 * and executing other SQL specified in the schema) after parsing.
+	 * @see parseSchema()
+	 * @see parseSchemaString()
+	 * @see executeInline()
+	 *
+	 * @param array $sqlArray Array of SQL statements that will be applied rather than
+	 *		the current schema.
+	 * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
+	 * @returns integer 0 if failure, 1 if errors, 2 if successful.
+	 */
 	function executeSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
 		if( !is_bool( $continueOnErr ) ) {
 			$continueOnErr = $this->continueOnError();
@@ -1751,35 +1754,35 @@ class adoSchema {
 	}
 
 	/**
-	* Returns the current SQL array.
-	*
-	* Call this method to fetch the array of SQL queries resulting from
-	* parseSchema() or parseSchemaString().
-	*
-	* @param string $format Format: HTML, TEXT, or NONE (PHP array)
-	* @return array Array of SQL statements or FALSE if an error occurs
-	*/
+	 * Returns the current SQL array.
+	 *
+	 * Call this method to fetch the array of SQL queries resulting from
+	 * parseSchema() or parseSchemaString().
+	 *
+	 * @param string $format Format: HTML, TEXT, or NONE (PHP array)
+	 * @return array Array of SQL statements or FALSE if an error occurs
+	 */
 	function printSQL( $format = 'NONE' ) {
 		$sqlArray = null;
 		return $this->getSQL( $format, $sqlArray );
 	}
 
 	/**
-	* Saves the current SQL array to the local filesystem as a list of SQL queries.
-	*
-	* Call this method to save the array of SQL queries (generally resulting from a
-	* parsed XML schema) to the filesystem.
-	*
-	* @param string $filename Path and name where the file should be saved.
-	* @return boolean TRUE if save is successful, else FALSE.
-	*/
+	 * Saves the current SQL array to the local filesystem as a list of SQL queries.
+	 *
+	 * Call this method to save the array of SQL queries (generally resulting from a
+	 * parsed XML schema) to the filesystem.
+	 *
+	 * @param string $filename Path and name where the file should be saved.
+	 * @return boolean TRUE if save is successful, else FALSE.
+	 */
 	function saveSQL( $filename = './schema.sql' ) {
 
 		if( !isset( $sqlArray ) ) {
 			$sqlArray = $this->sqlArray;
 		}
 		if( !isset( $sqlArray ) ) {
-			return FALSE;
+			return false;
 		}
 
 		$fp = fopen( $filename, "w" );
@@ -1791,12 +1794,12 @@ class adoSchema {
 	}
 
 	/**
-	* Create an xml parser
-	*
-	* @return object PHP XML parser object
-	*
-	* @access private
-	*/
+	 * Create an xml parser
+	 *
+	 * @return object PHP XML parser object
+	 *
+	 * @access private
+	 */
 	function create_parser() {
 		// Create the parser
 		$xmlParser = xml_parser_create();
@@ -1810,10 +1813,10 @@ class adoSchema {
 	}
 
 	/**
-	* XML Callback to process start elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process start elements
+	 *
+	 * @access private
+	 */
 	function _tag_open( $parser, $tag, $attributes ) {
 		switch( strtoupper( $tag ) ) {
 			case 'TABLE':
@@ -1835,39 +1838,39 @@ class adoSchema {
 	}
 
 	/**
-	* XML Callback to process CDATA elements
-	*
-	* @access private
-	*/
+	 * XML Callback to process CDATA elements
+	 *
+	 * @access private
+	 */
 	function _tag_cdata( $parser, $cdata ) {
 	}
 
 	/**
-	* XML Callback to process end elements
-	*
-	* @access private
-	* @internal
-	*/
+	 * XML Callback to process end elements
+	 *
+	 * @access private
+	 * @internal
+	 */
 	function _tag_close( $parser, $tag ) {
 
 	}
 
 	/**
-	* Converts an XML schema string to the specified DTD version.
-	*
-	* Call this method to convert a string containing an XML schema to a different AXMLS
-	* DTD version. For instance, to convert a schema created for an pre-1.0 version for
-	* AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
-	* parameter is specified, the schema will be converted to the current DTD version.
-	* If the newFile parameter is provided, the converted schema will be written to the specified
-	* file.
-	* @see convertSchemaFile()
-	*
-	* @param string $schema String containing XML schema that will be converted.
-	* @param string $newVersion DTD version to convert to.
-	* @param string $newFile File name of (converted) output file.
-	* @return string Converted XML schema or FALSE if an error occurs.
-	*/
+	 * Converts an XML schema string to the specified DTD version.
+	 *
+	 * Call this method to convert a string containing an XML schema to a different AXMLS
+	 * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+	 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+	 * parameter is specified, the schema will be converted to the current DTD version.
+	 * If the newFile parameter is provided, the converted schema will be written to the specified
+	 * file.
+	 * @see convertSchemaFile()
+	 *
+	 * @param string $schema String containing XML schema that will be converted.
+	 * @param string $newVersion DTD version to convert to.
+	 * @param string $newFile File name of (converted) output file.
+	 * @return string Converted XML schema or FALSE if an error occurs.
+	 */
 	function convertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
 
 		// grab current version
@@ -1893,30 +1896,22 @@ class adoSchema {
 		return $result;
 	}
 
-	/*
-	// compat for pre-4.3 - jlim
-	function _file_get_contents($path)
-	{
-		if (function_exists('file_get_contents')) return file_get_contents($path);
-		return join('',file($path));
-	}*/
-
-	/**
-	* Converts an XML schema file to the specified DTD version.
-	*
-	* Call this method to convert the specified XML schema file to a different AXMLS
-	* DTD version. For instance, to convert a schema created for an pre-1.0 version for
-	* AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
-	* parameter is specified, the schema will be converted to the current DTD version.
-	* If the newFile parameter is provided, the converted schema will be written to the specified
-	* file.
-	* @see convertSchemaString()
-	*
-	* @param string $filename Name of XML schema file that will be converted.
-	* @param string $newVersion DTD version to convert to.
-	* @param string $newFile File name of (converted) output file.
-	* @return string Converted XML schema or FALSE if an error occurs.
-	*/
+	/**
+	 * Converts an XML schema file to the specified DTD version.
+	 *
+	 * Call this method to convert the specified XML schema file to a different AXMLS
+	 * DTD version. For instance, to convert a schema created for an pre-1.0 version for
+	 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
+	 * parameter is specified, the schema will be converted to the current DTD version.
+	 * If the newFile parameter is provided, the converted schema will be written to the specified
+	 * file.
+	 * @see convertSchemaString()
+	 *
+	 * @param string $filename Name of XML schema file that will be converted.
+	 * @param string $newVersion DTD version to convert to.
+	 * @param string $newFile File name of (converted) output file.
+	 * @return string Converted XML schema or FALSE if an error occurs.
+	 */
 	function convertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
 
 		// grab current version
@@ -1929,7 +1924,7 @@ class adoSchema {
 		}
 
 		if( $version == $newVersion ) {
-			$result = _file_get_contents( $filename );
+			$result = file_get_contents( $filename );
 
 			// remove unicode BOM if present
 			if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
@@ -1968,7 +1963,7 @@ class adoSchema {
 					return FALSE;
 				}
 
-				$schema = _file_get_contents( $schema );
+				$schema = file_get_contents( $schema );
 				break;
 			case 'string':
 			default:
@@ -1979,14 +1974,14 @@ class adoSchema {
 
 		$arguments = array (
 			'/_xml' => $schema,
-			'/_xsl' => _file_get_contents( $xsl_file )
+			'/_xsl' => file_get_contents( $xsl_file )
 		);
 
 		// create an XSLT processor
 		$xh = xslt_create ();
 
 		// set error handler
-		xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
+		xslt_set_error_handler ($xh, array ($this, 'xslt_error_handler'));
 
 		// process the schema
 		$result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
@@ -1997,15 +1992,15 @@ class adoSchema {
 	}
 
 	/**
-	* Processes XSLT transformation errors
-	*
-	* @param object $parser XML parser object
-	* @param integer $errno Error number
-	* @param integer $level Error level
-	* @param array $fields Error information fields
-	*
-	* @access private
-	*/
+	 * Processes XSLT transformation errors
+	 *
+	 * @param object $parser XML parser object
+	 * @param integer $errno Error number
+	 * @param integer $level Error level
+	 * @param array $fields Error information fields
+	 *
+	 * @access private
+	 */
 	function xslt_error_handler( $parser, $errno, $level, $fields ) {
 		if( is_array( $fields ) ) {
 			$msg = array(
@@ -2050,14 +2045,14 @@ class adoSchema {
 	}
 
 	/**
-	* Returns the AXMLS Schema Version of the requested XML schema file.
-	*
-	* Call this method to obtain the AXMLS DTD version of the requested XML schema file.
-	* @see SchemaStringVersion()
-	*
-	* @param string $filename AXMLS schema file
-	* @return string Schema version number or FALSE on error
-	*/
+	 * Returns the AXMLS Schema Version of the requested XML schema file.
+	 *
+	 * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
+	 * @see SchemaStringVersion()
+	 *
+	 * @param string $filename AXMLS schema file
+	 * @return string Schema version number or FALSE on error
+	 */
 	function schemaFileVersion( $filename ) {
 		// Open the file
 		if( !($fp = fopen( $filename, 'r' )) ) {
@@ -2076,14 +2071,14 @@ class adoSchema {
 	}
 
 	/**
-	* Returns the AXMLS Schema Version of the provided XML schema string.
-	*
-	* Call this method to obtain the AXMLS DTD version of the provided XML schema string.
-	* @see SchemaFileVersion()
-	*
-	* @param string $xmlstring XML schema string
-	* @return string Schema version number or FALSE on error
-	*/
+	 * Returns the AXMLS Schema Version of the provided XML schema string.
+	 *
+	 * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
+	 * @see SchemaFileVersion()
+	 *
+	 * @param string $xmlstring XML schema string
+	 * @return string Schema version number or FALSE on error
+	 */
 	function schemaStringVersion( $xmlstring ) {
 		if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
 			return FALSE;
@@ -2097,18 +2092,18 @@ class adoSchema {
 	}
 
 	/**
-	* Extracts an XML schema from an existing database.
-	*
-	* Call this method to create an XML schema string from an existing database.
-	* If the data parameter is set to TRUE, AXMLS will include the data from the database
-	* tables in the schema.
-	*
-	* @param boolean $data include data in schema dump
-	* @param string $indent indentation to use
-	* @param string $prefix extract only tables with given prefix
-	* @param boolean $stripprefix strip prefix string when storing in XML schema
-	* @return string Generated XML schema
-	*/
+	 * Extracts an XML schema from an existing database.
+	 *
+	 * Call this method to create an XML schema string from an existing database.
+	 * If the data parameter is set to TRUE, AXMLS will include the data from the database
+	 * tables in the schema.
+	 *
+	 * @param boolean $data include data in schema dump
+	 * @param string $indent indentation to use
+	 * @param string $prefix extract only tables with given prefix
+	 * @param boolean $stripprefix strip prefix string when storing in XML schema
+	 * @return string Generated XML schema
+	 */
 	function extractSchema( $data = FALSE, $indent = '  ', $prefix = '' , $stripprefix=false) {
 		$old_mode = $this->db->setFetchMode( ADODB_FETCH_NUM );
 
@@ -2217,15 +2212,15 @@ class adoSchema {
 	}
 
 	/**
-	* Sets a prefix for database objects
-	*
-	* Call this method to set a standard prefix that will be prepended to all database tables
-	* and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
-	*
-	* @param string $prefix Prefix that will be prepended.
-	* @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
-	* @return boolean TRUE if successful, else FALSE
-	*/
+	 * Sets a prefix for database objects
+	 *
+	 * Call this method to set a standard prefix that will be prepended to all database tables
+	 * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
+	 *
+	 * @param string $prefix Prefix that will be prepended.
+	 * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
+	 * @return boolean TRUE if successful, else FALSE
+	 */
 	function setPrefix( $prefix = '', $underscore = TRUE ) {
 		switch( TRUE ) {
 			// clear prefix
@@ -2252,13 +2247,13 @@ class adoSchema {
 	}
 
 	/**
-	* Returns an object name with the current prefix prepended.
-	*
-	* @param string	$name Name
-	* @return string	Prefixed name
-	*
-	* @access private
-	*/
+	 * Returns an object name with the current prefix prepended.
+	 *
+	 * @param string	$name Name
+	 * @return string	Prefixed name
+	 *
+	 * @access private
+	 */
 	function prefix( $name = '' ) {
 		// if prefix is set
 		if( !empty( $this->objectPrefix ) ) {
@@ -2272,13 +2267,13 @@ class adoSchema {
 	}
 
 	/**
-	* Checks if element references a specific platform
-	*
-	* @param string $platform Requested platform
-	* @returns boolean TRUE if platform check succeeds
-	*
-	* @access private
-	*/
+	 * Checks if element references a specific platform
+	 *
+	 * @param string $platform Requested platform
+	 * @returns boolean TRUE if platform check succeeds
+	 *
+	 * @access private
+	 */
 	function supportedPlatform( $platform = NULL ) {
 		if( !empty( $platform ) ) {
 			$regex = '/(^|\|)' . $this->db->databaseType . '(\||$)/i';
@@ -2301,22 +2296,22 @@ class adoSchema {
 	}
 
 	/**
-	* Clears the array of generated SQL.
-	*
-	* @access private
-	*/
+	 * Clears the array of generated SQL.
+	 *
+	 * @access private
+	 */
 	function clearSQL() {
 		$this->sqlArray = array();
 	}
 
 	/**
-	* Adds SQL into the SQL array.
-	*
-	* @param mixed $sql SQL to Add
-	* @return boolean TRUE if successful, else FALSE.
-	*
-	* @access private
-	*/
+	 * Adds SQL into the SQL array.
+	 *
+	 * @param mixed $sql SQL to Add
+	 * @return boolean TRUE if successful, else FALSE.
+	 *
+	 * @access private
+	 */
 	function addSQL( $sql = NULL ) {
 		if( is_array( $sql ) ) {
 			foreach( $sql as $line ) {
@@ -2352,13 +2347,13 @@ class adoSchema {
 	}
 
 	/**
-	* Gets the SQL array in the specified format.
-	*
-	* @param string $format Format
-	* @return mixed SQL
-	*
-	* @access private
-	*/
+	 * Gets the SQL array in the specified format.
+	 *
+	 * @param string $format Format
+	 * @return mixed SQL
+	 *
+	 * @access private
+	 */
 	function getSQL( $format = NULL, $sqlArray = NULL ) {
 		if( !is_array( $sqlArray ) ) {
 			$sqlArray = $this->sqlArray;
@@ -2380,20 +2375,20 @@ class adoSchema {
 	}
 
 	/**
-	* Destroys an adoSchema object.
-	*
-	* Call this method to clean up after an adoSchema object that is no longer in use.
-	* @deprecated adoSchema now cleans up automatically.
-	*/
+	 * Destroys an adoSchema object.
+	 *
+	 * Call this method to clean up after an adoSchema object that is no longer in use.
+	 * @deprecated adoSchema now cleans up automatically.
+	 */
 	function destroy() {
 	}
 }
 
 /**
-* Message logging function
-*
-* @access private
-*/
+ * Message logging function
+ *
+ * @access private
+ */
 function logMsg( $msg, $title = NULL, $force = FALSE ) {
 	if( XMLS_DEBUG or $force ) {
 		echo '<pre>';
diff --git a/adodb.inc.php b/adodb.inc.php
index 3d5d1ad..0eb42d9 100644
--- a/adodb.inc.php
+++ b/adodb.inc.php
@@ -70,19 +70,19 @@ if (!defined('_ADODB_LAYER')) {
 	//==============================================================================================
 
 	/*********************************************************
-	* Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3).
-	* Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi
-	* @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:adodb_force_type
-	*
-	* 0 = ignore empty fields. All empty fields in array are ignored.
-	* 1 = force null. All empty, php null and string 'null' fields are
-	*     changed to sql NULL values.
-	* 2 = force empty. All empty, php null and string 'null' fields are
-	*     changed to sql empty '' or 0 values.
-	* 3 = force value. Value is left as it is. Php null and string 'null'
-	*     are set to sql NULL values and empty fields '' are set to empty '' sql values.
-	* 4 = force value. Like 1 but numeric empty fields are set to zero.
-    */
+	 * Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3).
+	 * Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:adodb_force_type
+	 *
+	 * 0 = ignore empty fields. All empty fields in array are ignored.
+	 * 1 = force null. All empty, php null and string 'null' fields are
+	 *     changed to sql NULL values.
+	 * 2 = force empty. All empty, php null and string 'null' fields are
+	 *     changed to sql empty '' or 0 values.
+	 * 3 = force value. Value is left as it is. Php null and string 'null'
+	 *     are set to sql NULL values and empty fields '' are set to empty '' sql values.
+	 * 4 = force value. Like 1 but numeric empty fields are set to zero.
+	 */
 		define('ADODB_FORCE_IGNORE',0);
 		define('ADODB_FORCE_NULL',1);
 		define('ADODB_FORCE_EMPTY',2);
@@ -99,10 +99,10 @@ if (!defined('_ADODB_LAYER')) {
 	define ('ADODB_STRINGMAX_NOLIMIT',-2);
 
 	/*
-	* Defines the the default meta type returned
-	* when ADOdb encounters a type that it is not
-	* defined in the metaTypes.
-	*/
+	 * Defines the the default meta type returned
+	 * when ADOdb encounters a type that it is not
+	 * defined in the metaTypes.
+	 */
 	if (!defined('ADODB_DEFAULT_METATYPE'))
 		define ('ADODB_DEFAULT_METATYPE','N');
 
@@ -198,7 +198,7 @@ if (!defined('_ADODB_LAYER')) {
 		/**
 		 * ADODB version as a string.
 		 */
-		$ADODB_vers = 'v5.21.4  2022-01-22';
+		$ADODB_vers = 'v5.22.6  2023-06-11';
 
 		/**
 		 * Determines whether recordset->RecordCount() is used.
@@ -221,8 +221,12 @@ if (!defined('_ADODB_LAYER')) {
 	// CLASS ADOFieldObject
 	//==============================================================================================
 	/**
-	 * Helper class for FetchFields -- holds info on a column
+	 * Helper class for FetchFields -- holds info on a column.
+	 *
+	 * Note: Dynamic properties are required here, as some drivers may require
+	 * the object to hold database-specific field metadata.
 	 */
+	#[\AllowDynamicProperties]
 	class ADOFieldObject {
 		var $name = '';
 		var $max_length=0;
@@ -240,12 +244,25 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 
+	/**
+	 * Parse date string to prevent injection attack.
+	 *
+	 * @param string $s
+	 *
+	 * @return string
+	 */
 	function _adodb_safedate($s) {
 		return str_replace(array("'", '\\'), '', $s);
 	}
 
-	// parse date string to prevent injection attack
-	// date string will have one quote at beginning e.g. '3434343'
+	/**
+	 * Parse date string to prevent injection attack.
+	 * Date string will have one quote at beginning e.g. '3434343'
+	 *
+	 * @param string $s
+	 *
+	 * @return string
+	 */
 	function _adodb_safedateq($s) {
 		$len = strlen($s);
 		if ($s[0] !== "'") {
@@ -269,9 +286,17 @@ if (!defined('_ADODB_LAYER')) {
 		return strlen($s2) == 0 ? 'null' : $s2;
 	}
 
-
-	// for transaction handling
-
+	/**
+	 * For transaction handling.
+	 *
+	 * @param $dbms
+	 * @param $fn
+	 * @param $errno
+	 * @param $errmsg
+	 * @param $p1
+	 * @param $p2
+	 * @param $thisConnection
+	 */
 	function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) {
 		//print "Errorno ($fn errno=$errno m=$errmsg) ";
 		$thisConnection->_transOK = false;
@@ -281,8 +306,9 @@ if (!defined('_ADODB_LAYER')) {
 		}
 	}
 
-	//------------------
-	// class for caching
+	/**
+	 * Class ADODB_Cache_File
+	 */
 	class ADODB_Cache_File {
 
 		var $createdir = true; // requires creation of temp dirs
@@ -294,18 +320,42 @@ if (!defined('_ADODB_LAYER')) {
 			}
 		}
 
-		// write serialised recordset to cache item/file
-		function writecache($filename, $contents,  $debug, $secs2cache) {
+		/**
+		 * Write serialised RecordSet to cache item/file.
+		 *
+		 * @param $filename
+		 * @param $contents
+		 * @param $debug
+		 * @param $secs2cache
+		 *
+		 * @return bool|int
+		 */
+		function writecache($filename, $contents, $debug, $secs2cache) {
 			return adodb_write_file($filename, $contents,$debug);
 		}
 
-		// load serialised recordset and unserialise it
+		/**
+		 * load serialised RecordSet and unserialise it
+		 *
+		 * @param $filename
+		 * @param $err
+		 * @param $secs2cache
+		 * @param $rsClass
+		 *
+		 * @return ADORecordSet
+		 */
 		function &readcache($filename, &$err, $secs2cache, $rsClass) {
 			$rs = csv2rs($filename,$err,$secs2cache,$rsClass);
 			return $rs;
 		}
 
-		// flush all items in cache
+		/**
+		 * Flush all items in cache.
+		 *
+		 * @param bool $debug
+		 *
+		 * @return bool|void
+		 */
 		function flushall($debug=false) {
 			global $ADODB_CACHE_DIR;
 
@@ -320,7 +370,12 @@ if (!defined('_ADODB_LAYER')) {
 			return $rez;
 		}
 
-		// flush one file in cache
+		/**
+		 * Flush one file in cache.
+		 *
+		 * @param string $f
+		 * @param bool   $debug
+		 */
 		function flushcache($f, $debug=false) {
 			if (!@unlink($f)) {
 				if ($debug) {
@@ -329,20 +384,29 @@ if (!defined('_ADODB_LAYER')) {
 			}
 		}
 
+		/**
+		 * @param string $hash
+		 *
+		 * @return string
+		 */
 		function getdirname($hash) {
 			global $ADODB_CACHE_DIR;
-			if (!isset($this->notSafeMode)) {
-				$this->notSafeMode = !ini_get('safe_mode');
-			}
-			return ($this->notSafeMode) ? $ADODB_CACHE_DIR.'/'.substr($hash,0,2) : $ADODB_CACHE_DIR;
+			return $ADODB_CACHE_DIR . '/' . substr($hash, 0, 2);
 		}
 
-		// create temp directories
+		/**
+		 * Create temp directories.
+		 *
+		 * @param string $hash
+		 * @param bool   $debug
+		 *
+		 * @return string
+		 */
 		function createdir($hash, $debug) {
 			global $ADODB_CACHE_PERMS;
 
 			$dir = $this->getdirname($hash);
-			if ($this->notSafeMode && !file_exists($dir)) {
+			if (!file_exists($dir)) {
 				$oldu = umask(0);
 				if (!@mkdir($dir, empty($ADODB_CACHE_PERMS) ? 0771 : $ADODB_CACHE_PERMS)) {
 					if(!is_dir($dir) && $debug) {
@@ -395,7 +459,20 @@ if (!defined('_ADODB_LAYER')) {
 	//
 	var $dataProvider = 'native';
 	var $databaseType = '';		/// RDBMS currently in use, eg. odbc, mysql, mssql
-	var $database = '';			/// Name of database to be used.
+
+	/**
+	 * @var string Current database name.
+	 *
+	 * This used to be stored in the $databaseName property, which was marked
+	 * as deprecated in 4.66 and removed in 5.22.5.
+	 */
+	public $database = '';
+
+	/**
+	 * @var string If the driver is PDO, then the dsnType is e.g. sqlsrv, otherwise empty
+	 */
+	public $dsnType = '';
+
 	var $host = '';				/// The hostname of the database server
 	var $port = '';				/// The port of the database server
 	var $user = '';				/// The username which is used to connect to the database server.
@@ -416,8 +493,33 @@ if (!defined('_ADODB_LAYER')) {
 	var $leftBracket = '[';		/// left square bracked for t-sql styled column names
 	var $rightBracket = ']';	/// right square bracked for t-sql styled column names
 	var $charSet=false;			/// character set to use - only for interbase, postgres and oci8
+
+	/** @var string SQL statement to get databases */
 	var $metaDatabasesSQL = '';
+
+	/** @var string SQL statement to get database tables */
 	var $metaTablesSQL = '';
+
+	/** @var string SQL statement to get table columns. */
+	var $metaColumnsSQL;
+
+	/**
+	 * SQL statement to get the last IDENTITY value inserted into an IDENTITY
+	 * column in the same scope.
+	 * @see https://learn.microsoft.com/en-us/sql/t-sql/functions/scope-identity-transact-sql
+	 * @var string
+	 */
+	var $identitySQL;
+
+	/** @var string SQL statement to create a Sequence . */
+	var $_genSeqSQL;
+
+	/** @var string SQL statement to drop a Sequence. */
+	var $_dropSeqSQL;
+
+	/** @var string SQL statement to generate a Sequence ID. */
+	var $_genIDSQL;
+
 	var $uniqueOrderBy = false; /// All order by columns have to be unique
 	var $emptyDate = '&nbsp;';
 	var $emptyTimeStamp = '&nbsp;';
@@ -440,11 +542,47 @@ if (!defined('_ADODB_LAYER')) {
 	var $isoDates = false;			/// accepts dates in ISO format
 	var $cacheSecs = 3600;			/// cache for 1 hour
 
-	// memcache
-	var $memCache = false; /// should we use memCache instead of caching in files
-	var $memCacheHost; /// memCache host
-	var $memCachePort = 11211; /// memCache port
-	var $memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib, not supported w/memcached library)
+	/*****************************************
+	* memcached server options
+	******************************************/
+
+	/**
+	 * Use memCache library instead of caching in files.
+	 * @var bool $memCache
+	 */
+	public $memCache = false;
+
+	/**
+	 * The memcache server(s) to connect to. Can be defined as:
+	 * - a single host name/ip address
+	 * - a list of hosts/ip addresses
+	 * - an array of server connection data (weighted server groups).
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:userguide:memcached
+	 * @var string|array $memCacheHost
+	 */
+	public $memCacheHost;
+
+	/**
+	 * Default port number.
+	 * The default port can be overridden if memcache server connection data
+	 * is provided as an array {@see $memCacheHost}.
+	 * @var int $memCachePort
+	 */
+	public $memCachePort = 11211;
+
+	/**
+	 * Enable compression of stored items.
+	 * @var bool $memCacheCompress
+	 */
+	public $memCacheCompress = false;
+
+	/**
+	 * An array of memcached options.
+	 * Only used with memcached; memcache ignores this setting.
+	 * @link https://www.php.net/manual/en/memcached.constants.php
+	 * @var array $memCacheOptions
+	 */
+	public $memCacheOptions = array();
 
 	var $sysDate = false; /// name of function that returns the current date
 	var $sysTimeStamp = false; /// name of function that returns the current timestamp
@@ -479,6 +617,10 @@ if (!defined('_ADODB_LAYER')) {
 
 	var $null2null = 'null'; // in autoexecute/getinsertsql/getupdatesql, this value will be converted to a null
 	var $bulkBind = false; // enable 2D Execute array
+
+	/** @var string SQL statement executed by some drivers after successful connection. */
+	public $connectStmt = '';
+
 	//
 	// PRIVATE VARS
 	//
@@ -486,14 +628,34 @@ if (!defined('_ADODB_LAYER')) {
 	var $_transOK = null;
 	/** @var resource Identifier for the native database connection */
 	var $_connectionID = false;
-	var $_errorMsg = false;		/// A variable which was used to keep the returned last error message.  The value will
-								/// then returned by the errorMsg() function
-	var $_errorCode = false;	/// Last error code, not guaranteed to be used - only by oci8
+
+	/**
+	 * Stores the last returned error message.
+	 * @see ADOConnection::errorMsg()
+	 * @var string|false
+	 */
+	var $_errorMsg = false;
+
+	/**
+	 * Stores the last returned error code.
+	 * Not guaranteed to be used. Only some drivers actually populate it.
+	 * @var int|false
+	 */
+	var $_errorCode = false;
+
 	var $_queryID = false;		/// This variable keeps the last created result link identifier
 
 	var $_isPersistentConnection = false;	/// A boolean variable to state whether its a persistent connection or normal connection.	*/
 	var $_bindInputArray = false; /// set to true if ADOConnection.Execute() permits binding of array parameters.
-	var $_evalAll = false;
+
+	/**
+	 * Eval string used to filter data.
+	 * Only used in the deprecated Text driver.
+	 * @see https://adodb.org/dokuwiki/doku.php?id=v5:database:text#workaround
+	 * @var string
+	 */
+	var $evalAll = false;
+
 	var $_affected = false;
 	var $_logsql = false;
 	var $_transmode = ''; // transaction mode
@@ -512,6 +674,29 @@ if (!defined('_ADODB_LAYER')) {
 	 */
 	protected $connectionParameters = array();
 
+	/*
+	 * A simple associative array of user-defined custom actual/meta types
+	 */
+	public $customActualTypes = array();
+
+	/*
+	 * An array of user-defined custom meta/actual types.
+	 * $this->customMetaTypes[$meta] = array(
+	 *     'actual'=>'',
+	 *     'dictionary'=>'',
+	 *     'handler'=>'',
+	 *     'callback'=>''
+	 * );
+	 */
+	public $customMetaTypes = array();
+
+	/** @var ADORecordSet Recordset used to retrieve MetaType information */
+	var $_metars;
+
+	/** @var string a specified locale. */
+	var $locale;
+
+
 	/**
 	 * Default Constructor.
 	 * We define it even though it does not actually do anything. This avoids
@@ -572,11 +757,54 @@ if (!defined('_ADODB_LAYER')) {
 		return $matches[1];
 	}
 
+	/**
+	 * Set a custom meta type with a corresponding actual
+	 *
+	 * @param	string	$metaType	The Custom ADOdb metatype
+	 * @param	string	$dictionaryType	The database dictionary type
+	 * @param	string	$actualType	The database actual type
+	 * @param	bool	$handleAsType handle like an existing Metatype
+	 * @param	mixed	$callBack A pre-processing function
+	 *
+	 * @return bool success if the actual exists
+	 */
+	final public function setCustomMetaType(
+		$metaType,
+		$dictionaryType,
+		$actualType,
+		$handleAsType=false,
+		$callback=false){
+
+		$this->customMetaTypes[strtoupper($metaType)] = array(
+			'actual'=>$actualType,
+			'dictionary'=>strtoupper($dictionaryType),
+			'handler'=>$handleAsType,
+			'callback'=>$callback
+			);
+
+		/*
+		* Create a reverse lookup for the actualType
+		*/
+		$this->customActualTypes[$actualType] = $metaType;
+
+		return true;
+	}
+
+	/**
+	 * Get a list of custom meta types.
+	 *
+	 * @return string[]
+	 */
+	final public function getCustomMetaTypes()
+	{
+		return $this->customMetaTypes;
+	}
+
+
 	/**
 	 * Get server version info.
 	 *
-	 * @return string[] An array with 2 elements: $arr['string'] is the description string,
-	 *				 	and $arr[version] is the version (also a string).
+	 * @return string[] Array with 2 string elements: version and description
 	 */
 	function ServerInfo() {
 		return array('description' => '', 'version' => '');
@@ -591,6 +819,13 @@ if (!defined('_ADODB_LAYER')) {
 		return !empty($this->_connectionID);
 	}
 
+	/**
+	 * Find version string.
+	 *
+	 * @param string $str
+	 *
+	 * @return string
+	 */
 	function _findvers($str) {
 		if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) {
 			return $arr[1];
@@ -729,6 +964,16 @@ if (!defined('_ADODB_LAYER')) {
 		return false;
 	}
 
+	/**
+	 * Always force a new connection to database.
+	 *
+	 * @param string $argHostname     Host to connect to
+	 * @param string $argUsername     Userid to login
+	 * @param string $argPassword     Associated password
+	 * @param string $argDatabaseName Database name
+	 *
+	 * @return bool
+	 */
 	function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) {
 		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
 	}
@@ -807,7 +1052,13 @@ if (!defined('_ADODB_LAYER')) {
 		return $ret;
 	}
 
-	function outp_throw($msg,$src='WARN',$sql='') {
+	/**
+	 * Throw an exception if the handler is defined or prints the message if not.
+	 * @param string $msg Message
+	 * @param string $src the name of the calling function (in uppercase)
+	 * @param string $sql Optional offending SQL statement
+	 */
+	function outp_throw($msg, $src='WARN', $sql='') {
 		if (defined('ADODB_ERROR_HANDLER') &&  ADODB_ERROR_HANDLER == 'adodb_throw') {
 			adodb_throw($this->databaseType,$src,-9999,$msg,$sql,false,$this);
 			return;
@@ -843,6 +1094,8 @@ if (!defined('_ADODB_LAYER')) {
 	 *
 	 * @param string $fmt Format string
 	 * @param string $col Date column; use system date if not specified.
+	 *
+	 * @return string
 	 */
 	function SQLDate($fmt, $col = '') {
 		if (!$col) {
@@ -852,14 +1105,14 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	 * Prepare an sql statement and return the statement resource.
+	 * Prepare an SQL statement and return the statement resource.
 	 *
-	 * For databases that do not support this, we return the $sql. To ensure
-	 * compatibility with databases that do not support prepare:
+	 * For databases that do not support prepared statements, we return the
+	 * provided SQL statement as-is, to ensure compatibility:
 	 *
-	 *   $stmt = $db->Prepare("insert into table (id, name) values (?,?)");
-	 *   $db->Execute($stmt,array(1,'Jill')) or die('insert failed');
-	 *   $db->Execute($stmt,array(2,'Joe')) or die('insert failed');
+	 *   $stmt = $db->prepare("insert into table (id, name) values (?,?)");
+	 *   $db->execute($stmt, array(1,'Jill')) or die('insert failed');
+	 *   $db->execute($stmt, array(2,'Joe')) or die('insert failed');
 	 *
 	 * @param string $sql SQL to send to database
 	 *
@@ -870,6 +1123,17 @@ if (!defined('_ADODB_LAYER')) {
 		return $sql;
 	}
 
+	/**
+	 * Releases a previously prepared statement.
+	 *
+	 * @param mixed $stmt Statement resource, as returned by {@see prepare()}
+	 *
+	 * @return bool
+	 */
+	function releaseStatement(&$stmt) {
+		return true;
+	}
+
 	/**
 	 * Prepare a Stored Procedure and return the statement resource.
 	 *
@@ -897,6 +1161,12 @@ if (!defined('_ADODB_LAYER')) {
 		return $this->qstr($s);
 	}
 
+	/**
+	 * Quotes a string so that all strings are escaped.
+	 * Wrapper for qstr with magic_quotes = false.
+	 *
+	 * @param string &$s
+	 */
 	function q(&$s) {
 		//if (!empty($this->qNull && $s == 'null') {
 		//	return $s;
@@ -905,8 +1175,9 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* PEAR DB Compat - do not use internally.
-	*/
+	 * PEAR DB Compat - do not use internally.
+	 * @return int
+	 */
 	function ErrorNative() {
 		return $this->ErrorNo();
 	}
@@ -914,18 +1185,23 @@ if (!defined('_ADODB_LAYER')) {
 
 	/**
 	 * PEAR DB Compat - do not use internally.
+	 * @param string $seq_name
+	 * @return int
 	 */
 	function nextId($seq_name) {
 		return $this->GenID($seq_name);
 	}
 
 	/**
-	 * Lock a row, will escalate and lock the table if row locking not supported
-	 * will normally free the lock at the end of the transaction
+	 * Lock a row.
+	 * Will escalate and lock the table if row locking is not supported.
+	 * Will normally free the lock at the end of the transaction.
 	 *
-	 * @param string $table	name of table to lock
-	 * @param string $where	where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock
-     * @param string $col
+	 * @param string $table name of table to lock
+	 * @param string $where where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock
+	 * @param string $col
+	 *
+	 * @return bool
 	 */
 	function RowLock($table,$where,$col='1 as adodbignore') {
 		return false;
@@ -948,15 +1224,15 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* PEAR DB Compat - do not use internally.
-	*
-	* The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical
-	* for easy porting :-)
-	*
-	* @param int $mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM
-	*
-	* @return int Previous fetch mode
-	*/
+	 * PEAR DB Compat - do not use internally.
+	 *
+	 * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical
+	 * for easy porting :-)
+	 *
+	 * @param int $mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM
+	 *
+	 * @return int Previous fetch mode
+	 */
 	function SetFetchMode($mode) {
 		$old = $this->fetchMode;
 		$this->fetchMode = $mode;
@@ -968,15 +1244,14 @@ if (!defined('_ADODB_LAYER')) {
 		return $old;
 	}
 
-
 	/**
-	* PEAR DB Compat - do not use internally.
+	 * PEAR DB Compat - do not use internally.
 	 *
 	 * @param string     $sql
 	 * @param array|bool $inputarr
 	 *
 	 * @return ADORecordSet|bool
-	*/
+	 */
 	function Query($sql, $inputarr=false) {
 		$rs = $this->Execute($sql, $inputarr);
 		if (!$rs && defined('ADODB_PEAR')) {
@@ -985,7 +1260,6 @@ if (!defined('_ADODB_LAYER')) {
 		return $rs;
 	}
 
-
 	/**
 	 * PEAR DB Compat - do not use internally
 	 */
@@ -1026,36 +1300,54 @@ if (!defined('_ADODB_LAYER')) {
 		return '?';
 	}
 
-	/*
-		InParameter and OutParameter are self-documenting versions of Parameter().
-	*/
-	function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) {
+	/**
+	 * Self-documenting version of Parameter().
+	 *
+	 * @param $stmt
+	 * @param &$var
+	 * @param $name
+	 * @param int $maxLen
+	 * @param bool $type
+	 *
+	 * @return bool
+	 */
+	function InParameter(&$stmt, &$var, $name, $maxLen=4000, $type=false) {
 		return $this->Parameter($stmt,$var,$name,false,$maxLen,$type);
 	}
 
-	/*
-	*/
+	/**
+	 * Self-documenting version of Parameter().
+	 *
+	 * @param $stmt
+	 * @param $var
+	 * @param $name
+	 * @param int $maxLen
+	 * @param bool $type
+	 *
+	 * @return bool
+	 */
 	function OutParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) {
 		return $this->Parameter($stmt,$var,$name,true,$maxLen,$type);
 
 	}
 
-
-	/*
-	Usage in oracle
-		$stmt = $db->Prepare('select * from table where id =:myid and group=:group');
-		$db->Parameter($stmt,$id,'myid');
-		$db->Parameter($stmt,$group,'group',64);
-		$db->Execute();
-
-		@param $stmt Statement returned by Prepare() or PrepareSP().
-		@param $var PHP variable to bind to
-		@param $name Name of stored procedure variable name to bind to.
-		@param [$isOutput] Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
-		@param [$maxLen] Holds an maximum length of the variable.
-		@param [$type] The data type of $var. Legal values depend on driver.
-
-	*/
+	/**
+	 *
+	 * Usage in oracle
+	 *   $stmt = $db->Prepare('select * from table where id =:myid and group=:group');
+	 *   $db->Parameter($stmt,$id,'myid');
+	 *   $db->Parameter($stmt,$group,'group',64);
+	 *   $db->Execute();
+	 *
+	 * @param mixed &$stmt Statement returned by Prepare() or PrepareSP().
+	 * @param mixed &$var PHP variable to bind to
+	 * @param string $name Name of stored procedure variable name to bind to.
+	 * @param int|bool $isOutput Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
+	 * @param int $maxLen Holds an maximum length of the variable.
+	 * @param mixed $type The data type of $var. Legal values depend on driver.
+	 *
+	 * @return bool
+	 */
 	function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) {
 		return false;
 	}
@@ -1102,13 +1394,16 @@ if (!defined('_ADODB_LAYER')) {
 
 
 	/**
-		Used together with StartTrans() to end a transaction. Monitors connection
-		for sql errors, and will commit or rollback as appropriate.
-
-		@autoComplete if true, monitor sql errors and commit and rollback as appropriate,
-		and if set to false force rollback even if no SQL error detected.
-		@returns true on commit, false on rollback.
-	*/
+	 * Complete a transation.
+	 *
+	 * Used together with StartTrans() to end a transaction. Monitors connection
+	 * for sql errors, and will commit or rollback as appropriate.
+	 *
+	 * @param bool autoComplete if true, monitor sql errors and commit and
+	 *                          rollback as appropriate, and if set to false
+	 *                          force rollback even if no SQL error detected.
+	 * @returns true on commit, false on rollback.
+	 */
 	function CompleteTrans($autoComplete = true) {
 		if ($this->transOff > 1) {
 			$this->transOff -= 1;
@@ -1132,16 +1427,16 @@ if (!defined('_ADODB_LAYER')) {
 			$this->_transOK = false;
 			$this->RollbackTrans();
 			if ($this->debug) {
-				ADOCOnnection::outp("Smart Rollback occurred");
+				ADOConnection::outp("Smart Rollback occurred");
 			}
 		}
 
 		return $this->_transOK;
 	}
 
-	/*
-		At the end of a StartTrans/CompleteTrans block, perform a rollback.
-	*/
+	/**
+	 * At the end of a StartTrans/CompleteTrans block, perform a rollback.
+	 */
 	function FailTrans() {
 		if ($this->debug)
 			if ($this->transOff == 0) {
@@ -1154,8 +1449,8 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-		Check if transaction has failed, only for Smart Transactions.
-	*/
+	 * Check if transaction has failed, only for Smart Transactions.
+	 */
 	function HasFailedTrans() {
 		if ($this->transOff > 0) {
 			return $this->_transOK == false;
@@ -1171,7 +1466,7 @@ if (!defined('_ADODB_LAYER')) {
 	 * @param array|bool $inputarr holds the input data to bind to.
 	 *                             Null elements will be set to null.
 	 *
-	 * @return ADORecordSet|bool
+	 * @return ADORecordSet|false
 	 */
 	public function Execute($sql, $inputarr = false) {
 		if ($this->fnExecute) {
@@ -1324,9 +1619,6 @@ if (!defined('_ADODB_LAYER')) {
 
 		// error handling if query fails
 		if ($this->_queryID === false) {
-			if ($this->debug == 99) {
-				adodb_backtrace(true,5);
-			}
 			$fn = $this->raiseErrorFn;
 			if ($fn) {
 				$fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this);
@@ -1374,6 +1666,18 @@ if (!defined('_ADODB_LAYER')) {
 		return $rs;
 	}
 
+	/**
+	 * Execute a query.
+	 *
+	 * @param string|array $sql        Query to execute.
+	 * @param array        $inputarr   An optional array of parameters.
+	 *
+	 * @return mixed|bool Query identifier or true if execution successful, false if failed.
+	 */
+	function _query($sql, $inputarr = false) {
+		return false;
+	}
+
 	function CreateSequence($seqname='adodbseq',$startID=1) {
 		if (empty($this->_genSeqSQL)) {
 			return false;
@@ -1496,8 +1800,8 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* @return # rows affected by UPDATE/DELETE
-	*/
+	 * @return int|false Number of rows affected by UPDATE/DELETE
+	 */
 	function Affected_Rows() {
 		if ($this->hasAffectedRows) {
 			if ($this->fnExecute === 'adodb_log_sql') {
@@ -1572,11 +1876,24 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	 * @returns assoc array where keys are tables, and values are foreign keys
+	 * Returns a list of Foreign Keys associated with a specific table.
+	 *
+	 * If there are no foreign keys then the function returns false.
+	 *
+	 * @param string $table       The name of the table to get the foreign keys for.
+	 * @param string $owner       Table owner/schema.
+	 * @param bool   $upper       If true, only matches the table with the uppercase name.
+	 * @param bool   $associative Returns the result in associative mode;
+	 *                            if ADODB_FETCH_MODE is already associative, then
+	 *                            this parameter is discarded.
+	 *
+	 * @return string[]|false An array where keys are tables, and values are foreign keys;
+	 *                        false if no foreign keys could be found.
 	 */
-	function MetaForeignKeys($table, $owner=false, $upper=false) {
+	function metaForeignKeys($table, $owner = '', $upper = false, $associative = false) {
 		return false;
 	}
+
 	/**
 	 * Choose a database to connect to. Many databases do not support this.
 	 *
@@ -1693,12 +2010,12 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* Create serializable recordset. Breaks rs link to connection.
-	*
-	* @param ADORecordSet $rs the recordset to serialize
+	 * Create serializable recordset. Breaks rs link to connection.
+	 *
+	 * @param ADORecordSet $rs the recordset to serialize
 	 *
-	* @return ADORecordSet_array|bool the new recordset
-	*/
+	 * @return ADORecordSet_array|bool the new recordset
+	 */
 	function SerializableRS(&$rs) {
 		$rs2 = $this->_rs2rs($rs);
 		$ignore = false;
@@ -1708,17 +2025,17 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* Convert a database recordset to an array recordset.
+	 * Convert a database recordset to an array recordset.
+	 *
+	 * Input recordset's cursor should be at beginning, and old $rs will be closed.
 	 *
-	* Input recordset's cursor should be at beginning, and old $rs will be closed.
-	*
 	 * @param ADORecordSet $rs     the recordset to copy
 	 * @param int          $nrows  number of rows to retrieve (optional)
 	 * @param int          $offset offset by number of rows (optional)
 	 * @param bool         $close
 	 *
 	 * @return ADORecordSet_array|ADORecordSet|bool the new recordset
-	*/
+	 */
 	function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) {
 		if (! $rs) {
 			$ret = false;
@@ -1747,7 +2064,7 @@ if (!defined('_ADODB_LAYER')) {
 
 		$arrayClass = $this->arrayClass;
 
-		$rs2 = new $arrayClass();
+		$rs2 = new $arrayClass($fakeQueryId=1);
 		$rs2->connection = $this;
 		$rs2->sql = $rs->sql;
 		$rs2->dataProvider = $this->dataProvider;
@@ -1756,7 +2073,7 @@ if (!defined('_ADODB_LAYER')) {
 		return $rs2;
 	}
 
-	/*
+	/**
 	 * Return all rows.
 	 *
 	 * Compat with PEAR DB.
@@ -1765,9 +2082,9 @@ if (!defined('_ADODB_LAYER')) {
 	 * @param array|bool $inputarr Input bind array
 	 *
 	 * @return array|false
-	*/
+	 */
 	function GetAll($sql, $inputarr=false) {
-        return $this->GetArray($sql,$inputarr);
+		return $this->GetArray($sql,$inputarr);
 	}
 
 	/**
@@ -1941,23 +2258,17 @@ if (!defined('_ADODB_LAYER')) {
 		return $rv;
 	}
 
-	function Transpose(&$rs,$addfieldnames=true) {
-		$rs2 = $this->_rs2rs($rs);
-		if (!$rs2) {
-			return false;
-		}
-
-		$rs2->_transpose($addfieldnames);
-		return $rs2;
-	}
-
-	/*
-		Calculate the offset of a date for a particular database and generate
-			appropriate SQL. Useful for calculating future/past dates and storing
-			in a database.
-
-		If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour.
-	*/
+	/**
+	 * Calculate the offset of a date for a particular database
+	 * and generate appropriate SQL.
+	 *
+	 * Useful for calculating future/past dates and storing in a database.
+	 *
+	 * @param double       $dayFraction 1.5 means 1.5 days from now, 1.0/24 for 1 hour
+	 * @param string|false $date        Reference date, false for system time
+	 *
+	 * @return string
+	 */
 	function OffsetDate($dayFraction,$date=false) {
 		if (!$date) {
 			$date = $this->sysDate;
@@ -2029,14 +2340,14 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* Return one row of sql statement. Recordset is disposed for you.
-	* Note that SelectLimit should not be called.
-	*
-	* @param string     $sql      SQL statement
-	* @param array|bool $inputarr input bind array
+	 * Return one row of sql statement. Recordset is disposed for you.
+	 * Note that SelectLimit should not be called.
+	 *
+	 * @param string     $sql      SQL statement
+	 * @param array|bool $inputarr input bind array
 	 *
-	* @return array|false Array containing the first row of the query
-	*/
+	 * @return array|false Array containing the first row of the query
+	 */
 	function GetRow($sql,$inputarr=false) {
 		global $ADODB_COUNTRECS;
 
@@ -2083,24 +2394,24 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* Insert or replace a single record. Note: this is not the same as MySQL's replace.
-	* ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL.
-	* Also note that no table locking is done currently, so it is possible that the
-	* record be inserted twice by two programs...
-	*
-	* $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname');
-	*
-	* $table		table name
-	* $fieldArray	associative array of data (you must quote strings yourself).
-	* $keyCol		the primary key field name or if compound key, array of field names
-	* autoQuote		set to true to use a heuristic to quote strings. Works with nulls and numbers
-	*					but does not work with dates nor SQL functions.
-	* has_autoinc	the primary key is an auto-inc field, so skip in insert.
-	*
-	* Currently blob replace not supported
-	*
-	* returns 0 = fail, 1 = update, 2 = insert
-	*/
+	 * Insert or replace a single record. Note: this is not the same as MySQL's replace.
+	 * ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL.
+	 * Also note that no table locking is done currently, so it is possible that the
+	 * record be inserted twice by two programs...
+	 *
+	 * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname');
+	 *
+	 * $table		table name
+	 * $fieldArray	associative array of data (you must quote strings yourself).
+	 * $keyCol		the primary key field name or if compound key, array of field names
+	 * autoQuote		set to true to use a heuristic to quote strings. Works with nulls and numbers
+	 *					but does not work with dates nor SQL functions.
+	 * has_autoinc	the primary key is an auto-inc field, so skip in insert.
+	 *
+	 * Currently blob replace not supported
+	 *
+	 * returns 0 = fail, 1 = update, 2 = insert
+	 */
 
 	function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false) {
 		global $ADODB_INCLUDED_LIB;
@@ -2182,7 +2493,7 @@ if (!defined('_ADODB_LAYER')) {
 	 *  - userid
 	 *  - setFetchMode (adodb 4.23)
 	 *
-	 * When not in safe mode, we create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR).
+	 * We create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR).
 	 * Assuming that we can have 50,000 files per directory with good performance,
 	 * then we can scale to 12.8 million unique cached recordsets. Wow!
 	 */
@@ -2323,34 +2634,32 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 
-	/*
-
-
-		$forceUpdate .
-	 */
 	/**
-	 * Similar to PEAR DB's autoExecute(), except that $mode can be 'INSERT'
-	 * or 'UPDATE' or DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE.
-	 * If $mode == 'UPDATE', then $where is compulsory as a safety measure.
+	 * Simple interface to insert and update records.
 	 *
-	 * @param $table
-	 * @param $fields_values
-	 * @param string $mode
-	 * @param false $where
-	 * @param bool $forceUpdate  If true, perform update even if the data has not changed.
-	 * @param bool $magic_quotes This param is not used since 5.21.0.
-	 *                           It remains for backwards compatibility.
+	 * Automatically generate and execute INSERT and UPDATE statements
+	 * on a given table, similar to PEAR DB's autoExecute().
+	 *
+	 * @param string $table        Name of the table to process.
+	 * @param array $fields_values Associative array of field names => values.
+	 * @param string|int $mode     Execution mode: 'INSERT' (default), 'UPDATE' or
+	 *                             one of the DB_AUTOQUERY_xx constants.
+	 * @param string $where        SQL where clause (mandatory in UPDATE mode as a safety measure)
+	 * @param bool $forceUpdate    If true, update all provided fields, even if they have not changed;
+	 * 							   otherwise only modified fields are updated.
+	 * @param bool $magic_quotes   This param is not used since 5.21.0.
+	 *                             It remains for backwards compatibility.
 	 *
 	 * @return bool
 	 *
 	 * @noinspection PhpUnusedParameterInspection
 	 */
-	function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $forceUpdate = true, $magic_quotes = false) {
+	function autoExecute($table, $fields_values, $mode = 'INSERT', $where = '', $forceUpdate = true, $magic_quotes = false) {
 		if (empty($fields_values)) {
 			$this->outp_throw('AutoExecute: Empty fields array', 'AutoExecute');
 			return false;
 		}
-		if ($where === false && ($mode == 'UPDATE' || $mode == 2 /* DB_AUTOQUERY_UPDATE */) ) {
+		if (empty($where) && ($mode == 'UPDATE' || $mode == 2 /* DB_AUTOQUERY_UPDATE */)) {
 			$this->outp_throw('AutoExecute: Illegal mode=UPDATE with empty WHERE clause', 'AutoExecute');
 			return false;
 		}
@@ -2362,7 +2671,7 @@ if (!defined('_ADODB_LAYER')) {
 		}
 
 		$rs->tableName = $table;
-		if ($where !== false) {
+		if (!empty($where)) {
 			$sql .= " WHERE $where";
 		}
 		$rs->sql = $sql;
@@ -2457,37 +2766,47 @@ if (!defined('_ADODB_LAYER')) {
 
 
 	/**
-	* Update a blob column, given a where clause. There are more sophisticated
-	* blob handling functions that we could have implemented, but all require
-	* a very complex API. Instead we have chosen something that is extremely
-	* simple to understand and use.
-	*
-	* Note: $blobtype supports 'BLOB' and 'CLOB', default is BLOB of course.
-	*
-	* Usage to update a $blobvalue which has a primary key blob_id=1 into a
-	* field blobtable.blobcolumn:
-	*
-	*	UpdateBlob('blobtable', 'blobcolumn', $blobvalue, 'blob_id=1');
-	*
-	* Insert example:
-	*
-	*	$conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
-	*	$conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
-	*/
-	function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') {
+	 * Update a BLOB column, given a where clause.
+	 *
+	 * There are more sophisticated blob handling functions that we could have
+	 * implemented, but all require a very complex API. Instead we have chosen
+	 * something that is extremely simple to understand and use.
+	 *
+	 * Sample usage:
+	 * - update a BLOB in field table.blob_col with value $blobValue, for a
+	 *   record having primary key id=1
+	 *   $conn->updateBlob('table', 'blob_col', $blobValue, 'id=1');
+	 * - insert example:
+	 *   $conn->execute('INSERT INTO table (id, blob_col) VALUES (1, null)');
+	 *   $conn->updateBlob('table', 'blob_col', $blobValue, 'id=1');
+	 *
+	 * @param string $table
+	 * @param string $column
+	 * @param string $val      Filename containing blob data
+	 * @param mixed  $where    {@see updateBlob()}
+	 * @param string $blobtype supports 'BLOB' (default) and 'CLOB'
+	 *
+	 * @return bool success
+	 */
+	function updateBlob($table, $column, $val, $where, $blobtype='BLOB') {
 		return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
 	}
 
 	/**
-	* Usage:
-	*	UpdateBlob('TABLE', 'COLUMN', '/path/to/file', 'ID=1');
-	*
-	*	$blobtype supports 'BLOB' and 'CLOB'
-	*
-	*	$conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
-	*	$conn->UpdateBlob('blobtable','blobcol',$blobpath,'id=1');
-	*/
-	function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') {
+	 * Update a BLOB from a file.
+	 *
+	 * Usage example:
+	 * $conn->updateBlobFile('table', 'blob_col', '/path/to/file', 'id=1');
+	 *
+	 * @param string $table
+	 * @param string $column
+	 * @param string $path     Filename containing blob data
+	 * @param mixed  $where    {@see updateBlob()}
+	 * @param string $blobtype supports 'BLOB' and 'CLOB'
+	 *
+	 * @return bool success
+	 */
+	function updateBlobFile($table, $column, $path, $where, $blobtype='BLOB') {
 		$fd = fopen($path,'rb');
 		if ($fd === false) {
 			return false;
@@ -2551,12 +2870,12 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	* Usage:
-	*	UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
-	*
-	*	$conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
-	*	$conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
-	*/
+	 * Usage:
+	 *	UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
+	 *
+	 *	$conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
+	 *	$conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
+	 */
 	function UpdateClob($table,$column,$val,$where) {
 		return $this->UpdateBlob($table,$column,$val,$where,'CLOB');
 	}
@@ -2575,9 +2894,9 @@ if (!defined('_ADODB_LAYER')) {
 
 
 	/**
-	*  Change the SQL connection locale to a specified locale.
-	*  This is used to get the date formats written depending on the client locale.
-	*/
+	 *  Change the SQL connection locale to a specified locale.
+	 *  This is used to get the date formats written depending on the client locale.
+	 */
 	function SetDateLocale($locale = 'En') {
 		$this->locale = $locale;
 		switch (strtoupper($locale))
@@ -2656,7 +2975,9 @@ if (!defined('_ADODB_LAYER')) {
 	}
 
 	/**
-	 * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans().
+	 * Begin a Transaction.
+	 *
+	 * Must be followed by CommitTrans() or RollbackTrans().
 	 *
 	 * @return bool true if succeeded or false if database does not support transactions
 	 */
@@ -2718,11 +3039,14 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	}
 
 	/**
-	 * If database does not support transactions, always return true as data always committed
+	 * Commits a transaction.
 	 *
-	 * @param bool $ok  set to false to rollback transaction, true to commit
+	 * If database does not support transactions, return true as data is
+	 * always committed.
 	 *
-	 * @return true/false.
+	 * @param bool $ok True to commit, false to rollback the transaction.
+	 *
+	 * @return bool true if successful
 	 */
 	function CommitTrans($ok=true) {
 		return true;
@@ -2730,9 +3054,12 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 
 	/**
-	 * If database does not support transactions, rollbacks always fail, so return false
+	 * Rolls back a transaction.
 	 *
-	 * @return bool
+	 * If database does not support transactions, return false as rollbacks
+	 * always fail.
+	 *
+	 * @return bool true if successful
 	 */
 	function RollbackTrans() {
 		return false;
@@ -3239,20 +3566,21 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 
 	/**
-	 * Will select the supplied $page number from a recordset, given that it is paginated in pages of
-	 * $nrows rows per page. It also saves two boolean values saying if the given page is the first
-	 * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
+	 * Execute query with pagination.
 	 *
-	 * See docs-adodb.htm#ex8 for an example of usage.
-	 * NOTE: phpLens uses a different algorithm and does not use PageExecute().
+	 * Will select the supplied $page number from a recordset, divided in
+	 * pages of $nrows rows each. It also saves two boolean values saying
+	 * if the given page is the first and/or last one of the recordset.
 	 *
-	 * @param string $sql
-	 * @param int    $nrows          Number of rows per page to get
-	 * @param int    $page           Page number to get (1-based)
-	 * @param mixed[]|bool $inputarr Array of bind variables
-	 * @param int    $secs2cache     Private parameter only used by jlim
+	 * @param string     $sql        Query to execute
+	 * @param int        $nrows      Number of rows per page
+	 * @param int        $page       Page number to retrieve (1-based)
+	 * @param array|bool $inputarr   Array of bind variables
+	 * @param int        $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
+	 *
+	 * @return ADORecordSet|bool the recordset ($rs->databaseType == 'array')
 	 *
-	 * @return mixed		the recordset ($rs->databaseType == 'array')
+	 * @author Iván Oliva
 	 */
 	function PageExecute($sql, $nrows, $page, $inputarr=false, $secs2cache=0) {
 		global $ADODB_INCLUDED_LIB;
@@ -3269,17 +3597,17 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 
 	/**
-	* Will select the supplied $page number from a recordset, given that it is paginated in pages of
-	* $nrows rows per page. It also saves two boolean values saying if the given page is the first
-	* and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
-	*
-	* @param int $secs2cache	seconds to cache data, set to 0 to force query
-	* @param string $sql
-	* @param int $nrows		is the number of rows per page to get
-	* @param int $page		is the page number to get (1-based)
-	* @param mixed[]|bool $inputarr	array of bind variables
-	* @return mixed	the recordset ($rs->databaseType == 'array')
-	*/
+	 * Will select the supplied $page number from a recordset, given that it is paginated in pages of
+	 * $nrows rows per page. It also saves two boolean values saying if the given page is the first
+	 * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
+	 *
+	 * @param int $secs2cache	seconds to cache data, set to 0 to force query
+	 * @param string $sql
+	 * @param int $nrows		is the number of rows per page to get
+	 * @param int $page		is the page number to get (1-based)
+	 * @param mixed[]|bool $inputarr	array of bind variables
+	 * @return mixed	the recordset ($rs->databaseType == 'array')
+	 */
 	function CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) {
 		/*switch($this->dataProvider) {
 		case 'postgres':
@@ -3291,37 +3619,37 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	}
 
 	/**
-	* Returns the maximum size of a MetaType C field. If the method
-	* is not defined in the driver returns ADODB_STRINGMAX_NOTSET
-	*
-	* @return int
-	*/
+	 * Returns the maximum size of a MetaType C field. If the method
+	 * is not defined in the driver returns ADODB_STRINGMAX_NOTSET
+	 *
+	 * @return int
+	 */
 	function charMax() {
 		return ADODB_STRINGMAX_NOTSET;
 	}
 
 	/**
-	* Returns the maximum size of a MetaType X field. If the method
-	* is not defined in the driver returns ADODB_STRINGMAX_NOTSET
-	*
-	* @return int
-	*/
+	 * Returns the maximum size of a MetaType X field. If the method
+	 * is not defined in the driver returns ADODB_STRINGMAX_NOTSET
+	 *
+	 * @return int
+	 */
 	function textMax() {
 		return ADODB_STRINGMAX_NOTSET;
 	}
 
 	/**
-	* Returns a substring of a varchar type field
-	*
-	* Some databases have variations of the parameters, which is why
-	* we have an ADOdb function for it
-	*
-	* @param	string	$fld	The field to sub-string
-	* @param	int		$start	The start point
-	* @param	int		$length	An optional length
-	*
-	* @return string	The SQL text
-	*/
+	 * Returns a substring of a varchar type field
+	 *
+	 * Some databases have variations of the parameters, which is why
+	 * we have an ADOdb function for it
+	 *
+	 * @param	string	$fld	The field to sub-string
+	 * @param	int		$start	The start point
+	 * @param	int		$length	An optional length
+	 *
+	 * @return string	The SQL text
+	 */
 	function substr($fld,$start,$length=0) {
 		$text = "{$this->substr}($fld,$start";
 		if ($length > 0)
@@ -3426,15 +3754,15 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	//==============================================================================================
 
 	/**
-	* Internal placeholder for record objects. Used by ADORecordSet->FetchObj().
-	*/
+	 * Internal placeholder for record objects. Used by ADORecordSet->FetchObj().
+	 */
+	#[\AllowDynamicProperties]
 	class ADOFetchObj {
 	};
 
-	//==============================================================================================
-	// CLASS ADORecordSet_empty
-	//==============================================================================================
-
+	/**
+	 * Class ADODB_Iterator_empty
+	 */
 	class ADODB_Iterator_empty implements Iterator {
 
 		private $rs;
@@ -3477,8 +3805,8 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 
 	/**
-	* Lightweight recordset when there are no records to be returned
-	*/
+	 * Lightweight recordset when there are no records to be returned
+	 */
 	class ADORecordSet_empty implements IteratorAggregate
 	{
 		var $dataProvider = 'empty';
@@ -3564,10 +3892,9 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		include_once(ADODB_DIR.'/adodb-time.inc.php');
 	}
 
-	//==============================================================================================
-	// CLASS ADORecordSet
-	//==============================================================================================
-
+	/**
+	 * Class ADODB_Iterator
+	 */
 	class ADODB_Iterator implements Iterator {
 
 		private $rs;
@@ -3612,18 +3939,29 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	}
 
 
+/**
+ * RecordSet class that represents the dataset returned by the database.
+ *
+ * To keep memory overhead low, this class holds only the current row in memory.
+ * No prefetching of data is done, so the RecordCount() can return -1 (which
+ * means recordcount not known).
+ */
+class ADORecordSet implements IteratorAggregate {
 	/**
-	 * RecordSet class that represents the dataset returned by the database.
-	 * To keep memory overhead low, this class holds only the current row in memory.
-	 * No prefetching of data is done, so the RecordCount() can return -1 ( which
-	 * means recordcount not known).
+	 * Used for cases when a recordset object is not created by executing a query.
 	 */
-	class ADORecordSet implements IteratorAggregate {
+	const DUMMY_QUERY_ID = -1;
 
 	/**
 	 * public variables
 	 */
 	var $dataProvider = "native";
+
+	/**
+	 * @var string Table name (used in _adodb_getupdatesql() and _adodb_getinsertsql())-
+	 */
+	public $tableName = '';
+
 	/** @var bool|array  */
 	var $fields = false;	/// holds the current row data
 	var $blobSize = 100;	/// any varchar/char field this size or greater is treated as a blob
@@ -3639,41 +3977,66 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 	var $bind = false;		/// used by Fields() to hold array - should be private?
 	var $fetchMode;			/// default fetch mode
-	var $connection = false; /// the parent connection
-
+	/** @var ADOConnection The parent connection */
+	var $connection = false;
 	/**
 	 *	private variables
 	 */
 	var $_numOfRows = -1;	/** number of rows, or -1 */
 	var $_numOfFields = -1;	/** number of fields in recordset */
-	/** @var resource result link identifier */
-	var $_queryID = -1;
+
+	/**
+	 * @var resource|int|false result link identifier
+	 */
+	var $_queryID = self::DUMMY_QUERY_ID;
+
 	var $_currentRow = -1;	/** This variable keeps the current row in the Recordset.	*/
 	var $_closed = false;	/** has recordset been closed */
 	var $_inited = false;	/** Init() should only be called once */
 	var $_obj;				/** Used by FetchObj */
 	var $_names;			/** Used by FetchObj */
 
-	var $_currentPage = -1;	/** Added by Iván Oliva to implement recordset pagination */
-	var $_atFirstPage = false;	/** Added by Iván Oliva to implement recordset pagination */
-	var $_atLastPage = false;	/** Added by Iván Oliva to implement recordset pagination */
+	// Recordset pagination
+	/** @var int Number of rows per page */
+	var $rowsPerPage;
+	/** @var int Current page number */
+	var $_currentPage = -1;
+	/** @var bool True if current page is the first page */
+	var $_atFirstPage = false;
+	/** @var bool True if current page is the last page */
+	var $_atLastPage = false;
+	/** @var int Last page number */
 	var $_lastPageNo = -1;
+	/** @var int Total number of rows in recordset */
 	var $_maxRecordCount = 0;
+
 	var $datetime = false;
 
+	public $customActualTypes;
+	public $customMetaTypes;
+
+	/** @var int Only used in _adodb_getinsertsql() */
+	public $insertSig;
+
 	/**
 	 * @var ADOFieldObject[] Field metadata cache
 	 * @see fieldTypesArray()
 	 */
 	protected $fieldObjectsCache;
 
+	/**
+	 * @var int Defines the Fetch Mode for a recordset
+	 * See the ADODB_FETCH_* constants
+	 */
+	public $adodbFetchMode;
+
 	/**
 	 * Constructor
 	 *
-	 * @param resource|int queryID	this is the queryID returned by ADOConnection->_query()
-	 *
+	 * @param resource|int $queryID Query ID returned by ADOConnection->_query()
+	 * @param int|bool     $mode    The ADODB_FETCH_MODE value
 	 */
-	function __construct($queryID) {
+	function __construct($queryID,$mode=false) {
 		$this->_queryID = $queryID;
 	}
 
@@ -3698,7 +4061,7 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		}
 		$this->_inited = true;
 		if ($this->_queryID) {
-			@$this->_initrs();
+			@$this->_initRS();
 		} else {
 			$this->_numOfRows = 0;
 			$this->_numOfFields = 0;
@@ -3713,6 +4076,16 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		}
 	}
 
+	/**
+	 * Recordset initialization stub
+	 */
+	protected function _initRS() {}
+
+	/**
+	 * Row fetch stub
+	 * @return bool
+	 */
+	protected function _fetch() {}
 
 	/**
 	 * Generate a SELECT tag from a recordset, and return the HTML markup.
@@ -3851,24 +4224,28 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		return $this->GetArray($nRows);
 	}
 
-	/*
-	* Some databases allow multiple recordsets to be returned. This function
-	* will return true if there is a next recordset, or false if no more.
-	*/
+	/**
+	 * Checks if there is another available recordset.
+	 *
+	 * Some databases allow multiple recordsets to be returned.
+	 *
+	 * @return boolean true if there is a next recordset, or false if no more
+	 */
 	function NextRecordSet() {
 		return false;
 	}
 
 	/**
-	 * return recordset as a 2-dimensional array.
+	 * Return recordset as a 2-dimensional array.
+	 *
 	 * Helper function for ADOConnection->SelectLimit()
 	 *
-	 * @param offset	is the row to start calculations from (1-based)
-	 * @param [nrows]	is the number of rows to return
+	 * @param int $nrows  Number of rows to return
+	 * @param int $offset Starting row (1-based)
 	 *
 	 * @return array an array indexed by the rows (0-based) from the recordset
 	 */
-	function GetArrayLimit($nrows,$offset=-1) {
+	function getArrayLimit($nrows, $offset=-1) {
 		if ($offset <= 0) {
 			return $this->GetArray($nrows);
 		}
@@ -3889,11 +4266,11 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	/**
 	 * Synonym for GetArray() for compatibility with ADO.
 	 *
-	 * @param [nRows]  is the number of rows to return. -1 means every row.
+	 * @param int $nRows Number of rows to return. -1 means every row.
 	 *
 	 * @return array an array indexed by the rows (0-based) from the recordset
 	 */
-	function GetRows($nRows = -1) {
+	function getRows($nRows = -1) {
 		return $this->GetArray($nRows);
 	}
 
@@ -3994,9 +4371,7 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 			switch ($showArrayMethod) {
 			case 0:
 
-				if ($fetchMode == ADODB_FETCH_ASSOC
-				||  $fetchMode == ADODB_FETCH_BOTH)
-				{
+				if ($fetchMode != ADODB_FETCH_NUM) {
 					/*
 					* The driver should have already handled the key
 					* casing, but in case it did not. We will check and force
@@ -4106,8 +4481,8 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 
 	/**
-	* PEAR DB Compat - do not use internally
-	*/
+	 * PEAR DB Compat - do not use internally
+	 */
 	function Free() {
 		return $this->Close();
 	}
@@ -4152,13 +4527,13 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 
 	/**
-	* Fetch a row, returning PEAR_Error if no more rows.
-	* This is PEAR DB compat mode.
-	*
-	* @param mixed[]|false $arr
-	*
-	* @return mixed DB_OK or error object
-	*/
+	 * Fetch a row, returning PEAR_Error if no more rows.
+	 * This is PEAR DB compat mode.
+	 *
+	 * @param mixed[]|false $arr
+	 *
+	 * @return mixed DB_OK or error object
+	 */
 	function FetchInto(&$arr) {
 		if ($this->EOF) {
 			return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false;
@@ -4281,6 +4656,14 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		return false;
 	}
 
+	/**
+	 * Adjusts the result pointer to an arbitrary row in the result.
+	 *
+	 * @param int $row The row to seek to.
+	 *
+	 * @return bool False if the recordset contains no rows, otherwise true.
+	 */
+	function _seek($row) {}
 
 	/**
 	 * Get the value of a field in the current row by column name.
@@ -4354,8 +4737,9 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	 * Use associative array to get fields array for databases that do not support
 	 * associative arrays. Submitted by Paolo S. Asioli paolo.asioli#libero.it
 	 *
-	 * @param int [$upper] Case for the array keys, defaults to uppercase
+	 * @param int $upper Case for the array keys, defaults to uppercase
 	 *                   (see ADODB_ASSOC_CASE_xxx constants)
+	 * @return array
 	 */
 	function GetRowAssoc($upper = ADODB_ASSOC_CASE) {
 		$record = array();
@@ -4391,15 +4775,14 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	}
 
 	/**
-	 * Synonyms RecordCount and RowCount
+	 * Number of rows in recordset.
 	 *
 	 * @return int Number of rows or -1 if this is not supported
 	 */
-	function RecordCount() {
+	function recordCount() {
 		return $this->_numOfRows;
 	}
 
-
 	/**
 	 * If we are using PageExecute(), this will return the maximum possible rows
 	 * that can be returned when paging a recordset.
@@ -4407,26 +4790,32 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	 * @return int
 	 */
 	function MaxRecordCount() {
-		return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->RecordCount();
+		return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->recordCount();
 	}
 
 	/**
-	 * synonyms RecordCount and RowCount
+	 * Number of rows in recordset.
+	 * Alias for {@see recordCount()}
 	 *
-	 * @return the number of rows or -1 if this is not supported
+	 * @return int Number of rows or -1 if this is not supported
 	 */
-	function RowCount() {
-		return $this->_numOfRows;
+	function rowCount() {
+		return $this->recordCount();
 	}
 
-
-	 /**
-	 * Portable RecordCount. Pablo Roca <pabloroca@mvps.org>
+	/**
+	 * Portable RecordCount.
+	 *
+	 * Be aware of possible problems in multiuser environments.
+	 * For better speed the table must be indexed by the condition.
+	 * Heavy test this before deploying.
 	 *
-	 * @return  the number of records from a previous SELECT. All databases support this.
+	 * @param string $table
+	 * @param string $condition
+	 *
+	 * @return int Number of records from a previous SELECT. All databases support this.
 	 *
-	 * But aware possible problems in multiuser environments. For better speed the table
-	 * must be indexed by the condition. Heavy test this before deploying.
+	 * @author Pablo Roca <pabloroca@mvps.org>
 	 */
 	function PO_RecordCount($table="", $condition="") {
 
@@ -4500,24 +4889,24 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	}
 
 	/**
-	* Return the fields array of the current row as an object for convenience.
-	* The default case is lowercase field names.
-	*
-	* @return the object with the properties set to the fields of the current row
-	*/
+	 * Return the fields array of the current row as an object for convenience.
+	 * The default case is lowercase field names.
+	 *
+	 * @return the object with the properties set to the fields of the current row
+	 */
 	function FetchObj() {
 		return $this->FetchObject(false);
 	}
 
 	/**
-	* Return the fields array of the current row as an object for convenience.
-	* The default case is uppercase.
-	*
-	* @param $isupper to set the object property names to uppercase
-	*
-	* @return the object with the properties set to the fields of the current row
-	*/
-	function FetchObject($isupper=true) {
+	 * Return the fields array of the current row as an object for convenience.
+	 * The default case is uppercase.
+	 *
+	 * @param bool $isUpper to set the object property names to uppercase
+	 *
+	 * @return ADOFetchObj The object with properties set to the fields of the current row
+	 */
+	function FetchObject($isUpper=true) {
 		if (empty($this->_obj)) {
 			$this->_obj = new ADOFetchObj();
 			$this->_names = array();
@@ -4526,12 +4915,11 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 				$this->_names[] = $f->name;
 			}
 		}
-		$i = 0;
 		$o = clone($this->_obj);
 
 		for ($i=0; $i <$this->_numOfFields; $i++) {
 			$name = $this->_names[$i];
-			if ($isupper) {
+			if ($isUpper) {
 				$n = strtoupper($name);
 			} else {
 				$n = $name;
@@ -4543,34 +4931,34 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	}
 
 	/**
-	* Return the fields array of the current row as an object for convenience.
-	* The default is lower-case field names.
-	*
-	* @return the object with the properties set to the fields of the current row,
-	*	or false if EOF
-	*
-	* Fixed bug reported by tim@orotech.net
-	*/
+	 * Return the fields array of the current row as an object for convenience.
+	 * The default is lower-case field names.
+	 *
+	 * @return ADOFetchObj|false The object with properties set to the fields of the current row
+	 *                           or false if EOF.
+	 *
+	 * Fixed bug reported by tim@orotech.net
+	 */
 	function FetchNextObj() {
 		return $this->FetchNextObject(false);
 	}
 
 
 	/**
-	* Return the fields array of the current row as an object for convenience.
-	* The default is upper case field names.
-	*
-	* @param $isupper to set the object property names to uppercase
-	*
-	* @return the object with the properties set to the fields of the current row,
-	*	or false if EOF
-	*
-	* Fixed bug reported by tim@orotech.net
-	*/
-	function FetchNextObject($isupper=true) {
+	 * Return the fields array of the current row as an object for convenience.
+	 * The default is upper case field names.
+	 *
+	 * @param bool $isUpper to set the object property names to uppercase
+	 *
+	 * @return ADOFetchObj|false The object with properties set to the fields of the current row
+	 *                           or false if EOF.
+	 *
+	 * Fixed bug reported by tim@orotech.net
+	 */
+	function FetchNextObject($isUpper=true) {
 		$o = false;
 		if ($this->_numOfRows != 0 && !$this->EOF) {
-			$o = $this->FetchObject($isupper);
+			$o = $this->FetchObject($isUpper);
 			$this->_currentRow++;
 			if ($this->_fetch()) {
 				return $o;
@@ -4581,37 +4969,29 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 	}
 
 	/**
-	 * Get the metatype of the column. This is used for formatting. This is because
-	 * many databases use different names for the same type, so we transform the original
-	 * type to our standardised version which uses 1 character codes:
-	 *
-	 * @param t  is the type passed in. Normally is ADOFieldObject->type.
-	 * @param len is the maximum length of that field. This is because we treat character
-	 *	fields bigger than a certain size as a 'B' (blob).
-	 * @param fieldobj is the field object returned by the database driver. Can hold
-	 *	additional info (eg. primary_key for mysql).
+	 * Get the ADOdb metatype.
 	 *
-	 * @return the general type of the data:
-	 *	C for character < 250 chars
-	 *	X for teXt (>= 250 chars)
-	 *	B for Binary
-	 *	N for numeric or floating point
-	 *	D for date
-	 *	T for timestamp
-	 *	L for logical/Boolean
-	 *	I for integer
-	 *	R for autoincrement counter/integer
+	 * Many databases use different names for the same type, so we transform
+	 * the native type to our standardised one, which uses 1 character codes.
+	 * @see https://adodb.org/dokuwiki/doku.php?id=v5:dictionary:dictionary_index#portable_data_types
 	 *
+	 * @param string|ADOFieldObject $t  Native type (usually ADOFieldObject->type)
+	 *                                  It is also possible to provide an
+	 *                                  ADOFieldObject here.
+	 * @param int $len The field's maximum length. This is because we treat
+	 *                 character fields bigger than a certain size as a 'B' (blob).
+	 * @param ADOFieldObject $fieldObj Field object returned by the database driver;
+	 *                                 can hold additional info (eg. primary_key for mysql).
 	 *
-	*/
-	function MetaType($t,$len=-1,$fieldobj=false) {
-		if (is_object($t)) {
-			$fieldobj = $t;
-			$t = $fieldobj->type;
-			$len = $fieldobj->max_length;
+	 * @return string The ADOdb Standard type
+	 */
+	function metaType($t, $len = -1, $fieldObj = false) {
+		if ($t instanceof ADOFieldObject) {
+			$fieldObj = $t;
+			$t = $fieldObj->type;
+			$len = $fieldObj->max_length;
 		}
 
-
 		// changed in 2.32 to hashing instead of switch stmt for speed...
 		static $typeMap = array(
 			'VARCHAR' => 'C',
@@ -4718,8 +5098,6 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 			"SQLBOOL" => 'L'
 		);
 
-
-		$tmap = false;
 		$t = strtoupper($t);
 		$tmap = (isset($typeMap[$t])) ? $typeMap[$t] : ADODB_DEFAULT_METATYPE;
 		switch ($tmap) {
@@ -4735,7 +5113,7 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 				return 'C';
 
 			case 'I':
-				if (!empty($fieldobj->primary_key)) {
+				if (!empty($fieldObj->primary_key)) {
 					return 'R';
 				}
 				return 'I';
@@ -4744,8 +5122,8 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 				return 'N';
 
 			case 'B':
-				if (isset($fieldobj->binary)) {
-					return ($fieldobj->binary) ? 'B' : 'X';
+				if (isset($fieldObj->binary)) {
+					return ($fieldObj->binary) ? 'B' : 'X';
 				}
 				return 'B';
 
@@ -4796,8 +5174,10 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 	/**
 	 * set/returns the current recordset page when paginating
+	 * @param int $page
+	 * @return int
 	 */
-	function AbsolutePage($page=-1) {
+	function absolutePage($page=-1) {
 		if ($page != -1) {
 			$this->_currentPage = $page;
 		}
@@ -4806,6 +5186,8 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 	/**
 	 * set/returns the status of the atFirstPage flag when paginating
+	 * @param bool $status
+	 * @return bool
 	 */
 	function AtFirstPage($status=false) {
 		if ($status != false) {
@@ -4814,6 +5196,10 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		return $this->_atFirstPage;
 	}
 
+	/**
+	 * @param bool $page
+	 * @return bool
+	 */
 	function LastPageNo($page = false) {
 		if ($page != false) {
 			$this->_lastPageNo = $page;
@@ -4823,6 +5209,8 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 	/**
 	 * set/returns the status of the atLastPage flag when paginating
+	 * @param bool $status
+	 * @return bool
 	 */
 	function AtLastPage($status=false) {
 		if ($status != false) {
@@ -4860,48 +5248,22 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 
 		/**
 		 * Constructor
+		 *
+		 * The parameters passed to this recordset are always fake because
+		 * this class does not use the queryID
+		 *
+		 * @param resource|int $queryID Ignored
+		 * @param int|bool     $mode    The ADODB_FETCH_MODE value
 		 */
-		function __construct($fakeid=1) {
+		function __construct($queryID, $mode=false) {
 			global $ADODB_FETCH_MODE,$ADODB_COMPAT_FETCH;
 
 			// fetch() on EOF does not delete $this->fields
 			$this->compat = !empty($ADODB_COMPAT_FETCH);
-			parent::__construct($fakeid); // fake queryID
+			parent::__construct($queryID); // fake queryID
 			$this->fetchMode = $ADODB_FETCH_MODE;
 		}
 
-		function _transpose($addfieldnames=true) {
-			global $ADODB_INCLUDED_LIB;
-
-			if (empty($ADODB_INCLUDED_LIB)) {
-				include_once(ADODB_DIR.'/adodb-lib.inc.php');
-			}
-			$hdr = true;
-
-			$fobjs = $addfieldnames ? $this->_fieldobjects : false;
-			adodb_transpose($this->_array, $newarr, $hdr, $fobjs);
-			//adodb_pr($newarr);
-
-			$this->_skiprow1 = false;
-			$this->_array = $newarr;
-			$this->_colnames = $hdr;
-
-			adodb_probetypes($newarr,$this->_types);
-
-			$this->_fieldobjects = array();
-
-			foreach($hdr as $k => $name) {
-				$f = new ADOFieldObject();
-				$f->name = $name;
-				$f->type = $this->_types[$k];
-				$f->max_length = -1;
-				$this->_fieldobjects[] = $f;
-			}
-			$this->fields = reset($this->_array);
-
-			$this->_initrs();
-
-		}
 
 		/**
 		 * Setup the array.
@@ -5115,12 +5477,8 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 				break;
 
 			case 'mysql':
-				// mysql driver deprecated since 5.5, removed in 7.0
-				// automatically switch to mysqli
-				if(version_compare(PHP_VERSION, '7.0.0', '>=')) {
-					$db = 'mysqli';
-				}
-				$class = $db;
+				// mysql extension removed in PHP 7.0 - automatically switch to mysqli
+				$class = $db = 'mysqli';
 				break;
 
 			default:
@@ -5485,6 +5843,7 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		}
 		include_once($path);
 		$class = "ADODB2_$drivername";
+		/** @var ADODB_DataDict $dict */
 		$dict = new $class();
 		$dict->dataProvider = $conn->dataProvider;
 		$dict->connection = $conn;
@@ -5497,9 +5856,9 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		return $dict;
 	}
 
-	/*
-		Perform a print_r, with pre tags for better formatting.
-	*/
+	/**
+	 * Perform a print_r, with pre tags for better formatting.
+	 */
 	function adodb_pr($var,$as_string=false) {
 		if ($as_string) {
 			ob_start();
@@ -5518,12 +5877,15 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
 		}
 	}
 
-	/*
-		Perform a stack-crawl and pretty print it.
-
-		@param printOrArr  Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then).
-		@param levels Number of levels to display
-	*/
+	/**
+	 * Perform a stack-crawl and pretty print it.
+	 *
+	 * @param bool  $printOrArr Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then).
+	 * @param int   $levels     Number of levels to display
+	 * @param mixed $ishtml
+	 *
+	 * @return string
+	 */
 	function adodb_backtrace($printOrArr=true,$levels=9999,$ishtml=null) {
 		global $ADODB_INCLUDED_LIB;
 		if (empty($ADODB_INCLUDED_LIB)) {
diff --git a/composer.json b/composer.json
index 807866b..cd97e9d 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,7 @@
 	},
 
 	"require" : {
-		"php" : "^5.5.9 || ^7.0 || ^8.0"
+		"php" : "^7.0 || ^8.0"
 	},
 
 	"require-dev" : {
diff --git a/datadict/datadict-access.inc.php b/datadict/datadict-access.inc.php
index 7a7d4cb..b3f9fad 100644
--- a/datadict/datadict-access.inc.php
+++ b/datadict/datadict-access.inc.php
@@ -30,6 +30,15 @@ class ADODB2_access extends ADODB_DataDict {
 
  	function ActualType($meta)
 	{
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch($meta) {
 		case 'C': return 'TEXT';
 		case 'XL':
@@ -41,18 +50,19 @@ class ADODB2_access extends ADODB_DataDict {
 		case 'B': return 'BINARY';
 
 		case 'TS':
-		case 'D': return 'DATETIME';
+		case 'D': 
+		return 'DATETIME';
 		case 'T': return 'DATETIME';
 
-		case 'L': return 'BYTE';
-		case 'I': return 'INTEGER';
+		case 'L':  return 'BYTE';
+		case 'I':  return 'INTEGER';
 		case 'I1': return 'BYTE';
 		case 'I2': return 'SMALLINT';
 		case 'I4': return 'INTEGER';
 		case 'I8': return 'INTEGER';
 
-		case 'F': return 'DOUBLE';
-		case 'N': return 'NUMERIC';
+		case 'F':  return 'DOUBLE';
+		case 'N':  return 'NUMERIC';
 		default:
 			return $meta;
 		}
diff --git a/datadict/datadict-db2.inc.php b/datadict/datadict-db2.inc.php
index c5dda09..9ac106b 100644
--- a/datadict/datadict-db2.inc.php
+++ b/datadict/datadict-db2.inc.php
@@ -34,6 +34,15 @@ class ADODB2_db2 extends ADODB_DataDict {
 	
  	function ActualType($meta)
 	{
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch($meta) {
 		case 'C': return 'VARCHAR';
 		case 'XL': return 'CLOB';
diff --git a/datadict/datadict-firebird.inc.php b/datadict/datadict-firebird.inc.php
index 0020a0a..79d0a8f 100644
--- a/datadict/datadict-firebird.inc.php
+++ b/datadict/datadict-firebird.inc.php
@@ -22,8 +22,8 @@
 // security - hide paths
 if (!defined('ADODB_DIR')) die();
 
-class ADODB2_firebird extends ADODB_DataDict {
-
+class ADODB2_firebird extends ADODB_DataDict
+{
 	var $databaseType = 'firebird';
 	var $seqField = false;
 	var $seqPrefix = 's_';
@@ -32,69 +32,92 @@ class ADODB2_firebird extends ADODB_DataDict {
 	var $alterCol = ' ALTER';
 	var $dropCol = ' DROP';
 
-	function ActualType($meta)
+	function actualType($meta)
 	{
-		switch($meta) {
-		case 'C': return 'VARCHAR';
-		case 'XL':
-		case 'X': return 'BLOB SUB_TYPE TEXT';
-
-		case 'C2': return 'VARCHAR(32765)'; // up to 32K
-		case 'X2': return 'VARCHAR(4096)';
-
-		case 'V': return 'CHAR';
-		case 'C1': return 'CHAR(1)';
 
-		case 'B': return 'BLOB';
+		$meta = strtoupper($meta);
 
-		case 'D': return 'DATE';
-		case 'TS':
-		case 'T': return 'TIMESTAMP';
-
-		case 'L': return 'SMALLINT';
-		case 'I': return 'INTEGER';
-		case 'I1': return 'SMALLINT';
-		case 'I2': return 'SMALLINT';
-		case 'I4': return 'INTEGER';
-		case 'I8': return 'BIGINT';
+		// Add support for custom meta types.
+		// We do this first, that allows us to override existing types
+		if (isset($this->connection->customMetaTypes[$meta])) {
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		}
 
-		case 'F': return 'DOUBLE PRECISION';
-		case 'N': return 'DECIMAL';
-		default:
-			return $meta;
+		switch($meta) {
+			case 'C':
+				return 'VARCHAR';
+			case 'XL':
+				return 'BLOB SUB_TYPE BINARY';
+			case 'X':
+				return 'BLOB SUB_TYPE TEXT';
+
+			case 'C2':
+				return 'VARCHAR(32765)'; // up to 32K
+			case 'X2':
+				return 'VARCHAR(4096)';
+
+			case 'V':
+				return 'CHAR';
+			case 'C1':
+				return 'CHAR(1)';
+
+			case 'B':
+				return 'BLOB';
+
+			case 'D':
+				return 'DATE';
+			case 'TS':
+			case 'T':
+				return 'TIMESTAMP';
+
+			case 'L':
+			case 'I1':
+			case 'I2':
+				return 'SMALLINT';
+			case 'I':
+			case 'I4':
+				return 'INTEGER';
+			case 'I8':
+				return 'BIGINT';
+
+			case 'F':
+				return 'DOUBLE PRECISION';
+			case 'N':
+				return 'DECIMAL';
+			default:
+				return $meta;
 		}
 	}
 
-	function NameQuote($name = NULL,$allowBrackets=false)
+	function nameQuote($name = null, $allowBrackets = false)
 	{
 		if (!is_string($name)) {
-			return FALSE;
+			return false;
 		}
 
 		$name = trim($name);
 
-		if ( !is_object($this->connection) ) {
+		if (!is_object($this->connection)) {
 			return $name;
 		}
 
 		$quote = $this->connection->nameQuote;
 
 		// if name is of the form `name`, quote it
-		if ( preg_match('/^`(.+)`$/', $name, $matches) ) {
+		if (preg_match('/^`(.+)`$/', $name, $matches)) {
 			return $quote . $matches[1] . $quote;
 		}
 
 		// if name contains special characters, quote it
-		if ( !preg_match('/^[' . $this->nameRegex . ']+$/', $name) ) {
+		if (!preg_match('/^[' . $this->nameRegex . ']+$/', $name)) {
 			return $quote . $name . $quote;
 		}
 
 		return $quote . $name . $quote;
 	}
 
-	function CreateDatabase($dbname, $options=false)
+	function createDatabase($dbname, $options = false)
 	{
-		$options = $this->_Options($options);
 		$sql = array();
 
 		$sql[] = "DECLARE EXTERNAL FUNCTION LOWER CSTRING(80) RETURNS CSTRING(80) FREE_IT ENTRY_POINT 'IB_UDF_lower' MODULE_NAME 'ib_udf'";
@@ -102,50 +125,62 @@ class ADODB2_firebird extends ADODB_DataDict {
 		return $sql;
 	}
 
-	function _DropAutoIncrement($t)
+	function _dropAutoIncrement($tabname)
 	{
-		if (strpos($t,'.') !== false) {
-			$tarr = explode('.',$t);
-			return 'DROP GENERATOR '.$tarr[0].'."s_'.$tarr[1].'"';
+		if (strpos($tabname, '.') !== false) {
+			$tarr = explode('.', $tabname);
+			return 'DROP SEQUENCE ' . $tarr[0] . '."s_' . $tarr[1] . '"';
 		}
-		return 'DROP GENERATOR s_'.$t;
+		return 'DROP SEQUENCE s_' . $tabname;
 	}
 
 
-	function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
+	function _createSuffix($fname, &$ftype, $fnotnull, $fdefault, $fautoinc, $fconstraint, $funsigned)
 	{
 		$suffix = '';
 
-		if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
-		if ($fnotnull) $suffix .= ' NOT NULL';
-		if ($fautoinc) $this->seqField = $fname;
+		if (strlen($fdefault)) {
+			$suffix .= " DEFAULT $fdefault";
+		}
+		if ($fnotnull) {
+			$suffix .= ' NOT NULL';
+		}
+		if ($fautoinc) {
+			$this->seqField = $fname;
+		}
 		$fconstraint = preg_replace("/``/", "\"", $fconstraint);
-		if ($fconstraint) $suffix .= ' '.$fconstraint;
+		if ($fconstraint) {
+			$suffix .= ' ' . $fconstraint;
+		}
 
 		return $suffix;
 	}
 
 	/**
-	 Generate the SQL to create table. Returns an array of sql strings.
-	*/
-	function CreateTableSQL($tabname, $flds, $tableoptions=array())
+	 * Generate the SQL to create table. Returns an array of sql strings.
+	 */
+	function createTableSQL($tabname, $flds, $tableoptions = array())
 	{
-		list($lines,$pkey,$idxs) = $this->_GenFields($flds, true);
+		list($lines, $pkey, $idxs) = $this->_GenFields($flds, true);
 		// genfields can return FALSE at times
-		if ($lines == null) $lines = array();
+		if ($lines == null) {
+			$lines = array();
+		}
 
 		$taboptions = $this->_Options($tableoptions);
-		$tabname = $this->TableName ($tabname);
-		$sql = $this->_TableSQL($tabname,$lines,$pkey,$taboptions);
+		$tabname = $this->TableName($tabname);
+		$sql = $this->_TableSQL($tabname, $lines, $pkey, $taboptions);
 
-		if ($this->autoIncrement && !isset($taboptions['DROP']))
-		{ $tsql = $this->_Triggers($tabname,$taboptions);
-			foreach($tsql as $s) $sql[] = $s;
+		if ($this->autoIncrement && !isset($taboptions['DROP'])) {
+			$tsql = $this->_Triggers($tabname, $taboptions);
+			foreach ($tsql as $s) {
+				$sql[] = $s;
+			}
 		}
 
 		if (is_array($idxs)) {
-			foreach($idxs as $idx => $idxdef) {
-				$sql_idxs = $this->CreateIndexSql($idx, $tabname,  $idxdef['cols'], $idxdef['opts']);
+			foreach ($idxs as $idx => $idxdef) {
+				$sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
 				$sql = array_merge($sql, $sql_idxs);
 			}
 		}
@@ -154,44 +189,47 @@ class ADODB2_firebird extends ADODB_DataDict {
 	}
 
 
-/*
-CREATE or replace TRIGGER jaddress_insert
-before insert on jaddress
-for each row
-begin
-IF ( NEW."seqField" IS NULL OR NEW."seqField" = 0 ) THEN
-  NEW."seqField" = GEN_ID("GEN_tabname", 1);
-end;
-*/
-	function _Triggers($tabname,$tableoptions)
+	/*
+	CREATE or replace TRIGGER jaddress_insert
+	before insert on jaddress
+	for each row
+	begin
+	IF ( NEW."seqField" IS NULL OR NEW."seqField" = 0 ) THEN
+	  NEW."seqField" = GEN_ID("GEN_tabname", 1);
+	end;
+	*/
+	function _triggers($tabname, $taboptions)
 	{
-		if (!$this->seqField) return array();
+		if (!$this->seqField) {
+			return array();
+		}
 
-		$tab1 = preg_replace( '/"/', '', $tabname );
+		$tab1 = preg_replace('/"/', '', $tabname);
 		if ($this->schema) {
-			$t = strpos($tab1,'.');
-			if ($t !== false) $tab = substr($tab1,$t+1);
-			else $tab = $tab1;
+			$t = strpos($tab1, '.');
+			if ($t !== false) {
+				$tab = substr($tab1, $t + 1);
+			} else {
+				$tab = $tab1;
+			}
 			$seqField = $this->seqField;
-			$seqname = $this->schema.'.'.$this->seqPrefix.$tab;
-			$trigname = $this->schema.'.t_'.$this->seqPrefix.$tab;
+			$seqname = $this->schema . '.' . $this->seqPrefix . $tab;
+			$trigname = $this->schema . '.t_' . $this->seqPrefix . $tab;
 		} else {
 			$seqField = $this->seqField;
-			$seqname = $this->seqPrefix.$tab1;
-			$trigname = 't_'.$seqname;
+			$seqname = $this->seqPrefix . $tab1;
+			$trigname = 't_' . $seqname;
 		}
 
-		if (isset($tableoptions['DROP']))
-		{ $sql[] = "DROP GENERATOR $seqname";
-		}
-		elseif (isset($tableoptions['REPLACE']))
-		{ $sql[] = "DROP GENERATOR \"$seqname\"";
-		  $sql[] = "CREATE GENERATOR \"$seqname\"";
-		  $sql[] = "ALTER TRIGGER \"$trigname\" BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END";
-		}
-		else
-		{ $sql[] = "CREATE GENERATOR $seqname";
-		  $sql[] = "CREATE TRIGGER $trigname FOR $tabname BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID($seqname, 1); END";
+		if (isset($taboptions['DROP'])) {
+			$sql[] = "DROP SEQUENCE $seqname";
+		} elseif (isset($taboptions['REPLACE'])) {
+			$sql[] = "DROP SEQUENCE \"$seqname\"";
+			$sql[] = "CREATE SEQUENCE \"$seqname\"";
+			$sql[] = "ALTER TRIGGER \"$trigname\" BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END";
+		} else {
+			$sql[] = "CREATE SEQUENCE $seqname";
+			$sql[] = "CREATE TRIGGER $trigname FOR $tabname BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID($seqname, 1); END";
 		}
 
 		$this->seqField = false;
@@ -201,27 +239,39 @@ end;
 	/**
 	 * Change the definition of one column
 	 *
-	 * As some DBM's can't do that on there own, you need to supply the complete definition of the new table,
-	 * to allow, recreating the table and copying the content over to the new table
 	 * @param string $tabname table-name
 	 * @param string $flds column-name and type for the changed column
-	 * @param string $tableflds='' complete definition of the new table, eg. for postgres, default ''
-	 * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default ''
+	 * @param string $tableflds Unused
+	 * @param array|string $tableoptions Unused
+	 *
 	 * @return array with SQL strings
 	 */
-	function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
+	public function alterColumnSQL($tabname, $flds, $tableflds = '', $tableoptions = '')
 	{
-		$tabname = $this->TableName ($tabname);
+		$tabname = $this->TableName($tabname);
 		$sql = array();
-		list($lines,$pkey,$idxs) = $this->_GenFields($flds);
+		list($lines, , $idxs) = $this->_GenFields($flds);
 		// genfields can return FALSE at times
-		if ($lines == null) $lines = array();
+
+		if ($lines == null) {
+			$lines = array();
+		}
+
 		$alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' ';
-		foreach($lines as $v) {
+
+		foreach ($lines as $v) {
+			/*
+			* The type must be preceded by the keyword 'TYPE'
+			*/
+			$vExplode = explode(' ', $v);
+			$vExplode = array_filter($vExplode);
+			array_splice($vExplode, 1, 0, array('TYPE'));
+			$v = implode(' ', $vExplode);
 			$sql[] = $alter . $v;
 		}
+
 		if (is_array($idxs)) {
-			foreach($idxs as $idx => $idxdef) {
+			foreach ($idxs as $idx => $idxdef) {
 				$sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
 				$sql = array_merge($sql, $sql_idxs);
 			}
diff --git a/datadict/datadict-generic.inc.php b/datadict/datadict-generic.inc.php
index c9c8dee..1a60dbc 100644
--- a/datadict/datadict-generic.inc.php
+++ b/datadict/datadict-generic.inc.php
@@ -28,8 +28,19 @@ class ADODB2_generic extends ADODB_DataDict {
 	var $seqField = false;
 
 
+
  	function ActualType($meta)
 	{
+		
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch($meta) {
 		case 'C': return 'VARCHAR';
 		case 'XL':
diff --git a/datadict/datadict-ibase.inc.php b/datadict/datadict-ibase.inc.php
index 5f58880..4310ded 100644
--- a/datadict/datadict-ibase.inc.php
+++ b/datadict/datadict-ibase.inc.php
@@ -30,6 +30,15 @@ class ADODB2_ibase extends ADODB_DataDict {
 
  	function ActualType($meta)
 	{
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch($meta) {
 		case 'C': return 'VARCHAR';
 		case 'XL':
diff --git a/datadict/datadict-informix.inc.php b/datadict/datadict-informix.inc.php
index acb5ba7..9e15163 100644
--- a/datadict/datadict-informix.inc.php
+++ b/datadict/datadict-informix.inc.php
@@ -30,6 +30,15 @@ class ADODB2_informix extends ADODB_DataDict {
 
 	function ActualType($meta)
 	{
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch($meta) {
 		case 'C': return 'VARCHAR';// 255
 		case 'XL':
diff --git a/datadict/datadict-mssql.inc.php b/datadict/datadict-mssql.inc.php
index 1bcb27d..17df9e3 100644
--- a/datadict/datadict-mssql.inc.php
+++ b/datadict/datadict-mssql.inc.php
@@ -68,9 +68,14 @@ class ADODB2_mssql extends ADODB_DataDict {
 			$t = $fieldobj->type;
 			$len = $fieldobj->max_length;
 		}
+		
+		$t = strtoupper($t);
+		
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
 
 		$len = -1; // mysql max_length is not accurate
-		switch (strtoupper($t)) {
+		switch ($t) {
 		case 'R':
 		case 'INT':
 		case 'INTEGER': return  'I';
@@ -87,6 +92,16 @@ class ADODB2_mssql extends ADODB_DataDict {
 
 	function ActualType($meta)
 	{
+		
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch(strtoupper($meta)) {
 
 		case 'C': return 'VARCHAR';
diff --git a/datadict/datadict-mssqlnative.inc.php b/datadict/datadict-mssqlnative.inc.php
index b53dcd9..59228cb 100644
--- a/datadict/datadict-mssqlnative.inc.php
+++ b/datadict/datadict-mssqlnative.inc.php
@@ -71,7 +71,13 @@ class ADODB2_mssqlnative extends ADODB_DataDict {
 			$fieldobj = $t;
 			$t = $fieldobj->type;
 		}
-
+		
+	
+		$t = strtoupper($t);
+		
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+		
 		$_typeConversion = array(
 			-155 => 'D',
 			  93 => 'D',
@@ -115,7 +121,15 @@ class ADODB2_mssqlnative extends ADODB_DataDict {
 	function ActualType($meta)
 	{
 		$DATE_TYPE = 'DATETIME';
-
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch(strtoupper($meta)) {
 
 		case 'C': return 'VARCHAR';
diff --git a/datadict/datadict-mysql.inc.php b/datadict/datadict-mysql.inc.php
index a1ee950..9efbba1 100644
--- a/datadict/datadict-mysql.inc.php
+++ b/datadict/datadict-mysql.inc.php
@@ -33,7 +33,7 @@ class ADODB2_mysql extends ADODB_DataDict {
 
 	public $blobAllowsNotNull = true;
 	
-	function MetaType($t,$len=-1,$fieldobj=false)
+	function metaType($t,$len=-1,$fieldobj=false)
 	{
 		
 		if (is_object($t)) {
@@ -44,7 +44,14 @@ class ADODB2_mysql extends ADODB_DataDict {
 		$is_serial = is_object($fieldobj) && $fieldobj->primary_key && $fieldobj->auto_increment;
 
 		$len = -1; // mysql max_length is not accurate
-		switch (strtoupper($t)) {
+			
+		$t = strtoupper($t);
+		
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+		
+		switch ($t) {
+			
 		case 'STRING':
 		case 'CHAR':
 		case 'VARCHAR':
@@ -84,13 +91,27 @@ class ADODB2_mysql extends ADODB_DataDict {
 		case 'SMALLINT': return $is_serial ? 'R' : 'I2';
 		case 'MEDIUMINT': return $is_serial ? 'R' : 'I4';
 		case 'BIGINT':  return $is_serial ? 'R' : 'I8';
-		default: return ADODB_DEFAULT_METATYPE;
+		default: 
+			
+			return ADODB_DEFAULT_METATYPE;
 		}
 	}
 
 	function ActualType($meta)
 	{
-		switch(strtoupper($meta)) {
+		
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+				
+		switch($meta) 
+		{
+		
 		case 'C': return 'VARCHAR';
 		case 'XL':return 'LONGTEXT';
 		case 'X': return 'TEXT';
@@ -114,7 +135,9 @@ class ADODB2_mysql extends ADODB_DataDict {
 
 		case 'F': return 'DOUBLE';
 		case 'N': return 'NUMERIC';
+			
 		default:
+			
 			return $meta;
 		}
 	}
diff --git a/datadict/datadict-oci8.inc.php b/datadict/datadict-oci8.inc.php
index 9a23909..6d2cd24 100644
--- a/datadict/datadict-oci8.inc.php
+++ b/datadict/datadict-oci8.inc.php
@@ -50,7 +50,13 @@ class ADODB2_oci8 extends ADODB_DataDict {
 			$t = $fieldobj->type;
 			$len = $fieldobj->max_length;
 		}
-		switch (strtoupper($t)) {
+		
+		$t = strtoupper($t);
+		
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
+		switch ($t) {
 	 	case 'VARCHAR':
 	 	case 'VARCHAR2':
 		case 'CHAR':
@@ -92,6 +98,15 @@ class ADODB2_oci8 extends ADODB_DataDict {
 
  	function ActualType($meta)
 	{
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch($meta) {
 		case 'C': return 'VARCHAR';
 		case 'X': return $this->typeX;
diff --git a/datadict/datadict-postgres.inc.php b/datadict/datadict-postgres.inc.php
index 17627c4..89bcc0a 100644
--- a/datadict/datadict-postgres.inc.php
+++ b/datadict/datadict-postgres.inc.php
@@ -34,7 +34,7 @@ class ADODB2_postgres extends ADODB_DataDict
 
 	public $blobAllowsDefaultValue = true;
 	public $blobAllowsNotNull = true;
-	
+
 	function metaType($t, $len=-1, $fieldobj=false)
 	{
 		if (is_object($t)) {
@@ -42,16 +42,23 @@ class ADODB2_postgres extends ADODB_DataDict
 			$t = $fieldobj->type;
 			$len = $fieldobj->max_length;
 		}
+
+		$t = strtoupper($t);
+
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
 		$is_serial = is_object($fieldobj) && !empty($fieldobj->primary_key) && !empty($fieldobj->unique) &&
 			!empty($fieldobj->has_default) && substr($fieldobj->default_value,0,8) == 'nextval(';
 
-		switch (strtoupper($t)) {
+		switch ($t) {
+
 			case 'INTERVAL':
 			case 'CHAR':
 			case 'CHARACTER':
 			case 'VARCHAR':
 			case 'NAME':
-	   		case 'BPCHAR':
+			case 'BPCHAR':
 				if ($len <= $this->blobSize) return 'C';
 
 			case 'TEXT':
@@ -94,13 +101,22 @@ class ADODB2_postgres extends ADODB_DataDict
 			case 'REAL':
 				return 'F';
 
-			 default:
-			 	return ADODB_DEFAULT_METATYPE;
+			default:
+				return ADODB_DEFAULT_METATYPE;
 		}
 	}
 
- 	function actualType($meta)
+	function actualType($meta)
 	{
+		$meta = strtoupper($meta);
+
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+
 		switch ($meta) {
 		case 'C': return 'VARCHAR';
 		case 'XL':
@@ -144,7 +160,8 @@ class ADODB2_postgres extends ADODB_DataDict
 		$sql = array();
 		$not_null = false;
 		list($lines,$pkey) = $this->_genFields($flds);
-		$alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' ';
+		$alter = 'ALTER TABLE ' . $tabname . $this->addCol;
+		$alter .= (float)@$this->serverInfo['version'] < 9.6 ? ' ' : ' IF NOT EXISTS ';
 		foreach($lines as $v) {
 			if (($not_null = preg_match('/NOT NULL/i',$v))) {
 				$v = preg_replace('/NOT NULL/i','',$v);
@@ -152,7 +169,7 @@ class ADODB2_postgres extends ADODB_DataDict
 			if (preg_match('/^([^ ]+) .*DEFAULT (\'[^\']+\'|\"[^\"]+\"|[^ ]+)/',$v,$matches)) {
 				list(,$colname,$default) = $matches;
 				$sql[] = $alter . str_replace('DEFAULT '.$default,'',$v);
-				$sql[] = 'UPDATE '.$tabname.' SET '.$colname.'='.$default;
+				$sql[] = 'UPDATE '.$tabname.' SET '.$colname.'='.$default.' WHERE '.$colname.' IS NULL ';
 				$sql[] = 'ALTER TABLE '.$tabname.' ALTER COLUMN '.$colname.' SET DEFAULT ' . $default;
 			} else {
 				$sql[] = $alter . $v;
@@ -168,18 +185,21 @@ class ADODB2_postgres extends ADODB_DataDict
 
 	function dropIndexSQL($idxname, $tabname = NULL)
 	{
-	   return array(sprintf($this->dropIndex, $this->tableName($idxname), $this->tableName($tabname)));
+		return array(sprintf($this->dropIndex, $this->tableName($idxname), $this->tableName($tabname)));
 	}
 
 	/**
 	 * Change the definition of one column
 	 *
-	 * Postgres can't do that on it's own, you need to supply the complete definition of the new table,
-	 * to allow, recreating the table and copying the content over to the new table
-	 * @param string $tabname table-name
-	 * @param string $flds column-name and type for the changed column
-	 * @param string $tableflds complete definition of the new table, eg. for postgres, default ''
-	 * @param array/ $tableoptions options for the new table see CreateTableSQL, default ''
+	 * Postgres can't do that on its own, you need to supply the complete
+	 * definition of the new table, to allow recreating the table and copying
+	 * the content over to the new table.
+	 *
+	 * @param string $tabname      table-name
+	 * @param string $flds         column-name and type for the changed column
+	 * @param string $tableflds    complete definition of the new table, e.g. for postgres, default ''
+	 * @param array  $tableoptions options for the new table {@see CreateTableSQL()}, default ''
+	 *
 	 * @return array with SQL strings
 	 */
 	function alterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
@@ -197,9 +217,9 @@ class ADODB2_postgres extends ADODB_DataDict
 				if ($not_null = preg_match('/NOT NULL/i',$v)) {
 					$v = preg_replace('/NOT NULL/i','',$v);
 				}
-				 // this next block doesn't work - there is no way that I can see to
-				 // explicitly ask a column to be null using $flds
-				else if ($set_null = preg_match('/NULL/i',$v)) {
+				// this next block doesn't work - there is no way that I can see to
+				// explicitly ask a column to be null using $flds
+				elseif ($set_null = preg_match('/NULL/i',$v)) {
 					// if they didn't specify not null, see if they explicitly asked for null
 					// Lookbehind pattern covers the case 'fieldname NULL datatype DEFAULT NULL'
 					// only the first NULL should be removed, not the one specifying
@@ -278,7 +298,7 @@ class ADODB2_postgres extends ADODB_DataDict
 	 * @param string $tabname table-name
 	 * @param string $flds column-name and type for the changed column
 	 * @param string $tableflds complete definition of the new table, eg. for postgres, default ''
-	 * @param array/ $tableoptions options for the new table see CreateTableSQL, default ''
+	 * @param array  $tableoptions options for the new table {@see CreateTableSQL}, default []
 	 * @return array with SQL strings
 	 */
 	function dropColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
@@ -305,7 +325,7 @@ class ADODB2_postgres extends ADODB_DataDict
 	 * @param string $tabname table-name
 	 * @param string $dropflds column-names to drop
 	 * @param string $tableflds complete definition of the new table, eg. for postgres
-	 * @param array/string $tableoptions options for the new table see CreateTableSQL, default ''
+	 * @param array|string $tableoptions options for the new table see CreateTableSQL, default ''
 	 * @return array with SQL strings
 	 */
 	function _recreate_copy_table($tabname, $dropflds, $tableflds, $tableoptions='')
@@ -477,11 +497,11 @@ CREATE [ UNIQUE ] INDEX index_name ON table
 		if (isset($idxoptions['HASH'])) {
 			$s .= 'USING HASH ';
 		}
-		
+
 		if (isset($idxoptions[$this->upperName])) {
 			$s .= $idxoptions[$this->upperName];
 		}
-		
+
 		if (is_array($flds)) {
 			$flds = implode(', ', $flds);
 		}
@@ -514,7 +534,7 @@ CREATE [ UNIQUE ] INDEX index_name ON table
 		}
 		return $ftype;
 	}
-	
+
 	function changeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFlds=false)
 	{
 		global $ADODB_FETCH_MODE;
@@ -524,18 +544,18 @@ CREATE [ UNIQUE ] INDEX index_name ON table
 		if ($this->connection->fetchMode !== false) {
 			$savem = $this->connection->setFetchMode(false);
 		}
-		
+
 		// check table exists
 		$save_handler = $this->connection->raiseErrorFn;
 		$this->connection->raiseErrorFn = '';
 		$cols = $this->metaColumns($tablename);
 		$this->connection->raiseErrorFn = $save_handler;
-		
+
 		if (isset($savem)) {
 			$this->connection->setFetchMode($savem);
 		}
 		$ADODB_FETCH_MODE = $save;
-		
+
 		$sqlResult=array();
 		if ( empty($cols)) {
 			$sqlResult=$this->createTableSQL($tablename, $flds, $tableoptions);
@@ -543,7 +563,7 @@ CREATE [ UNIQUE ] INDEX index_name ON table
 			$sqlResultAdd = $this->addColumnSQL($tablename, $flds);
 			$sqlResultAlter = $this->alterColumnSQL($tablename, $flds, '', $tableoptions);
 			$sqlResult = array_merge((array)$sqlResultAdd, (array)$sqlResultAlter);
-			
+
 			if ($dropOldFlds) {
 				// already exists, alter table instead
 				list($lines,$pkey,$idxs) = $this->_genFields($flds);
@@ -558,7 +578,7 @@ CREATE [ UNIQUE ] INDEX index_name ON table
 					}
 				}
 			}
-			
+
 		}
 		return $sqlResult;
 	}
diff --git a/datadict/datadict-sapdb.inc.php b/datadict/datadict-sapdb.inc.php
index 20c16aa..c469800 100644
--- a/datadict/datadict-sapdb.inc.php
+++ b/datadict/datadict-sapdb.inc.php
@@ -30,6 +30,15 @@ class ADODB2_sapdb extends ADODB_DataDict {
 
  	function ActualType($meta)
 	{
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch($meta) {
 		case 'C': return 'VARCHAR';
 		case 'XL':
@@ -65,6 +74,12 @@ class ADODB2_sapdb extends ADODB_DataDict {
 			$t = $fieldobj->type;
 			$len = $fieldobj->max_length;
 		}
+		
+		$t = strtoupper($t);
+		
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
 		static $maxdb_type2adodb = array(
 			'VARCHAR'	=> 'C',
 			'CHARACTER'	=> 'C',
diff --git a/datadict/datadict-sqlite.inc.php b/datadict/datadict-sqlite.inc.php
index 942927f..d565f88 100644
--- a/datadict/datadict-sqlite.inc.php
+++ b/datadict/datadict-sqlite.inc.php
@@ -35,6 +35,16 @@ class ADODB2_sqlite extends ADODB_DataDict {
     
 	function ActualType($meta)
 	{
+		
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch(strtoupper($meta)) {
 		case 'C': return 'VARCHAR'; //  TEXT , TEXT affinity
 		case 'XL':return 'LONGTEXT'; //  TEXT , TEXT affinity
diff --git a/datadict/datadict-sybase.inc.php b/datadict/datadict-sybase.inc.php
index e565f8e..d6573df 100644
--- a/datadict/datadict-sybase.inc.php
+++ b/datadict/datadict-sybase.inc.php
@@ -35,8 +35,15 @@ class ADODB2_sybase extends ADODB_DataDict {
 			$len = $fieldobj->max_length;
 		}
 
+		$t = strtoupper($t);
+		
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
 		$len = -1; // mysql max_length is not accurate
-		switch (strtoupper($t)) {
+
+		switch ($t) {
+
 
 		case 'INT':
 		case 'INTEGER': return  'I';
@@ -53,6 +60,15 @@ class ADODB2_sybase extends ADODB_DataDict {
 
 	function ActualType($meta)
 	{
+		$meta = strtoupper($meta);
+		
+		/*
+		* Add support for custom meta types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (isset($this->connection->customMetaTypes[$meta]))
+			return $this->connection->customMetaTypes[$meta]['actual'];
+		
 		switch(strtoupper($meta)) {
 		case 'C': return 'VARCHAR';
 		case 'XL':
diff --git a/debian/changelog b/debian/changelog
index 2f93b13..dc07f0a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-libphp-adodb (5.21.4-2) UNRELEASED; urgency=medium
+libphp-adodb (5.22.6-1) UNRELEASED; urgency=medium
 
   [ Debian Janitor ]
   * Remove constraints unnecessary since buster:
@@ -8,7 +8,11 @@ libphp-adodb (5.21.4-2) UNRELEASED; urgency=medium
   * d/control: Added Multi-Arch: foreign.
   * d/changelog: Updated comment about #1004376.
 
- -- Jean-Michel Vourgère <nirgal@debian.org>  Mon, 14 Mar 2022 11:30:41 +0100
+  [ Debian Janitor ]
+  * New upstream release.
+  * New upstream release.
+
+ -- Jean-Michel Vourgère <nirgal@debian.org>  Mon, 10 Jul 2023 00:49:17 -0000
 
 libphp-adodb (5.21.4-1) unstable; urgency=medium
 
diff --git a/docs/changelog.md b/docs/changelog.md
index 5298b3d..121e102 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -14,8 +14,279 @@ Older changelogs:
 
 --------------------------------------------------------------------------------
 
-## [5.21.4] - 2022-01-22
-## [5.20.21] - 2022-01-22
+## [5.22.6] - 2023-06-11
+
+### Deprecated
+
+- Date/Time Library
+  [#970](https://github.com/ADOdb/ADOdb/issues/970)
+
+### Fixed
+
+- Creation of dynamic property deprecated warning with PHP 8.2
+  [#954](https://github.com/ADOdb/ADOdb/issues/954)
+  [#975](https://github.com/ADOdb/ADOdb/issues/975)
+- Remove unused oldProvider property in _rs2serialize()
+  [#957](https://github.com/ADOdb/ADOdb/issues/957)
+- Fix ADOConnection::execute() documentation of return type
+  [#964](https://github.com/ADOdb/ADOdb/issues/964)
+- Define _query() method in ADOConnection base class
+  [#966](https://github.com/ADOdb/ADOdb/issues/966)
+- Restore rs2html() $htmlspecialchars param behavior
+  [#968](https://github.com/ADOdb/ADOdb/issues/968)
+- adodb_throw() does not respect @ operator on PHP 8
+  [#981](https://github.com/ADOdb/ADOdb/issues/981)
+- loadbalancer: PHP 8.2 warnings
+  [#951](https://github.com/ADOdb/ADOdb/issues/951)
+- mysql: Fail connection if native driver (mysqlnd) is not available
+  [#967](https://github.com/ADOdb/ADOdb/issues/967)
+- pgsql: Fix PHP 8.1 deprecated warning
+  [#956](https://github.com/ADOdb/ADOdb/issues/956)
+- pgsql: avoid Insert_ID() failing when lastval() is not set
+  [#978](https://github.com/ADOdb/ADOdb/issues/978)
+
+
+## [5.22.5] - 2023-04-03
+
+### Removed
+
+- Obsolete ADOConnection::$databaseName property
+  [#932](https://github.com/ADOdb/ADOdb/issues/932)
+- Dead code related to safe_mode
+  [#934](https://github.com/ADOdb/ADOdb/issues/934)
+- pdo: remove unnecessary methods _init() and _affectedrows()
+  [#935](https://github.com/ADOdb/ADOdb/issues/935)
+
+### Fixed
+
+- ADODB_FETCH_DEFAULT should be treated as ASSOC in getAssoc()
+  [#886](https://github.com/ADOdb/ADOdb/issues/886)
+- Allow dynamic properties for ADOFieldObject class
+  [#906](https://github.com/ADOdb/ADOdb/issues/906)
+- Fix autoExecute() $where parameter type
+  [#915](https://github.com/ADOdb/ADOdb/issues/915)
+- Creation of dynamic property deprecated warning in PHP 8.2
+  [#904](https://github.com/ADOdb/ADOdb/issues/904)
+  [#907](https://github.com/ADOdb/ADOdb/issues/907)
+  [#908](https://github.com/ADOdb/ADOdb/issues/908)
+  [#909](https://github.com/ADOdb/ADOdb/issues/909)
+  [#911](https://github.com/ADOdb/ADOdb/issues/911)
+  [#912](https://github.com/ADOdb/ADOdb/issues/912)
+  [#913](https://github.com/ADOdb/ADOdb/issues/913)
+  [#917](https://github.com/ADOdb/ADOdb/issues/917)
+  [#926](https://github.com/ADOdb/ADOdb/issues/926)
+  [#933](https://github.com/ADOdb/ADOdb/issues/933)
+  [#935](https://github.com/ADOdb/ADOdb/issues/935)
+- Partially-supported callable deprecated warning in PHP 8.2
+  [#928](https://github.com/ADOdb/ADOdb/issues/928)
+- Passing null to non-nullable parameter is deprecated in PHP 8.1
+  [#938](https://github.com/ADOdb/ADOdb/issues/938)
+- Define adodbFetchMode property in base ADORecordSet class
+  [#923](https://github.com/ADOdb/ADOdb/issues/923)
+- mysql: uncaught exception calling execute() with empty SQL on PHP 8
+  [#945](https://github.com/ADOdb/ADOdb/issues/945)
+- oci8: Replace obsolete oci_free_cursor() function alias
+  [#937](https://github.com/ADOdb/ADOdb/issues/937)
+- pdo: Move setTransactionMode() to ADODB_pdo_sqlsrv class
+  [#939](https://github.com/ADOdb/ADOdb/issues/939)
+- pgsql: fix "column already exists" error when adding a new column
+  [#897](https://github.com/ADOdb/ADOdb/issues/897)
+- pgsql: undefined array key in metaIndexes() when column is an expression
+  [#940](https://github.com/ADOdb/ADOdb/issues/940)
+- pgsql: revert non-functional "Noblob optimization"
+  [#112](https://github.com/ADOdb/ADOdb/issues/112)
+- session: full table scan when accessing sessions table on MySQL
+  [#941](https://github.com/ADOdb/ADOdb/issues/941)
+- session: inconsistent DB provider check
+  [#943](https://github.com/ADOdb/ADOdb/issues/943)
+
+
+## [5.22.4] - 2022-10-28
+
+### Fixed
+
+- adodb_strip_order_by() throws deprecated warnings on PHP 8.1 
+  [#869](https://github.com/ADOdb/ADOdb/issues/869)
+- adodb_strip_order_by() shouldn't strip clause from subqueries 
+  [#870](https://github.com/ADOdb/ADOdb/issues/870)
+- mssql: Affected_Rows() not returning correct value
+  [#895](https://github.com/ADOdb/ADOdb/issues/895)
+- mysqli: Fix mysqli_result could not be converted to int
+  [#867](https://github.com/ADOdb/ADOdb/issues/867)
+- mysqli: regression on errorMsg()/errorCode()
+  [#872](https://github.com/ADOdb/ADOdb/issues/872)
+
+
+## [5.22.3] - 2022-09-06
+
+### Fixed
+
+- alterColumnSql() and changeTableSQL() produce different SQL
+  [#124](https://github.com/ADOdb/ADOdb/issues/124)
+  [#383](https://github.com/ADOdb/ADOdb/issues/383)
+  [#865](https://github.com/ADOdb/ADOdb/issues/865)
+- Fix PHP 8.1 deprecated warning in GetUpdateSQL()
+  [#844](https://github.com/ADOdb/ADOdb/issues/844)
+- Fix PHP 8.1 deprecated warning in tohtml.inc.php
+  [#850](https://github.com/ADOdb/ADOdb/issues/850)
+- Fix str_repeat() error in _adodb_backtrace()
+  [#852](https://github.com/ADOdb/ADOdb/issues/852)
+- firebird/ibase: Fix uncaught TypeError
+  [#858](https://github.com/ADOdb/ADOdb/issues/858)
+- mssql: Fix undefined variable when closing connection
+  [#835](https://github.com/ADOdb/ADOdb/issues/835)
+- mssql: insert_id returns false if value is out of range
+  [#853](https://github.com/ADOdb/ADOdb/issues/853)
+- mysql: Fix regression with portable bind arrays
+  [#838](https://github.com/ADOdb/ADOdb/issues/838)
+- mysql: Fix errorMsg() / errorNo() on MySQL 8
+  [#842](https://github.com/ADOdb/ADOdb/issues/842)
+- oci8po: Replace deprecated functions aliases
+  [#862](https://github.com/ADOdb/ADOdb/issues/862)
+- pdo: Fix uncaught TypeError on PHP 8 
+  [#840](https://github.com/ADOdb/ADOdb/issues/840)
+- pgsql: check for dummy query Id before closing recordset
+  [#848](https://github.com/ADOdb/ADOdb/issues/848)
+- active record: fix changing case of class properties in Native mode
+  [#837](https://github.com/ADOdb/ADOdb/issues/837)
+
+
+## [5.22.2] - 2022-05-08
+
+### Fixed
+
+- mssql: Automatic conversion of false to array is deprecated in PHP 8.1
+  [#829](https://github.com/ADOdb/ADOdb/issues/829)
+- mysql: Affected_Rows() not returning correct value
+  [#820](https://github.com/ADOdb/ADOdb/issues/820)
+- mysql: uncaught ValueError exception calling execute() with an empty array
+  [#832](https://github.com/ADOdb/ADOdb/issues/832)
+- pgsql: Affected_Rows() always returns false on PHP 8.1
+  [#833](https://github.com/ADOdb/ADOdb/issues/833)
+
+
+## [5.22.1] - 2022-03-30
+
+### Removed
+
+- Legacy mysql, mysqlpo and mysqlt drivers
+  [#804](https://github.com/ADOdb/ADOdb/issues/804)
+
+### Fixed
+
+- firebird: undefined offset PHP notice in _fetchField()
+  [#808](https://github.com/ADOdb/ADOdb/issues/808)
+- firebird: PHP deprecation warning in _blobDecode()
+  [#811](https://github.com/ADOdb/ADOdb/issues/811)
+- firebird: PHP notice when executing query with empty array
+  [#812](https://github.com/ADOdb/ADOdb/issues/812)
+- firebird: undefined array key with uppercase columns
+  [#813](https://github.com/ADOdb/ADOdb/issues/813)
+- mysql: regression on setConnectionParameter()
+  [#803](https://github.com/ADOdb/ADOdb/issues/803)
+- mysql: regression on bulk binding
+  [#806](https://github.com/ADOdb/ADOdb/issues/806)
+- oci8: fix PHP 8.1 array deprecation warning
+  [#817](https://github.com/ADOdb/ADOdb/issues/817)
+- memcache: weighted servers not connecting
+  [#807](https://github.com/ADOdb/ADOdb/issues/807)
+- memcache: $memCacheCompress option ignored by memcached
+  [#823](https://github.com/ADOdb/ADOdb/issues/823)
+- memcache: use default port in server controller template
+  [#824](https://github.com/ADOdb/ADOdb/issues/824)
+- xml PHP Notice in dbData::create()
+  [#822](https://github.com/ADOdb/ADOdb/issues/822)
+
+
+## [5.22.0] - 2022-02-08
+
+### Added
+
+- Support for custom MetaTypes, e.g. JSON or GEOMETRY
+  [#602](https://github.com/ADOdb/ADOdb/issues/602)
+  [#626](https://github.com/ADOdb/ADOdb/issues/626)
+  [#649](https://github.com/ADOdb/ADOdb/issues/649)
+- Use of weighted server groups with Memcached
+  [#676](https://github.com/ADOdb/ADOdb/issues/676)
+- mssql: implement offsetDate() method
+  [#698](https://github.com/ADOdb/ADOdb/issues/698)
+- oci8: new ADOConnection::releaseStatement() method
+  [#770](https://github.com/ADOdb/ADOdb/issues/770)
+- sqlite3 performance monitor stub
+  [#661](https://github.com/ADOdb/ADOdb/issues/661)
+- sqlite: support blob handling
+  [#702](https://github.com/ADOdb/ADOdb/issues/702)
+
+### Changed
+
+- firebird: complete overhaul to support latest PHP drivers
+  [#710](https://github.com/ADOdb/ADOdb/issues/710)
+- mssql: Refactor _fetchField() method 
+  [#725](https://github.com/ADOdb/ADOdb/issues/725)
+- mysql: Support bound variable statements
+  [#655](https://github.com/ADOdb/ADOdb/issues/655)
+- pgsql: missing standard datatypes
+  [#782](https://github.com/ADOdb/ADOdb/issues/782)
+- xml: add field comments
+  [#732](https://github.com/ADOdb/ADOdb/issues/732)
+- loadbalancer: support calling a function upon connection
+  [#784](https://github.com/ADOdb/ADOdb/issues/784)
+- Code cleanup: PHPDoc, code style, whitespace, PHPStan errors, etc.
+  [#774](https://github.com/ADOdb/ADOdb/issues/774)
+
+### Deprecated
+
+- Compatibility with PHP < 7.2
+  [#797](https://github.com/ADOdb/ADOdb/issues/797)
+- Database Replication add-on
+  [#780](https://github.com/ADOdb/ADOdb/issues/780)
+
+### Removed
+
+- Compatibility with PHP 5.x
+  [#797](https://github.com/ADOdb/ADOdb/issues/797)
+- Transpose() function and assorted sub-functions
+  [#586](https://github.com/ADOdb/ADOdb/issues/586)
+- "proxy" server and client scripts
+  [#680](https://github.com/ADOdb/ADOdb/issues/680)
+
+### Fixed
+
+- metaIndexes does not return primary key correctly
+  [#656](https://github.com/ADOdb/ADOdb/issues/656)
+- Uniformize ADOrecordSet::__construct() parameters
+  [#772](https://github.com/ADOdb/ADOdb/issues/772)
+- Prevent PHP warning when throwing exception with P1 or P2 parameter as array
+  [#783](https://github.com/ADOdb/ADOdb/issues/783)
+- $dsnType property not defined before use
+  [#789](https://github.com/ADOdb/ADOdb/issues/789)
+- mysql: Update socket and client flags for ssl
+  [#622](https://github.com/ADOdb/ADOdb/issues/622)
+- mysql: Handle tables that are reserved words
+- [#759](https://github.com/ADOdb/ADOdb/issues/759)
+- pgsql: prevent AddColumnSQL() from updating existing values when default is changed
+  [#635](https://github.com/ADOdb/ADOdb/issues/635)
+- pgsql: Refactored _fixblobs() and replaced it with new _prepFields() method
+  [#767](https://github.com/ADOdb/ADOdb/issues/767)
+- pgsql: Incorrect pg_execute() api calls
+  [#768](https://github.com/ADOdb/ADOdb/issues/768)
+- pgsql: blobDelete() could silently fail with multiple connections
+  [#769](https://github.com/ADOdb/ADOdb/issues/769)
+- pdo: ensure bound statements are correctly formatted
+  [#695](https://github.com/ADOdb/ADOdb/issues/695)
+- perf: fix invalid SQL
+  [#753](https://github.com/ADOdb/ADOdb/issues/753)
+- sqlite: driver returns incorrect time when using $sysTimeStamp
+  [#697](https://github.com/ADOdb/ADOdb/issues/697)
+- sqlite: undeclared connection property
+  [#713](https://github.com/ADOdb/ADOdb/issues/713)
+- xml: Undefined array key error
+  [#775](https://github.com/ADOdb/ADOdb/issues/775)
+- memcache: library does not initialize correctly
+  [#788](https://github.com/ADOdb/ADOdb/issues/788)
+
+
+## [5.21.4] and [5.20.21] - 2022-01-22
 
 ### Fixed
 
@@ -72,6 +343,8 @@ Older changelogs:
   [#715](https://github.com/ADOdb/ADOdb/issues/715)
 - Incorrect handling of $ADODB_QUOTE_FIELDNAMES = true
   [#721](https://github.com/ADOdb/ADOdb/issues/721)
+- array to string conversion in adodb_debug_execute()
+  [#737](https://github.com/ADOdb/ADOdb/issues/737)
 - db2: fix columns always returned in lowercase
   [#719](https://github.com/ADOdb/ADOdb/issues/719)
 - PDO: Bind parameters fail if sent in associative array
@@ -412,9 +685,9 @@ Includes all fixes from 5.20.19.
 
 ## [5.20.16] - 2020-01-12
 
--### Fixed
+### Fixed
 
- mssql: queries are not correctly closed
+- mssql: queries are not correctly closed
   [#590](https://github.com/ADOdb/ADOdb/issues/590)
 
 
@@ -1143,7 +1416,13 @@ Released together with [v4.95](changelog_v4.x.md#495---17-may-2007)
 - Adodb5 version,more error checking code now will use exceptions if available.
 
 
-[Unreleased]: https://github.com/adodb/adodb/compare/v5.21.4...hotfix/5.21
+[5.22.6]: https://github.com/adodb/adodb/compare/v5.22.5...v5.22.6
+[5.22.5]: https://github.com/adodb/adodb/compare/v5.22.4...v5.22.5
+[5.22.4]: https://github.com/adodb/adodb/compare/v5.22.3...v5.22.4
+[5.22.3]: https://github.com/adodb/adodb/compare/v5.22.2...v5.22.3
+[5.22.2]: https://github.com/adodb/adodb/compare/v5.22.1...v5.22.2
+[5.22.1]: https://github.com/adodb/adodb/compare/v5.22.0...v5.22.1
+[5.22.0]: https://github.com/adodb/adodb/compare/v5.21.4...v5.22.0
 
 [5.21.4]: https://github.com/adodb/adodb/compare/v5.21.3...v5.21.4
 [5.21.3]: https://github.com/adodb/adodb/compare/v5.21.2...v5.21.3
diff --git a/drivers/adodb-ado.inc.php b/drivers/adodb-ado.inc.php
index 67a032d..df95c69 100644
--- a/drivers/adodb-ado.inc.php
+++ b/drivers/adodb-ado.inc.php
@@ -208,10 +208,6 @@ class ADODB_ado extends ADOConnection {
 		return empty($arr) ? $false : $arr;
 	}
 
-
-
-
-	/* returns queryID or false */
 	function _query($sql,$inputarr=false)
 	{
 
@@ -505,6 +501,9 @@ class ADORecordSet_ado extends ADORecordSet {
 			$len = $fieldobj->max_length;
 		}
 
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
 		if (!is_numeric($t)) return $t;
 
 		switch ($t) {
diff --git a/drivers/adodb-ado5.inc.php b/drivers/adodb-ado5.inc.php
index f673d09..04b45ab 100644
--- a/drivers/adodb-ado5.inc.php
+++ b/drivers/adodb-ado5.inc.php
@@ -233,7 +233,6 @@ class ADODB_ado extends ADOConnection {
 		return $arr;
 	}
 
-	/* returns queryID or false */
 	function _query($sql,$inputarr=false)
 	{
 		try { // In PHP5, all COM errors are exceptions, so to maintain old behaviour...
@@ -546,7 +545,13 @@ class ADORecordSet_ado extends ADORecordSet {
 			$len = $fieldobj->max_length;
 		}
 
-		if (!is_numeric($t)) return $t;
+		$t = strtoupper($t);
+
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
+		if (!is_numeric($t))
+			return $t;
 
 		switch ($t) {
 		case 0:
diff --git a/drivers/adodb-ads.inc.php b/drivers/adodb-ads.inc.php
index b9d4adb..16eec97 100644
--- a/drivers/adodb-ads.inc.php
+++ b/drivers/adodb-ads.inc.php
@@ -97,7 +97,7 @@ class ADODB_ads extends ADOConnection
 			$this->_connectionID = ads_connect($argDSN, $argUsername, $argPassword, $this->curmode);
 		}
 		$this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
-		if (isset($this->connectStmt)) {
+		if ($this->connectStmt) {
 			$this->Execute($this->connectStmt);
 		}
 
@@ -127,7 +127,7 @@ class ADODB_ads extends ADOConnection
 		if ($this->_connectionID && $this->autoRollback) {
 			@ads_rollback($this->_connectionID);
 		}
-		if (isset($this->connectStmt)) {
+		if ($this->connectStmt) {
 			$this->Execute($this->connectStmt);
 		}
 
@@ -564,7 +564,6 @@ class ADODB_ads extends ADOConnection
 		return array($sql, $stmt, false);
 	}
 
-	/* returns queryID or false */
 	function _query($sql, $inputarr = false)
 	{
 		$last_php_error = $this->resetLastError();
diff --git a/drivers/adodb-csv.inc.php b/drivers/adodb-csv.inc.php
index 8a59626..e6694d9 100644
--- a/drivers/adodb-csv.inc.php
+++ b/drivers/adodb-csv.inc.php
@@ -199,7 +199,7 @@ class ADODB_csv extends ADOConnection {
 	}
 } // class
 
-class ADORecordset_csv extends ADORecordset {
+class ADORecordset_csv extends ADORecordSet {
 
 	function _close()
 	{
diff --git a/drivers/adodb-db2.inc.php b/drivers/adodb-db2.inc.php
index 8f616fa..e214d2d 100644
--- a/drivers/adodb-db2.inc.php
+++ b/drivers/adodb-db2.inc.php
@@ -77,16 +77,6 @@ class ADODB_db2 extends ADOConnection {
 	 */
 	public $nameQuote = '"';
 
-	/*
-	 * Executed after successful connection
-	 */
-	public $connectStmt = '';
-
-	/*
-	 * Holds the current database name
-	 */
-	private $databaseName = '';
-
 	/*
 	 * Holds information about the stored procedure request
 	 * currently being built
@@ -113,7 +103,6 @@ class ADODB_db2 extends ADOConnection {
 
 	private function doDB2Connect($argDSN, $argUsername, $argPassword, $argDatabasename, $persistent=false)
 	{
-		global $php_errormsg;
 
 		if (!function_exists('db2_connect')) {
 			ADOConnection::outp("DB2 extension not installed.");
@@ -185,7 +174,6 @@ class ADODB_db2 extends ADOConnection {
 												null,
 												$db2Options);
 
-		$php_errormsg = '';
 
 		$this->_errorMsg = @db2_conn_errormsg();
 
@@ -209,7 +197,6 @@ class ADODB_db2 extends ADOConnection {
 	private function unpackParameters($argDSN, $argUsername, $argPassword, $argDatabasename)
 	{
 
-		global $php_errormsg;
 
 		$connectionParameters = array('dsn'=>'',
 									  'uid'=>'',
@@ -260,7 +247,7 @@ class ADODB_db2 extends ADOConnection {
 				$errorMessage = 'Supply uncatalogued connection parameters ';
 				$errorMessage.= 'in either the database or DSN arguments, ';
 				$errorMessage.= 'but not both';
-				$php_errormsg = $errorMessage;
+
 				if ($this->debug)
 					ADOConnection::outp($errorMessage);
 				return null;
@@ -285,7 +272,7 @@ class ADODB_db2 extends ADOConnection {
 			{
 				$errorMessage = 'For uncatalogued connections, provide ';
 				$errorMessage.= 'both UID and PWD in the connection string';
-				$php_errormsg = $errorMessage;
+
 				if ($this->debug)
 					ADOConnection::outp($errorMessage);
 				return null;
@@ -310,7 +297,7 @@ class ADODB_db2 extends ADOConnection {
 			}
 			elseif ($argDatabasename)
 			{
-				$this->databaseName = $argDatabasename;
+				$this->database = $argDatabasename;
 				$argDSN .= ';database=' . $argDatabasename;
 				$argDatabasename = '';
 				$useCataloguedConnection = false;
@@ -320,7 +307,7 @@ class ADODB_db2 extends ADOConnection {
 			{
 				$errorMessage = 'Uncatalogued connection parameters ';
 				$errorMessage.= 'must contain a database= argument';
-				$php_errormsg = $errorMessage;
+
 				if ($this->debug)
 					ADOConnection::outp($errorMessage);
 				return null;
@@ -350,9 +337,9 @@ class ADODB_db2 extends ADOConnection {
 		}
 
 		if ($argDatabasename)
-			$this->databaseName = $argDatabasename;
-		elseif (!$this->databaseName)
-			$this->databaseName = $this->getDatabasenameFromDsn($argDSN);
+			$this->database = $argDatabasename;
+		elseif (!$this->database)
+			$this->database = $this->getDatabasenameFromDsn($argDSN);
 
 
 		$connectionParameters = array('dsn'=>$argDSN,
@@ -684,16 +671,17 @@ class ADODB_db2 extends ADOConnection {
 	}
 
 	/**
-	 * returns assoc array where keys are tables, and values are foreign keys
+	 * Returns a list of Foreign Keys associated with a specific table.
 	 *
-	 * @param	string	$table
-	 * @param	string	$owner		[optional][discarded]
-	 * @param	bool	$upper		[optional][discarded]
-	 * @param	bool	$associative[optional][discarded]
+	 * @param string $table
+	 * @param string $owner       discarded
+	 * @param bool   $upper       discarded
+	 * @param bool   $associative discarded
 	 *
-	 * @return	mixed[]			Array of foreign key information
+	 * @return string[]|false An array where keys are tables, and values are foreign keys;
+	 *                        false if no foreign keys could be found.
 	 */
-	public function metaForeignKeys($table, $owner = FALSE, $upper = FALSE, $asociative = FALSE )
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 
 		global $ADODB_FETCH_MODE;
@@ -1005,7 +993,7 @@ class ADODB_db2 extends ADOConnection {
 	  */
 	public function metaDatabases(){
 
-		$dbName = $this->getMetaCasedValue($this->databaseName);
+		$dbName = $this->getMetaCasedValue($this->database);
 
 		return (array)$dbName;
 
@@ -1582,13 +1570,6 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2
 	 */
 	function _query(&$sql,$inputarr=false)
 	{
-
-		GLOBAL $php_errormsg;
-
-		if (isset($php_errormsg))
-			$php_errormsg = '';
-		$this->_error = '';
-
 		$db2Options = array();
 		/*
 		 * Use DB2 Internal case handling for best speed
@@ -1622,7 +1603,12 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2
 
 				if ($stmtid == false)
 				{
-					$this->_errorMsg = isset($php_errormsg) ? $php_errormsg : '';
+					$this->_errorMsg  = @db2_stmt_errormsg();
+					$this->_errorCode = @db2_stmt_error();
+
+					if ($this->debug)
+						ADOConnection::outp($this->_errorMsg);
+
 					return false;
 				}
 			}
@@ -1996,14 +1982,13 @@ class ADORecordSet_db2 extends ADORecordSet {
 		$ok = @db2_free_result($this->_queryID);
 		if (!$ok)
 		{
-			$this->_errorMsg  = @db2_stmt_errormsg($this->_queryId);
-			$this->_errorCode = @db2_stmt_error();
+			$this->connection->_errorMsg  = @db2_stmt_errormsg($this->_queryID);
+			$this->connection->_errorCode = @db2_stmt_error();
 
 			if ($this->debug)
-				ADOConnection::outp($this->_errorMsg);
+				ADOConnection::outp($this->connection->_errorMsg);
 			return false;
 		}
-
 	}
 
 }
diff --git a/drivers/adodb-fbsql.inc.php b/drivers/adodb-fbsql.inc.php
index 0fb895a..64913bc 100644
--- a/drivers/adodb-fbsql.inc.php
+++ b/drivers/adodb-fbsql.inc.php
@@ -134,7 +134,6 @@ class ADODB_fbsql extends ADOConnection {
 	}
 
 
-	// returns queryID or false
 	function _query($sql,$inputarr=false)
 	{
 		return fbsql_query("$sql;",$this->_connectionID);
@@ -232,8 +231,15 @@ class ADORecordSet_fbsql extends ADORecordSet{
 			$t = $fieldobj->type;
 			$len = $fieldobj->max_length;
 		}
+
+		$t = strtoupper($t);
+
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return $this->connection->customActualTypes[$t];
+
 		$len = -1; // fbsql max_length is not accurate
-		switch (strtoupper($t)) {
+
+		switch ($t) {
 		case 'CHARACTER':
 		case 'CHARACTER VARYING':
 		case 'BLOB':
diff --git a/drivers/adodb-firebird.inc.php b/drivers/adodb-firebird.inc.php
index 2fafbe4..db3cca5 100644
--- a/drivers/adodb-firebird.inc.php
+++ b/drivers/adodb-firebird.inc.php
@@ -19,6 +19,10 @@
  *
  * @copyright 2000-2013 John Lim
  * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
+ *
+ * Driver was cloned from Interbase, so there's quite a lot of duplicated code
+ * @noinspection DuplicatedCode
+ * @noinspection PhpUnused
  */
 
 // security - hide paths
@@ -35,39 +39,113 @@ class ADODB_firebird extends ADOConnection {
 	var $fmtTimeStamp = "'Y-m-d, H:i:s'";
 	var $concat_operator='||';
 	var $_transactionID;
-	var $metaTablesSQL = "select lower(rdb\$relation_name) from rdb\$relations where rdb\$relation_name not like 'RDB\$%'";
+
+	public $metaTablesSQL = "SELECT LOWER(rdb\$relation_name) FROM rdb\$relations";
 	//OPN STUFF start
+
 	var $metaColumnsSQL = "select lower(a.rdb\$field_name), a.rdb\$null_flag, a.rdb\$default_source, b.rdb\$field_length, b.rdb\$field_scale, b.rdb\$field_sub_type, b.rdb\$field_precision, b.rdb\$field_type from rdb\$relation_fields a, rdb\$fields b where a.rdb\$field_source = b.rdb\$field_name and a.rdb\$relation_name = '%s' order by a.rdb\$field_position asc";
 	//OPN STUFF end
-	var $ibasetrans;
+
+	public $_genSeqSQL = "CREATE SEQUENCE %s START WITH %s";
+
+	public $_dropSeqSQL = "DROP SEQUENCE %s";
+
 	var $hasGenID = true;
 	var $_bindInputArray = true;
-	var $buffers = 0;
-	var $dialect = 3;
 	var $sysDate = "cast('TODAY' as timestamp)";
 	var $sysTimeStamp = "CURRENT_TIMESTAMP"; //"cast('NOW' as timestamp)";
 	var $ansiOuter = true;
 	var $hasAffectedRows = true;
 	var $poorAffectedRows = false;
 	var $blobEncodeType = 'C';
-	var $role = false;
+	/*
+	* firebird custom optionally specifies the user role
+	*/
+	public $role = false;
+	/*
+	* firebird custom optionally specifies the connection buffers
+	*/
+	public $buffers = 0;
+
+	/*
+	* firebird custom optionally specifies database dialect
+	*/
+	public $dialect = 3;
+
 	var $nameQuote = '';		/// string to use to quote identifiers and names
 
 	function __construct()
 	{
-	// Ignore IBASE_DEFAULT we want a more practical transaction!
-	//	if (defined('IBASE_DEFAULT')) $this->ibasetrans = IBASE_DEFAULT;
-	//	else
-		$this->ibasetrans = IBASE_WAIT | IBASE_REC_VERSION | IBASE_COMMITTED;
+		parent::__construct();
+		$this->setTransactionMode('');
 	}
 
+	/**
+	 * Sets the isolation level of a transaction.
+	 *
+	 * The default behavior is a more practical IBASE_WAIT | IBASE_REC_VERSION | IBASE_COMMITTED
+	 * instead of IBASE_DEFAULT
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:settransactionmode
+	 *
+	 * @param string $transaction_mode The transaction mode to set.
+	 *
+	 * @return void
+	 */
+	public function setTransactionMode($transaction_mode)
+	{
+		$this->_transmode = $transaction_mode;
+
+		if (empty($transaction_mode)) {
+			$this->_transmode = IBASE_WAIT | IBASE_REC_VERSION | IBASE_COMMITTED;
+		}
 
-	// returns true or false
-	function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$persist=false)
+	}
+
+	/**
+	 * Connect to a database.
+	 *
+	 * @todo add: parameter int $port, parameter string $socket
+	 *
+	 * @param string|null $argHostname (Optional) The host to connect to.
+	 * @param string|null $argUsername (Optional) The username to connect as.
+	 * @param string|null $argPassword (Optional) The password to connect with.
+	 * @param string|null $argDatabasename (Optional) The name of the database to start in when connected.
+	 * @param bool $persist (Optional) Whether or not to use a persistent connection.
+	 *
+	 * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension
+	 * isn't currently loaded.
+	 */
+	public function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$persist=false)
 	{
-		if (!function_exists('fbird_pconnect')) return null;
-		if ($argDatabasename) $argHostname .= ':'.$argDatabasename;
+		if (!function_exists('fbird_pconnect'))
+			return null;
+
+		if ($argDatabasename)
+			$argHostname .= ':'.$argDatabasename;
+
 		$fn = ($persist) ? 'fbird_pconnect':'fbird_connect';
+
+		/*
+		* Now merge in the standard connection parameters setting
+		*/
+		foreach ($this->connectionParameters as $options)
+		{
+			foreach($options as $k=>$v)
+			{
+				switch($k){
+				case 'role':
+					$this->role = $v;
+					break;
+				case 'dialect':
+					$this->dialect = $v;
+					break;
+				case 'buffers':
+					$this->buffers = $v;
+				}
+			}
+		}
+
 		if ($this->role)
 			$this->_connectionID = $fn($argHostname,$argUsername,$argPassword,
 					$this->charSet,$this->buffers,$this->dialect,$this->role);
@@ -75,40 +153,39 @@ class ADODB_firebird extends ADOConnection {
 			$this->_connectionID = $fn($argHostname,$argUsername,$argPassword,
 					$this->charSet,$this->buffers,$this->dialect);
 
-		if ($this->dialect != 1) { // http://www.ibphoenix.com/ibp_60_del_id_ds.html
-			$this->replaceQuote = "''";
+		if ($this->dialect == 1) { // http://www.ibphoenix.com/ibp_60_del_id_ds.html
+			$this->replaceQuote = "";
 		}
 		if ($this->_connectionID === false) {
-			$this->_handleerror();
+			$this->_handleError();
 			return false;
 		}
 
-		// PHP5 change.
-		if (function_exists('fbird_timefmt')) {
-			fbird_timefmt($this->fbird_datefmt,fbird_DATE );
-			if ($this->dialect == 1) {
-				fbird_timefmt($this->fbird_datefmt,fbird_TIMESTAMP );
-			} else {
-				fbird_timefmt($this->fbird_timestampfmt,fbird_TIMESTAMP );
-			}
-			fbird_timefmt($this->fbird_timefmt,fbird_TIME );
+		ini_set("ibase.timestampformat", $this->fbird_timestampfmt);
+		ini_set("ibase.dateformat", $this->fbird_datefmt);
+		ini_set("ibase.timeformat", $this->fbird_timefmt);
 
-		} else {
-			ini_set("ibase.timestampformat", $this->fbird_timestampfmt);
-			ini_set("ibase.dateformat", $this->fbird_datefmt);
-			ini_set("ibase.timeformat", $this->fbird_timefmt);
-		}
 		return true;
 	}
 
-	// returns true or false
+	/**
+	 * Connect to a database with a persistent connection.
+	 *
+	 * @param string|null $argHostname The host to connect to.
+	 * @param string|null $argUsername The username to connect as.
+	 * @param string|null $argPassword The password to connect with.
+	 * @param string|null $argDatabasename The name of the database to start in when connected.
+	 *
+	 * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension
+	 * isn't currently loaded.
+	 */
 	function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
 	{
 		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,true);
 	}
 
 
-	function MetaPrimaryKeys($table,$owner_notused=false,$internalKey=false)
+	public function metaPrimaryKeys($table,$owner_notused=false,$internalKey=false)
 	{
 		if ($internalKey) {
 			return array('RDB$DB_KEY');
@@ -126,31 +203,58 @@ class ADODB_firebird extends ADOConnection {
 		return false;
 	}
 
-	function ServerInfo()
+	/**
+	 * Get information about the current Firebird server.
+	 *
+	 * @return array
+	 */
+	public function serverInfo()
 	{
 		$arr['dialect'] = $this->dialect;
 		switch($arr['dialect']) {
-		case '':
-		case '1': $s = 'Firebird Dialect 1'; break;
-		case '2': $s = 'Firebird Dialect 2'; break;
-		default:
-		case '3': $s = 'Firebird Dialect 3'; break;
+			case '':
+			case '1':
+				$s = 'Firebird Dialect 1';
+				break;
+			case '2':
+				$s = 'Firebird Dialect 2';
+				break;
+			default:
+			case '3':
+				$s = 'Firebird Dialect 3';
+				break;
 		}
 		$arr['version'] = ADOConnection::_findvers($s);
 		$arr['description'] = $s;
 		return $arr;
 	}
 
-	function BeginTrans()
+	/**
+	 * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans().
+	 *
+	 * @return bool true if succeeded or false if database does not support transactions
+	 */
+	public function beginTrans()
 	{
 		if ($this->transOff) return true;
 		$this->transCnt += 1;
 		$this->autoCommit = false;
-		$this->_transactionID = fbird_trans( $this->ibasetrans, $this->_connectionID );
+		/*
+		* We manage the transaction mode via fbird_trans
+		*/
+		$this->_transactionID = fbird_trans( $this->_transmode, $this->_connectionID );
 		return $this->_transactionID;
 	}
 
-	function CommitTrans($ok=true)
+
+	/**
+	 * Commits a transaction.
+	 *
+	 * @param bool $ok  false to rollback transaction, true to commit
+	 *
+	 * @return bool
+	 */
+	public function commitTrans($ok=true)
 	{
 		if (!$ok) {
 			return $this->RollbackTrans();
@@ -164,7 +268,6 @@ class ADODB_firebird extends ADOConnection {
 		$ret = false;
 		$this->autoCommit = true;
 		if ($this->_transactionID) {
-			//print ' commit ';
 			$ret = fbird_commit($this->_transactionID);
 		}
 		$this->_transactionID = false;
@@ -173,31 +276,26 @@ class ADODB_firebird extends ADOConnection {
 
 	function _affectedrows()
 	{
-			return fbird_affected_rows( $this->_transactionID ? $this->_transactionID : $this->_connectionID );
-	}
-
-	// there are some compat problems with ADODB_COUNTRECS=false and $this->_logsql currently.
-	// it appears that ibase extension cannot support multiple concurrent queryid's
-	function _Execute($sql,$inputarr=false) {
-	global $ADODB_COUNTRECS;
-
-		if ($this->_logsql) {
-			$savecrecs = $ADODB_COUNTRECS;
-			$ADODB_COUNTRECS = true; // force countrecs
-			$ret =& ADOConnection::_Execute($sql,$inputarr);
-			$ADODB_COUNTRECS = $savecrecs;
-		} else {
-			$ret = ADOConnection::_Execute($sql,$inputarr);
-		}
-		return $ret;
+		return fbird_affected_rows($this->_transactionID ?: $this->_connectionID);
 	}
 
-	function RollbackTrans()
+	/**
+	 * Rollback a smart transaction.
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:rollbacktrans
+	 *
+	 * @return bool
+	 */
+	public function rollbackTrans()
 	{
-		if ($this->transOff) return true;
-		if ($this->transCnt) $this->transCnt -= 1;
+		if ($this->transOff)
+			return true;
+		if ($this->transCnt)
+			$this->transCnt -= 1;
+
 		$ret = false;
 		$this->autoCommit = true;
+
 		if ($this->_transactionID) {
 			$ret = fbird_rollback($this->_transactionID);
 		}
@@ -206,16 +304,26 @@ class ADODB_firebird extends ADOConnection {
 		return $ret;
 	}
 
-	function &MetaIndexes ($table, $primary = FALSE, $owner=false)
+	/**
+	 * Get a list of indexes on the specified table.
+	 *
+	 * @param string $table The name of the table to get indexes for.
+	 * @param bool $primary (Optional) Whether or not to include the primary key.
+	 * @param bool $owner (Optional) Unused.
+	 *
+	 * @return array|bool An array of the indexes, or false if the query to get the indexes failed.
+	 */
+	public function metaIndexes($table, $primary = false, $owner = false)
 	{
 		// save old fetch mode
 		global $ADODB_FETCH_MODE;
-		$false = false;
 		$save = $ADODB_FETCH_MODE;
 		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+
 		if ($this->fetchMode !== FALSE) {
 				$savem = $this->SetFetchMode(FALSE);
 		}
+
 		$table = strtoupper($table);
 		$sql = "SELECT * FROM RDB\$INDICES WHERE RDB\$RELATION_NAME = '".$table."'";
 		if (!$primary) {
@@ -224,19 +332,19 @@ class ADODB_firebird extends ADOConnection {
 			$sql .= " AND RDB\$INDEX_NAME NOT LIKE 'RDB\$FOREIGN%'";
 		}
 		// get index details
-		$rs = $this->Execute($sql);
+		$rs = $this->execute($sql);
 		if (!is_object($rs)) {
 			// restore fetchmode
 			if (isset($savem)) {
 				$this->SetFetchMode($savem);
 			}
 			$ADODB_FETCH_MODE = $save;
-			return $false;
+			return false;
 		}
-
 		$indexes = array();
 		while ($row = $rs->FetchRow()) {
-			$index = $row[0];
+
+			$index = trim($row[0]);
 			if (!isset($indexes[$index])) {
 				if (is_null($row[3])) {
 					$row[3] = 0;
@@ -246,12 +354,13 @@ class ADODB_firebird extends ADOConnection {
 					'columns' => array()
 				);
 			}
-			$sql = "SELECT * FROM RDB\$INDEX_SEGMENTS WHERE RDB\$INDEX_NAME = '".$index."' ORDER BY RDB\$FIELD_POSITION ASC";
-			$rs1 = $this->Execute($sql);
+			$sql = sprintf("SELECT * FROM RDB\$INDEX_SEGMENTS WHERE RDB\$INDEX_NAME = '%s' ORDER BY RDB\$FIELD_POSITION ASC",$index);
+			$rs1 = $this->execute($sql);
 			while ($row1 = $rs1->FetchRow()) {
-				$indexes[$index]['columns'][$row1[2]] = $row1[1];
+				$indexes[$index]['columns'][$row1[2]] = trim($row1[1]);
 			}
 		}
+
 		// restore fetchmode
 		if (isset($savem)) {
 			$this->SetFetchMode($savem);
@@ -261,38 +370,60 @@ class ADODB_firebird extends ADOConnection {
 		return $indexes;
 	}
 
-
-	// See http://community.borland.com/article/0,1410,25844,00.html
-	function RowLock($tables,$where,$col=false)
+	/**
+	 * Lock a table row for a duration of a transaction.
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:rowlock
+	 * @link https://firebirdsql.org/refdocs/langrefupd21-notes-withlock.html
+	 *
+	 * @param string $table The table(s) to lock rows for.
+	 * @param string $where (Optional) The WHERE clause to use to determine which rows to lock.
+	 * @param string $col (Optional) The columns to select.
+	 *
+	 * @return bool True if the locking SQL statement executed successfully, otherwise false.
+	 */
+	public function rowLock($table,$where,$col=false)
 	{
-		if ($this->autoCommit) {
-			$this->BeginTrans();
-		}
-		$this->Execute("UPDATE $table SET $col=$col WHERE $where "); // is this correct - jlim?
-		return 1;
-	}
-
+		if ($this->transCnt==0)
+			$this->beginTrans();
 
-	function CreateSequence($seqname = 'adodbseq', $startID = 1)
-	{
-		$ok = $this->Execute(("CREATE GENERATOR $seqname" ));
-		if (!$ok) return false;
-		return $this->Execute("SET GENERATOR $seqname TO ".($startID-1));
+		if ($where) $where = ' where '.$where;
+		$rs = $this->execute("SELECT $col FROM $table $where FOR UPDATE WITH LOCK");
+		return !empty($rs);
 	}
 
-	function DropSequence($seqname = 'adodbseq')
+	/**
+	 * Creates a sequence in the database.
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:createsequence
+	 *
+	 * @param string $seqname The sequence name.
+	 * @param int $startID The start id.
+	 *
+	 * @return ADORecordSet|bool A record set if executed successfully, otherwise false.
+	 */
+	public function createSequence($seqname='adodbseq', $startID = 1)
 	{
-		$seqname = strtoupper($seqname);
-		return $this->Execute("DROP GENERATOR $seqname");
+		$sql = sprintf($this->_genSeqSQL,$seqname,$startID);
+		return $this->execute($sql);
 	}
 
-	function GenID($seqname='adodbseq',$startID=1)
+	/**
+	 * A portable method of creating sequence numbers.
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:genid
+	 *
+	 * @param string $seqname (Optional) The name of the sequence to use.
+	 * @param int $startID (Optional) The point to start at in the sequence.
+	 *
+	 * @return int
+	 */
+	public function genID($seqname='adodbseq',$startID=1)
 	{
 		$getnext = ("SELECT Gen_ID($seqname,1) FROM RDB\$DATABASE");
 		$rs = @$this->Execute($getnext);
 		if (!$rs) {
-			$this->Execute(("CREATE GENERATOR $seqname" ));
-			$this->Execute("SET GENERATOR $seqname TO ".($startID-1).';');
+			$this->Execute("CREATE SEQUENCE $seqname START WITH $startID");
 			$rs = $this->Execute($getnext);
 		}
 		if ($rs && !$rs->EOF) {
@@ -309,39 +440,62 @@ class ADODB_firebird extends ADOConnection {
 		return $this->genID;
 	}
 
-	function SelectDB($dbName)
+	function selectDB($dbName)
 	{
 		return false;
 	}
 
-	function _handleerror()
+	function _handleError()
 	{
-		$this->_errorMsg = fbird_errmsg();
+		$this->_errorCode = fbird_errcode();
+		$this->_errorMsg  = fbird_errmsg();
 	}
 
-	function ErrorNo()
+
+	public function errorNo()
 	{
-		if (preg_match('/error code = ([\-0-9]*)/i', $this->_errorMsg,$arr)) return (integer) $arr[1];
-		else return 0;
+		return (integer) $this->_errorCode;
 	}
 
-	function ErrorMsg()
+	function errorMsg()
 	{
 			return $this->_errorMsg;
 	}
 
-	function Prepare($sql)
+	/**
+	 * Prepares an SQL statement and returns a handle to use.
+	 * This is not used by bound parameters anymore
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:prepare
+	 * @todo update this function to handle prepared statements correctly
+	 *
+	 * @param string $sql The SQL to prepare.
+	 *
+	 * @return bool|array The SQL that was provided and the prepared parameters,
+	 *                    or false if the preparation fails
+	 */
+	public function prepare($sql)
 	{
 		$stmt = fbird_prepare($this->_connectionID,$sql);
-		if (!$stmt) return false;
+		if (!$stmt)
+			return false;
 		return array($sql,$stmt);
 	}
 
-	   // returns query ID if successful, otherwise false
-	   // there have been reports of problems with nested queries - the code is probably not re-entrant?
-	function _query($sql,$iarr=false)
+	/**
+	 * Execute a query.
+	 *
+	 * @param string|array $sql        Query to execute.
+	 * @param array        $inputarr   An optional array of parameters.
+	 *
+	 * @return object|bool Query identifier or true if execution successful, false if failed.
+	 */
+	function _query($sql, $inputarr = false)
 	{
-		if ( !$this->isConnected() ) return false;
+		if (!$this->isConnected()) {
+			return false;
+		}
+
 		if (!$this->autoCommit && $this->_transactionID) {
 			$conn = $this->_transactionID;
 			$docommit = false;
@@ -349,36 +503,31 @@ class ADODB_firebird extends ADOConnection {
 			$conn = $this->_connectionID;
 			$docommit = true;
 		}
+
 		if (is_array($sql)) {
+			// Prepared statement
 			$fn = 'fbird_execute';
-			$sql = $sql[1];
-			if (is_array($iarr)) {
-				if ( !isset($iarr[0]) ) 
-					$iarr[0] = ''; // PHP5 compat hack
-				$fnarr = array_merge( array($sql) , $iarr);
-				$ret = call_user_func_array($fn,$fnarr);
-			}
-			else {
-				$ret = $fn($sql);
-			}
+			$args = [$sql[1]];
 		} else {
 			$fn = 'fbird_query';
-			if (is_array($iarr)) 
-			{
-				if (sizeof($iarr) == 0) 
-					$iarr[0] = ''; // PHP5 compat hack
-				$fnarr = array_merge( array($conn,$sql) , $iarr);
-				$ret = call_user_func_array($fn,$fnarr);
-			}
-			else {
-				$ret = $fn($conn, $sql);
-			}
+			$args = [$conn, $sql];
+		}
+		if (is_array($inputarr)) {
+			$args = array_merge($args, $inputarr);
 		}
+		$ret = call_user_func_array($fn, $args);
+
+		// fbird_query() and fbird_execute() return number of affected rows
+		// ADOConnection::_Execute() expects true for INSERT/UPDATE/DELETE
+		if (is_numeric($ret)) {
+			$ret = true;
+		}
+
 		if ($docommit && $ret === true) {
 			fbird_commit($this->_connectionID);
 		}
 
-		$this->_handleerror();
+		$this->_handleError();
 		return $ret;
 	}
 
@@ -398,122 +547,133 @@ class ADODB_firebird extends ADOConnection {
 		$fld->max_length = $flen;
 		$fld->scale = null;
 		switch($ftype){
-			case 7:
-			case 8:
-				if ($dialect3) {
-					switch($fsubtype){
-						case 0:
-							$fld->type = ($ftype == 7 ? 'smallint' : 'integer');
-							break;
-						case 1:
-							$fld->type = 'numeric';
-							$fld->max_length = $fprecision;
-							$fld->scale = $fscale;
-							break;
-						case 2:
-							$fld->type = 'decimal';
-							$fld->max_length = $fprecision;
-							$fld->scale = $fscale;
-							break;
-					} // switch
-				} else {
-					if ($fscale !=0) {
+		case 7:
+		case 8:
+			if ($dialect3) {
+				switch($fsubtype){
+					case 0:
+						$fld->type = ($ftype == 7 ? 'smallint' : 'integer');
+						break;
+					case 1:
+						$fld->type = 'numeric';
+						$fld->max_length = $fprecision;
+						$fld->scale = $fscale;
+						break;
+					case 2:
 						$fld->type = 'decimal';
+						$fld->max_length = $fprecision;
 						$fld->scale = $fscale;
-						$fld->max_length = ($ftype == 7 ? 4 : 9);
-					} else {
-						$fld->type = ($ftype == 7 ? 'smallint' : 'integer');
-					}
-				}
-				break;
-			case 16:
-				if ($dialect3) {
-					switch($fsubtype){
-						case 0:
-							$fld->type = 'decimal';
-							$fld->max_length = 18;
-							$fld->scale = 0;
-							break;
-						case 1:
-							$fld->type = 'numeric';
-							$fld->max_length = $fprecision;
-							$fld->scale = $fscale;
-							break;
-						case 2:
-							$fld->type = 'decimal';
-							$fld->max_length = $fprecision;
-							$fld->scale = $fscale;
-							break;
-					} // switch
-				}
-				break;
-			case 10:
-				$fld->type = 'float';
-				break;
-			case 14:
-				$fld->type = 'char';
-				break;
-			case 27:
+						break;
+				} // switch
+			} else {
 				if ($fscale !=0) {
 					$fld->type = 'decimal';
-					$fld->max_length = 15;
-					$fld->scale = 5;
-				} else {
-					$fld->type = 'double';
-				}
-				break;
-			case 35:
-				if ($dialect3) {
-					$fld->type = 'timestamp';
+					$fld->scale = $fscale;
+					$fld->max_length = ($ftype == 7 ? 4 : 9);
 				} else {
-					$fld->type = 'date';
+					$fld->type = ($ftype == 7 ? 'smallint' : 'integer');
 				}
-				break;
-			case 12:
+			}
+			break;
+		case 16:
+			if ($dialect3) {
+				switch($fsubtype){
+				case 0:
+					$fld->type = 'decimal';
+					$fld->max_length = 18;
+					$fld->scale = 0;
+					break;
+				case 1:
+					$fld->type = 'numeric';
+					$fld->max_length = $fprecision;
+					$fld->scale = $fscale;
+					break;
+				case 2:
+					$fld->type = 'decimal';
+					$fld->max_length = $fprecision;
+					$fld->scale = $fscale;
+					break;
+				} // switch
+			}
+			break;
+		case 10:
+			$fld->type = 'float';
+			break;
+		case 14:
+			$fld->type = 'char';
+			break;
+		case 27:
+			if ($fscale !=0) {
+				$fld->type = 'decimal';
+				$fld->max_length = 15;
+				$fld->scale = 5;
+			} else {
+				$fld->type = 'double';
+			}
+			break;
+		case 35:
+			if ($dialect3) {
+				$fld->type = 'timestamp';
+			} else {
 				$fld->type = 'date';
-				break;
-			case 13:
-				$fld->type = 'time';
-				break;
-			case 37:
-				$fld->type = 'varchar';
-				break;
-			case 40:
-				$fld->type = 'cstring';
-				break;
-			case 261:
-				$fld->type = 'blob';
-				$fld->max_length = -1;
-				break;
+			}
+			break;
+		case 12:
+			$fld->type = 'date';
+			break;
+		case 13:
+			$fld->type = 'time';
+			break;
+		case 37:
+			$fld->type = 'varchar';
+			break;
+		case 40:
+			$fld->type = 'cstring';
+			break;
+		case 261:
+			$fld->type = 'blob';
+			$fld->max_length = -1;
+			break;
 		} // switch
 	}
 	//OPN STUFF end
 
-	// returns array of ADOFieldObjects for current table
-	function MetaColumns($table, $normalize=true)
+	/**
+	 * Return an array of information about a table's columns.
+	 *
+	 * @param string $table The name of the table to get the column info for.
+	 * @param bool $normalize (Optional) Unused.
+	 *
+	 * @return ADOFieldObject[]|bool An array of info for each column,
+	 * or false if it could not determine the info.
+	 */
+	public function metaColumns($table, $normalize = true)
 	{
-	global $ADODB_FETCH_MODE;
+
+		global $ADODB_FETCH_MODE;
 
 		$save = $ADODB_FETCH_MODE;
 		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
 
-		$rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
+		$rs = $this->execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
 
 		$ADODB_FETCH_MODE = $save;
-		$false = false;
+
 		if ($rs === false) {
-			return $false;
+			return false;
 		}
 
 		$retarr = array();
 		//OPN STUFF start
-		$dialect3 = ($this->dialect==3 ? true : false);
+		$dialect3 = $this->dialect == 3;
 		//OPN STUFF end
 		while (!$rs->EOF) { //print_r($rs->fields);
 			$fld = new ADOFieldObject();
 			$fld->name = trim($rs->fields[0]);
 			//OPN STUFF start
-			$this->_ConvertFieldType($fld, $rs->fields[7], $rs->fields[3], $rs->fields[4], $rs->fields[5], $rs->fields[6], $dialect3);
+				//print_r($rs->fields);
+			$this->_ConvertFieldType(
+				$fld, $rs->fields[7], $rs->fields[3], $rs->fields[4], $rs->fields[5], $rs->fields[6], $dialect3);
 			if (isset($rs->fields[1]) && $rs->fields[1]) {
 				$fld->not_null = true;
 			}
@@ -521,17 +681,24 @@ class ADODB_firebird extends ADOConnection {
 
 				$fld->has_default = true;
 				$d = substr($rs->fields[2],strlen('default '));
-				switch ($fld->type)
-				{
-				case 'smallint':
-				case 'integer': $fld->default_value = (int) $d; break;
-				case 'char':
-				case 'blob':
-				case 'text':
-				case 'varchar': $fld->default_value = (string) substr($d,1,strlen($d)-2); break;
-				case 'double':
-				case 'float': $fld->default_value = (float) $d; break;
-				default: $fld->default_value = $d; break;
+				switch ($fld->type) {
+					case 'smallint':
+					case 'integer':
+						$fld->default_value = (int)$d;
+						break;
+					case 'char':
+					case 'blob':
+					case 'text':
+					case 'varchar':
+						$fld->default_value = (string)substr($d, 1, strlen($d) - 2);
+						break;
+					case 'double':
+					case 'float':
+						$fld->default_value = (float)$d;
+						break;
+					default:
+						$fld->default_value = $d;
+						break;
 				}
 		//	case 35:$tt = 'TIMESTAMP'; break;
 			}
@@ -547,61 +714,115 @@ class ADODB_firebird extends ADOConnection {
 			$rs->MoveNext();
 		}
 		$rs->Close();
-		if ( empty($retarr)) return $false;
+		if ( empty($retarr))
+			return false;
 		else return $retarr;
 	}
 
-	function BlobEncode( $blob )
+	/**
+	 * Retrieves a list of tables based on given criteria
+	 *
+	 * @param string|bool $ttype (Optional) Table type = 'TABLE', 'VIEW' or false=both (default)
+	 * @param string|bool $showSchema (Optional) schema name, false = current schema (default)
+	 * @param string|bool $mask (Optional) filters the table by name
+	 *
+	 * @return array list of tables
+	 */
+	public function metaTables($ttype = false, $showSchema = false, $mask = false)
+	{
+		$save = $this->metaTablesSQL;
+		if (!$showSchema) {
+			$this->metaTablesSQL .= " WHERE (rdb\$relation_name NOT LIKE 'RDB\$%' AND rdb\$relation_name NOT LIKE 'MON\$%' AND rdb\$relation_name NOT LIKE 'SEC\$%')";
+		} elseif (is_string($showSchema)) {
+			$this->metaTablesSQL .= $this->qstr($showSchema);
+		}
+
+		if ($mask) {
+			$mask = $this->qstr($mask);
+			$this->metaTablesSQL .= " AND table_name LIKE $mask";
+		}
+		$ret = ADOConnection::metaTables($ttype,$showSchema);
+
+		$this->metaTablesSQL = $save;
+		return $ret;
+	}
+
+	/**
+	 * Encodes a blob, then assigns an id ready to be used
+	 *
+	 * @param string $blob The blob to be encoded
+	 *
+	 * @return bool success
+	 */
+	public function blobEncode( $blob )
 	{
 		$blobid = fbird_blob_create( $this->_connectionID);
 		fbird_blob_add( $blobid, $blob );
 		return fbird_blob_close( $blobid );
 	}
 
-	// since we auto-decode all blob's since 2.42,
-	// BlobDecode should not do any transforms
-	function BlobDecode($blob)
+	/**
+	 * Manually decode a blob
+	 *
+	 * since we auto-decode all blob's since 2.42,
+	 * BlobDecode should not do any transforms
+	 *
+	 * @param string $blob
+	 *
+	 * @return string the same blob
+	 */
+	public function blobDecode($blob)
 	{
 		return $blob;
 	}
 
-	// old blobdecode function
-	// still used to auto-decode all blob's
-	function _BlobDecode_old( $blob )
+	/**
+	 * Auto function called on read of blob to decode
+	 *
+	 * @param string $blob Value to decode
+	 *
+	 * @return string Decoded blob
+	 */
+	public function _blobDecode($blob)
 	{
-		$blobid = fbird_blob_open($this->_connectionID, $blob );
-		$realblob = fbird_blob_get( $blobid,$this->maxblobsize); // 2nd param is max size of blob -- Kevin Boillet <kevinboillet@yahoo.fr>
-		while($string = fbird_blob_get($blobid, 8192)){
-			$realblob .= $string;
+		if ($blob === null) {
+			return '';
 		}
-		fbird_blob_close( $blobid );
 
-		return( $realblob );
-	}
+		$blob_data = fbird_blob_info($this->_connectionID, $blob);
+		$blobId = fbird_blob_open($this->_connectionID, $blob);
 
-	function _BlobDecode( $blob )
-	{
-		$blob_data = fbird_blob_info($this->_connectionID, $blob );
-		$blobid    = fbird_blob_open($this->_connectionID, $blob );
-
-		if( $blob_data[0] > $this->maxblobsize ) {
-			$realblob = fbird_blob_get($blobid, $this->maxblobsize);
-
-			while($string = fbird_blob_get($blobid, 8192)) {
-				$realblob .= $string;
+		if ($blob_data[0] > $this->maxblobsize) {
+			$realBlob = fbird_blob_get($blobId, $this->maxblobsize);
+			while ($string = fbird_blob_get($blobId, 8192)) {
+				$realBlob .= $string;
 			}
 		} else {
-			$realblob = fbird_blob_get($blobid, $blob_data[0]);
+			$realBlob = fbird_blob_get($blobId, $blob_data[0]);
 		}
 
-		fbird_blob_close( $blobid );
-		return( $realblob );
+		fbird_blob_close($blobId);
+		return $realBlob;
 	}
 
-	function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB')
+	/**
+	 * Insert blob data into a database column directly
+	 * from file
+	 *
+	 * @param string $table table to insert
+	 * @param string $column column to insert
+	 * @param string $path  physical file name
+	 * @param string $where string to find unique record
+	 * @param string $blobtype BLOB or CLOB
+	 *
+	 * @return bool success
+	 */
+	public function updateBlobFile($table,$column,$path,$where,$blobtype='BLOB')
 	{
 		$fd = fopen($path,'rb');
-		if ($fd === false) return false;
+		if ($fd === false)
+			return false;
+
 		$blob_id = fbird_blob_create($this->_connectionID);
 
 		/* fill with data */
@@ -617,105 +838,106 @@ class ADODB_firebird extends ADOConnection {
 		return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
 	}
 
-	/*
-		Insert a null into the blob field of the table first.
-		Then use UpdateBlob to store the blob.
-
-		Usage:
-
-		$conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
-		$conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
-	*/
-	function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+	/**
+	 * Insert blob data into a database column
+	 *
+	 * @param string $table table to insert
+	 * @param string $column column to insert
+	 * @param string $val    value to insert
+	 * @param string $where string to find unique record
+	 * @param string $blobtype BLOB or CLOB
+	 *
+	 * @return bool success
+	 */
+	public function updateBlob($table,$column,$val,$where,$blobtype='BLOB')
 	{
-	$blob_id = fbird_blob_create($this->_connectionID);
+		$blob_id = fbird_blob_create($this->_connectionID);
 
-	// fbird_blob_add($blob_id, $val);
+		// fbird_blob_add($blob_id, $val);
 
-	// replacement that solves the problem by which only the first modulus 64K /
-	// of $val are stored at the blob field ////////////////////////////////////
-	// Thx Abel Berenstein  aberenstein#afip.gov.ar
-	$len = strlen($val);
-	$chunk_size = 32768;
-	$tail_size = $len % $chunk_size;
-	$n_chunks = ($len - $tail_size) / $chunk_size;
+		// replacement that solves the problem by which only the first modulus 64K /
+		// of $val are stored at the blob field ////////////////////////////////////
+		// Thx Abel Berenstein  aberenstein#afip.gov.ar
+		$len = strlen($val);
+		$chunk_size = 32768;
+		$tail_size = $len % $chunk_size;
+		$n_chunks = ($len - $tail_size) / $chunk_size;
 
-	for ($n = 0; $n < $n_chunks; $n++) {
-		$start = $n * $chunk_size;
-		$data = substr($val, $start, $chunk_size);
-		fbird_blob_add($blob_id, $data);
-	}
+		for ($n = 0; $n < $n_chunks; $n++) {
+			$start = $n * $chunk_size;
+			$data = substr($val, $start, $chunk_size);
+			fbird_blob_add($blob_id, $data);
+		}
 
-	if ($tail_size) {
-		$start = $n_chunks * $chunk_size;
-		$data = substr($val, $start, $tail_size);
-		fbird_blob_add($blob_id, $data);
-	}
-	// end replacement /////////////////////////////////////////////////////////
+		if ($tail_size) {
+			$start = $n_chunks * $chunk_size;
+			$data = substr($val, $start, $tail_size);
+			fbird_blob_add($blob_id, $data);
+		}
+		// end replacement /////////////////////////////////////////////////////////
 
-	$blob_id_str = fbird_blob_close($blob_id);
+		$blob_id_str = fbird_blob_close($blob_id);
 
-	return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
+		return $this->execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
 
 	}
 
 
-	function OldUpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
+	/**
+	 * Returns a portably-formatted date string from a timestamp database column.
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:sqldate
+	 *
+	 * Firebird does not support an AM/PM format, so the A indicator always shows AM
+	 *
+	 * @param string $fmt The date format to use.
+	 * @param string|bool $col (Optional) The table column to date format, or if false, use NOW().
+	 *
+	 * @return string The SQL DATE_FORMAT() string, or empty if the provided date format was empty.
+	 */
+	public function sqlDate($fmt, $col=false)
 	{
-		$blob_id = fbird_blob_create($this->_connectionID);
-		fbird_blob_add($blob_id, $val);
-		$blob_id_str = fbird_blob_close($blob_id);
-		return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
-	}
+		if (!$col)
+			$col = 'CURRENT_TIMESTAMP';
 
-	// Format date column in sql string given an input format that understands Y M D
-	// Only since Interbase 6.0 - uses EXTRACT
-	// problem - does not zero-fill the day and month yet
-	function SQLDate($fmt, $col=false)
-	{
-		if (!$col) $col = $this->sysDate;
 		$s = '';
 
 		$len = strlen($fmt);
 		for ($i=0; $i < $len; $i++) {
 			if ($s) $s .= '||';
 			$ch = $fmt[$i];
-			switch($ch) {
+			$choice = strtoupper($ch);
+			switch($choice) {
 			case 'Y':
-			case 'y':
-				$s .= "extract(year from $col)";
+				$s .= "EXTRACT(YEAR FROM $col)";
 				break;
 			case 'M':
-			case 'm':
-				$s .= "extract(month from $col)";
+				$s .= "RIGHT('0' || TRIM(EXTRACT(MONTH FROM $col)),2)";
 				break;
 			case 'W':
-			case 'w':
 				// The more accurate way of doing this is with a stored procedure
 				// See http://wiki.firebirdsql.org/wiki/index.php?page=DATE+Handling+Functions for details
-				$s .= "((extract(yearday from $col) - extract(weekday from $col - 1) + 7) / 7)";
+				$s .= "((EXTRACT(YEARDAY FROM $col) - EXTRACT(WEEKDAY FROM $col - 1) + 7) / 7)";
 				break;
 			case 'Q':
-			case 'q':
-				$s .= "cast(((extract(month from $col)+2) / 3) as integer)";
+				$s .= "CAST(((EXTRACT(MONTH FROM $col)+2) / 3) AS INTEGER)";
 				break;
 			case 'D':
-			case 'd':
-				$s .= "(extract(day from $col))";
+				$s .= "RIGHT('0' || TRIM(EXTRACT(DAY FROM $col)),2)";
 				break;
 			case 'H':
-			case 'h':
-				$s .= "(extract(hour from $col))";
+				$s .= "RIGHT('0' || TRIM(EXTRACT(HOUR FROM $col)),2)";
 				break;
 			case 'I':
-			case 'i':
-				$s .= "(extract(minute from $col))";
+				$s .= "RIGHT('0' || TRIM(EXTRACT(MINUTE FROM $col)),2)";
 				break;
 			case 'S':
-			case 's':
-				$s .= "CAST((extract(second from $col)) AS INTEGER)";
+				//$s .= "CAST((EXTRACT(SECOND FROM $col)) AS INTEGER)";
+				$s .= "RIGHT('0' || TRIM(EXTRACT(SECOND FROM $col)),2)";
+				break;
+			case 'A':
+				$s .= $this->qstr('AM');
 				break;
-
 			default:
 				if ($ch == '\\') {
 					$i++;
@@ -728,10 +950,44 @@ class ADODB_firebird extends ADOConnection {
 		return $s;
 	}
 
+	/**
+	 * Creates a portable date offset field, for use in SQL statements.
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:offsetdate
+	 *
+	 * @param float $dayFraction A day in floating point
+	 * @param string|bool $date (Optional) The date to offset. If false, uses CURDATE()
+	 *
+	 * @return string
+	 */
+	public function offsetDate($dayFraction, $date=false)
+	{
+		if (!$date)
+			$date = $this->sysTimeStamp;
+
+		$fraction = $dayFraction * 24 * 3600;
+		return sprintf("DATEADD (second, %s, %s)  FROM RDB\$DATABASE",$fraction,$date);
+	}
+
+
 	// Note that Interbase 6.5 uses this ROWS instead - don't you love forking wars!
 	// 		SELECT col1, col2 FROM table ROWS 5 -- get 5 rows
 	//		SELECT col1, col2 FROM TABLE ORDER BY col1 ROWS 3 TO 7 -- first 5 skip 2
-	function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false, $secs=0)
+	/**
+	 * Executes a provided SQL statement and returns a handle to the result, with the ability to supply a starting
+	 * offset and record count.
+	 *
+	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:selectlimit
+	 *
+	 * @param string $sql The SQL to execute.
+	 * @param int $nrows (Optional) The limit for the number of records you want returned. By default, all results.
+	 * @param int $offset (Optional) The offset to use when selecting the results. By default, no offset.
+	 * @param array|bool $inputarr (Optional) Any parameter values required by the SQL statement, or false if none.
+	 * @param int $secs2cache (Optional) If greater than 0, perform a cached execute. By default, normal execution.
+	 *
+	 * @return ADORecordSet|false The query results, or false if the query failed to execute.
+	 */
+	public function selectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false, $secs2cache=0)
 	{
 		$nrows = (integer) $nrows;
 		$offset = (integer) $offset;
@@ -740,46 +996,88 @@ class ADODB_firebird extends ADOConnection {
 		$str .=($offset>=0) ? "SKIP $offset " : '';
 
 		$sql = preg_replace('/^[ \t]*select/i',$str,$sql);
-		if ($secs)
-			$rs = $this->CacheExecute($secs,$sql,$inputarr);
+		if ($secs2cache)
+			$rs = $this->cacheExecute($secs2cache,$sql,$inputarr);
 		else
-			$rs = $this->Execute($sql,$inputarr);
+			$rs = $this->execute($sql,$inputarr);
 
 		return $rs;
 	}
 
 }
 
-/*--------------------------------------------------------------------------------------
-		 Class Name: Recordset
---------------------------------------------------------------------------------------*/
-
-class  ADORecordset_firebird extends ADORecordSet
+/**
+ * Class ADORecordset_firebird
+ */
+class ADORecordset_firebird extends ADORecordSet
 {
-
 	var $databaseType = "firebird";
-	var $bind=false;
-	var $_cacheType;
+	var $bind = false;
+
+	/**
+	 * @var ADOFieldObject[] Holds a cached version of the metadata
+	 */
+	private $fieldObjects = false;
+
+	/**
+	 * @var bool Flags if we have retrieved the metadata
+	 */
+	private $fieldObjectsRetrieved = false;
+
+	/**
+	 * @var array Cross-reference the objects by name for easy access
+	 */
+	private $fieldObjectsIndex = array();
+
+	/**
+	 * @var bool Flag to indicate if the result has a blob
+	 */
+	private $fieldObjectsHaveBlob = false;
 
-	function __construct($id,$mode=false)
+	function __construct($id, $mode = false)
 	{
-	global $ADODB_FETCH_MODE;
+		global $ADODB_FETCH_MODE;
 
-			$this->fetchMode = ($mode === false) ? $ADODB_FETCH_MODE : $mode;
-			parent::__construct($id);
+		$this->fetchMode = ($mode === false) ? $ADODB_FETCH_MODE : $mode;
+		parent::__construct($id);
 	}
 
+
 	/**
-	 * Get column information in the Recordset object.
-	 * fetchField() can be used in order to obtain information about fields in
-	 * a certain query result. If the field offset isn't specified, the next
-	 * field that wasn't yet retrieved by fetchField() is retrieved.
-	 * @return object containing field information.
-	*/
-	function FetchField($fieldOffset = -1)
+	 * Returns: an object containing field information.
+	 *
+	 * Get column information in the Recordset object. fetchField()
+	 * can be used in order to obtain information about fields in a
+	 * certain query result. If the field offset isn't specified,
+	 * the next field that wasn't yet retrieved by fetchField()
+	 * is retrieved.
+	 *
+	 * $param int $fieldOffset (optional default=-1 for all
+	 * @return mixed an ADOFieldObject, or array of objects
+	 */
+	private function _fetchField($fieldOffset = -1)
 	{
+		if ($this->fieldObjectsRetrieved) {
+			if ($this->fieldObjects) {
+				// Already got the information
+				if ($fieldOffset == -1) {
+					return $this->fieldObjects;
+				} else {
+					return $this->fieldObjects[$fieldOffset];
+				}
+			} else {
+				// No metadata available
+				return false;
+			}
+		}
+
+		// Populate the field objects cache
+		$this->fieldObjectsRetrieved = true;
+		$this->fieldObjectsHaveBlob = false;
+		$this->_numOfFields = fbird_num_fields($this->_queryID);
+		for ($fieldIndex = 0; $fieldIndex < $this->_numOfFields; $fieldIndex++) {
 			$fld = new ADOFieldObject;
-			 $ibf = fbird_field_info($this->_queryID,$fieldOffset);
+			$ibf = fbird_field_info($this->_queryID, $fieldIndex);
 
 			$name = empty($ibf['alias']) ? $ibf['name'] : $ibf['alias'];
 
@@ -799,23 +1097,54 @@ class  ADORecordset_firebird extends ADORecordSet
 			$fld->type = $ibf['type'];
 			$fld->max_length = $ibf['length'];
 
-			/*       This needs to be populated from the metadata */
+			// This needs to be populated from the metadata
 			$fld->not_null = false;
 			$fld->has_default = false;
 			$fld->default_value = 'null';
-			return $fld;
+
+			$this->fieldObjects[$fieldIndex] = $fld;
+			$this->fieldObjectsIndex[$fld->name] = $fieldIndex;
+
+			if ($fld->type == 'BLOB') {
+				$this->fieldObjectsHaveBlob = true;
+			}
+		}
+
+		if ($fieldOffset == -1) {
+			return $this->fieldObjects;
+		}
+
+		return $this->fieldObjects[$fieldOffset];
+	}
+
+	/**
+	 * Fetchfield copies the oracle method, it loads the field information
+	 * into the _fieldobjs array once, to save multiple calls to the
+	 * fbird_ function
+	 *
+	 * @param int $fieldOffset (optional)
+	 *
+	 * @return adoFieldObject|false
+	 */
+	public function fetchField($fieldOffset = -1)
+	{
+		if ($fieldOffset == -1) {
+			return $this->fieldObjects;
+		}
+
+		return $this->fieldObjects[$fieldOffset];
 	}
 
 	function _initrs()
 	{
 		$this->_numOfRows = -1;
-		$this->_numOfFields = @fbird_num_fields($this->_queryID);
 
-		// cache types for blob decode check
-		for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) {
-			$f1 = $this->FetchField($i);
-			$this->_cacheType[] = $f1->type;
-		}
+		/*
+		* Retrieve all of the column information first. We copy
+		* this method from oracle
+		*/
+		$this->_fetchField();
+
 	}
 
 	function _seek($row)
@@ -823,98 +1152,171 @@ class  ADORecordset_firebird extends ADORecordSet
 		return false;
 	}
 
-	function _fetch()
+	public function _fetch()
 	{
-		$f = @fbird_fetch_row($this->_queryID);
+		// Case conversion function for use in Closure defined below
+		$localFnCaseConv = null;
+
+		if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+			// Handle either associative or fetch both
+			$localNumeric = false;
+
+			$f = @fbird_fetch_assoc($this->_queryID);
+			if (is_array($f)) {
+				// Optimally do the case_upper or case_lower
+				if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) {
+					$f = array_change_key_case($f, CASE_LOWER);
+					$localFnCaseConv = 'strtolower';
+				} elseif (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_UPPER) {
+					$f = array_change_key_case($f, CASE_UPPER);
+					$localFnCaseConv = 'strtoupper';
+				}
+			}
+		} else {
+			// Numeric fetch mode
+			$localNumeric = true;
+			$f = @fbird_fetch_row($this->_queryID);
+		}
+
 		if ($f === false) {
 			$this->fields = false;
 			return false;
 		}
+
 		// OPN stuff start - optimized
 		// fix missing nulls and decode blobs automatically
-
 		global $ADODB_ANSI_PADDING_OFF;
-		//$ADODB_ANSI_PADDING_OFF=1;
 		$rtrim = !empty($ADODB_ANSI_PADDING_OFF);
 
-		for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) {
-			if ($this->_cacheType[$i]=="BLOB") {
-				if (isset($f[$i])) {
-					$f[$i] = $this->connection->_BlobDecode($f[$i]);
+		// For optimal performance, only process if there is a possibility of something to do
+		if ($this->fieldObjectsHaveBlob || $rtrim) {
+			$localFieldObjects = $this->fieldObjects;
+			$localFieldObjectIndex = $this->fieldObjectsIndex;
+			/** @var ADODB_firebird $localConnection */
+			$localConnection = &$this->connection;
+
+			/**
+			 * Closure for an efficient method of iterating over the elements.
+			 * @param mixed      $value
+			 * @param string|int $key
+			 * @return void
+			 */
+			$rowTransform = function ($value, $key) use (
+				&$f,
+				$rtrim,
+				$localFieldObjects,
+				$localConnection,
+				$localNumeric,
+				$localFnCaseConv,
+				$localFieldObjectIndex
+			) {
+				if ($localNumeric) {
+					$localKey = $key;
 				} else {
-					$f[$i] = null;
+					// Cross-reference the associative key back to numeric
+					// with appropriate case conversion
+					$index = $localFnCaseConv ? $localFnCaseConv($key) : $key;
+					$localKey = $localFieldObjectIndex[$index];
 				}
-			} else {
-				if (!isset($f[$i])) {
-					$f[$i] = null;
-				} else if ($rtrim && is_string($f[$i])) {
-					$f[$i] = rtrim($f[$i]);
+
+				// As we iterate the elements check for blobs and padding
+				if ($localFieldObjects[$localKey]->type == 'BLOB') {
+					$f[$key] = $localConnection->_BlobDecode($value);
+				} else {
+					if ($rtrim && is_string($value)) {
+						$f[$key] = rtrim($value);
+					}
 				}
-			}
+
+			};
+
+			// Walk the array, applying the above closure
+			array_walk($f, $rowTransform);
 		}
-		// OPN stuff end
 
-		$this->fields = $f;
-		if ($this->fetchMode == ADODB_FETCH_ASSOC) {
-			$this->fields = $this->GetRowAssoc();
-		} else if ($this->fetchMode == ADODB_FETCH_BOTH) {
-			$this->fields = array_merge($this->fields,$this->GetRowAssoc());
+		if (!$localNumeric && $this->fetchMode & ADODB_FETCH_NUM) {
+			// Creates a fetch both
+			$fNum = array_values($f);
+			$f = array_merge($f, $fNum);
 		}
+
+		$this->fields = $f;
+
 		return true;
 	}
 
-	/* Use associative array to get fields array */
-	function Fields($colname)
+	/**
+	 * Get the value of a field in the current row by column name.
+	 * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM.
+	 *
+	 * @param string $colname is the field to access
+	 *
+	 * @return mixed the value of $colname column
+	 */
+	public function fields($colname)
 	{
-		if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
+		if ($this->fetchMode & ADODB_FETCH_ASSOC) {
+			return $this->fields[$colname];
+		}
+
 		if (!$this->bind) {
-			$this->bind = array();
-			for ($i=0; $i < $this->_numOfFields; $i++) {
-				$o = $this->FetchField($i);
-				$this->bind[strtoupper($o->name)] = $i;
-			}
+			// fieldsObjectIndex populated by the recordset load
+			$this->bind = array_change_key_case($this->fieldObjectsIndex, CASE_UPPER);
 		}
 
 		return $this->fields[$this->bind[strtoupper($colname)]];
-
 	}
 
 
 	function _close()
 	{
-			return @fbird_free_result($this->_queryID);
+		return @fbird_free_result($this->_queryID);
 	}
 
-	function MetaType($t,$len=-1,$fieldobj=false)
+	public function metaType($t, $len = -1, $fieldObj = false)
 	{
 		if (is_object($t)) {
-			$fieldobj = $t;
-			$t = $fieldobj->type;
-			$len = $fieldobj->max_length;
+			$fieldObj = $t;
+			$t = $fieldObj->type;
+			$len = $fieldObj->max_length;
 		}
-		switch (strtoupper($t)) {
-		case 'CHAR':
-			return 'C';
 
-		case 'TEXT':
-		case 'VARCHAR':
-		case 'VARYING':
-		if ($len <= $this->blobSize) return 'C';
-			return 'X';
-		case 'BLOB':
-			return 'B';
+		$t = strtoupper($t);
+
+		if (array_key_exists($t, $this->connection->customActualTypes)) {
+			return $this->connection->customActualTypes[$t];
+		}
 
-		case 'TIMESTAMP':
-		case 'DATE': return 'D';
-		case 'TIME': return 'T';
-				//case 'T': return 'T';
+		switch ($t) {
+			case 'CHAR':
+				return 'C';
 
-				//case 'L': return 'L';
-		case 'INT':
-		case 'SHORT':
-		case 'INTEGER': return 'I';
-		default: return ADODB_DEFAULT_METATYPE;
+			case 'TEXT':
+			case 'VARCHAR':
+			case 'VARYING':
+				if ($len <= $this->blobSize) {
+					return 'C';
+				}
+				return 'X';
+			case 'BLOB':
+				return 'B';
+
+			case 'TIMESTAMP':
+			case 'DATE':
+				return 'D';
+			case 'TIME':
+				return 'T';
+			//case 'T': return 'T';
+
+			//case 'L': return 'L';
+			case 'INT':
+			case 'SHORT':
+			case 'INTEGER':
+				return 'I';
+			default:
+				return ADODB_DEFAULT_METATYPE;
 		}
 	}
 
 }
+
diff --git a/drivers/adodb-ibase.inc.php b/drivers/adodb-ibase.inc.php
index 69de30d..8159c51 100644
--- a/drivers/adodb-ibase.inc.php
+++ b/drivers/adodb-ibase.inc.php
@@ -256,7 +256,7 @@ class ADODB_ibase extends ADOConnection {
 
 
 	// See http://community.borland.com/article/0,1410,25844,00.html
-	function RowLock($tables,$where,$col=false)
+	function rowLock($table, $where, $col = false)
 	{
 		if ($this->autoCommit) {
 			$this->BeginTrans();
@@ -320,7 +320,7 @@ class ADODB_ibase extends ADOConnection {
 
 	function ErrorMsg()
 	{
-			return $this->_errorMsg;
+		return $this->_errorMsg;
 	}
 
 	function Prepare($sql)
@@ -332,9 +332,8 @@ class ADODB_ibase extends ADOConnection {
 
 	// returns query ID if successful, otherwise false
 	// there have been reports of problems with nested queries - the code is probably not re-entrant?
-	function _query($sql,$iarr=false)
+	function _query($sql, $inputarr = false)
 	{
-
 		if (!$this->autoCommit && $this->_transactionID) {
 			$conn = $this->_transactionID;
 			$docommit = false;
@@ -345,28 +344,35 @@ class ADODB_ibase extends ADOConnection {
 		if (is_array($sql)) {
 			$fn = 'ibase_execute';
 			$sql = $sql[1];
-			if (is_array($iarr)) {
-				if ( !isset($iarr[0]) ) 
-					$iarr[0] = ''; // PHP5 compat hack
-				$fnarr = array_merge( array($sql) , $iarr);
-				$ret = call_user_func_array($fn,$fnarr);
-			}
-			else {
+			if (is_array($inputarr)) {
+				if (!isset($inputarr[0])) {
+					$inputarr[0] = '';  // PHP5 compat hack
+				}
+				$fnarr = array_merge(array($sql), $inputarr);
+				$ret = call_user_func_array($fn, $fnarr);
+			} else {
 				$ret = $fn($sql);
 			}
 		} else {
 			$fn = 'ibase_query';
 
-			if (is_array($iarr)) {
-				if (sizeof($iarr) == 0) 
-					$iarr[0] = ''; // PHP5 compat hack
-				$fnarr = array_merge( array($conn,$sql) , $iarr);
-				$ret = call_user_func_array($fn,$fnarr);
-			}
-			else {
+			if (is_array($inputarr)) {
+				if (sizeof($inputarr) == 0) {
+					$inputarr[0] = ''; // PHP5 compat hack
+				}
+				$fnarr = array_merge(array($conn, $sql), $inputarr);
+				$ret = call_user_func_array($fn, $fnarr);
+			} else {
 				$ret = $fn($conn, $sql);
 			}
 		}
+
+		// ibase_query() and ibase_execute() return number of affected rows
+		// ADOConnection::_Execute() expects true for INSERT/UPDATE/DELETE
+		if (is_numeric($ret)) {
+			$ret = true;
+		}
+
 		if ($docommit && $ret === true) {
 			ibase_commit($this->_connectionID);
 		}
@@ -514,17 +520,24 @@ class ADODB_ibase extends ADOConnection {
 
 				$fld->has_default = true;
 				$d = substr($rs->fields[2],strlen('default '));
-				switch ($fld->type)
-				{
-				case 'smallint':
-				case 'integer': $fld->default_value = (int) $d; break;
-				case 'char':
-				case 'blob':
-				case 'text':
-				case 'varchar': $fld->default_value = (string) substr($d,1,strlen($d)-2); break;
-				case 'double':
-				case 'float': $fld->default_value = (float) $d; break;
-				default: $fld->default_value = $d; break;
+				switch ($fld->type) {
+					case 'smallint':
+					case 'integer':
+						$fld->default_value = (int)$d;
+						break;
+					case 'char':
+					case 'blob':
+					case 'text':
+					case 'varchar':
+						$fld->default_value = (string)substr($d, 1, strlen($d) - 2);
+						break;
+					case 'double':
+					case 'float':
+						$fld->default_value = (float)$d;
+						break;
+					default:
+						$fld->default_value = $d;
+						break;
 				}
 		//	case 35:$tt = 'TIMESTAMP'; break;
 			}
@@ -558,9 +571,6 @@ class ADODB_ibase extends ADOConnection {
 		return $blob;
 	}
 
-
-
-
 	// old blobdecode function
 	// still used to auto-decode all blob's
 	function _BlobDecode_old( $blob )
@@ -625,34 +635,34 @@ class ADODB_ibase extends ADOConnection {
 	*/
 	function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
 	{
-	$blob_id = ibase_blob_create($this->_connectionID);
+		$blob_id = ibase_blob_create($this->_connectionID);
 
-	// ibase_blob_add($blob_id, $val);
+		// ibase_blob_add($blob_id, $val);
 
-	// replacement that solves the problem by which only the first modulus 64K /
-	// of $val are stored at the blob field ////////////////////////////////////
-	// Thx Abel Berenstein  aberenstein#afip.gov.ar
-	$len = strlen($val);
-	$chunk_size = 32768;
-	$tail_size = $len % $chunk_size;
-	$n_chunks = ($len - $tail_size) / $chunk_size;
+		// replacement that solves the problem by which only the first modulus 64K /
+		// of $val are stored at the blob field ////////////////////////////////////
+		// Thx Abel Berenstein  aberenstein#afip.gov.ar
+		$len = strlen($val);
+		$chunk_size = 32768;
+		$tail_size = $len % $chunk_size;
+		$n_chunks = ($len - $tail_size) / $chunk_size;
 
-	for ($n = 0; $n < $n_chunks; $n++) {
-		$start = $n * $chunk_size;
-		$data = substr($val, $start, $chunk_size);
-		ibase_blob_add($blob_id, $data);
-	}
+		for ($n = 0; $n < $n_chunks; $n++) {
+			$start = $n * $chunk_size;
+			$data = substr($val, $start, $chunk_size);
+			ibase_blob_add($blob_id, $data);
+		}
 
-	if ($tail_size) {
-		$start = $n_chunks * $chunk_size;
-		$data = substr($val, $start, $tail_size);
-		ibase_blob_add($blob_id, $data);
-	}
-	// end replacement /////////////////////////////////////////////////////////
+		if ($tail_size) {
+			$start = $n_chunks * $chunk_size;
+			$data = substr($val, $start, $tail_size);
+			ibase_blob_add($blob_id, $data);
+		}
+		// end replacement /////////////////////////////////////////////////////////
 
-	$blob_id_str = ibase_blob_close($blob_id);
+		$blob_id_str = ibase_blob_close($blob_id);
 
-	return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false;
+		return $this->Execute("UPDATE $table SET $column=(?) WHERE $where", array($blob_id_str)) != false;
 
 	}
 
@@ -724,7 +734,7 @@ class ADODB_ibase extends ADOConnection {
 	Class Name: Recordset
 --------------------------------------------------------------------------------------*/
 
-class ADORecordset_ibase extends ADORecordSet
+class ADORecordSet_ibase extends ADORecordSet
 {
 
 	var $databaseType = "ibase";
@@ -844,13 +854,12 @@ class ADORecordset_ibase extends ADORecordSet
 		}
 
 		return $this->fields[$this->bind[strtoupper($colname)]];
-
 	}
 
 
 	function _close()
 	{
-			return @ibase_free_result($this->_queryID);
+		return @ibase_free_result($this->_queryID);
 	}
 
 	function MetaType($t,$len=-1,$fieldobj=false)
@@ -860,28 +869,41 @@ class ADORecordset_ibase extends ADORecordSet
 			$t = $fieldobj->type;
 			$len = $fieldobj->max_length;
 		}
-		switch (strtoupper($t)) {
-		case 'CHAR':
-			return 'C';
-
-		case 'TEXT':
-		case 'VARCHAR':
-		case 'VARYING':
-		if ($len <= $this->blobSize) return 'C';
-			return 'X';
-		case 'BLOB':
-			return 'B';
-
-		case 'TIMESTAMP':
-		case 'DATE': return 'D';
-		case 'TIME': return 'T';
-				//case 'T': return 'T';
-
-				//case 'L': return 'L';
-		case 'INT':
-		case 'SHORT':
-		case 'INTEGER': return 'I';
-		default: return ADODB_DEFAULT_METATYPE;
+
+		$t = strtoupper($t);
+
+		if (array_key_exists($t, $this->connection->customActualTypes)) {
+			return $this->connection->customActualTypes[$t];
+		}
+
+		switch ($t) {
+			case 'CHAR':
+				return 'C';
+
+			case 'TEXT':
+			case 'VARCHAR':
+			case 'VARYING':
+				if ($len <= $this->blobSize) {
+					return 'C';
+				}
+				return 'X';
+			case 'BLOB':
+				return 'B';
+
+			case 'TIMESTAMP':
+			case 'DATE':
+				return 'D';
+			case 'TIME':
+				return 'T';
+			//case 'T': return 'T';
+
+			//case 'L': return 'L';
+			case 'INT':
+			case 'SHORT':
+			case 'INTEGER':
+				return 'I';
+			default:
+				return ADODB_DEFAULT_METATYPE;
 		}
 	}
 
diff --git a/drivers/adodb-informix72.inc.php b/drivers/adodb-informix72.inc.php
index a9c43e2..6fddde3 100644
--- a/drivers/adodb-informix72.inc.php
+++ b/drivers/adodb-informix72.inc.php
@@ -80,12 +80,9 @@ class ADODB_informix72 extends ADOConnection {
 
 	function ServerInfo()
 	{
-	    if (isset($this->version)) return $this->version;
-
-	    $arr['description'] = $this->GetOne("select DBINFO('version','full') from systables where tabid = 1");
-	    $arr['version'] = $this->GetOne("select DBINFO('version','major') || DBINFO('version','minor') from systables where tabid = 1");
-	    $this->version = $arr;
-	    return $arr;
+		$arr['description'] = $this->GetOne("select DBINFO('version','full') from systables where tabid = 1");
+		$arr['version'] = $this->GetOne("select DBINFO('version','major') || DBINFO('version','minor') from systables where tabid = 1");
+		return $arr;
 	}
 
 
@@ -253,12 +250,12 @@ class ADODB_informix72 extends ADOConnection {
 		return $false;
 	}
 
-   function xMetaColumns($table)
-   {
+	function xMetaColumns($table)
+	{
 		return ADOConnection::MetaColumns($table,false);
-   }
+	}
 
-	 function MetaForeignKeys($table, $owner=false, $upper=false) //!Eos
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 		$sql = "
 			select tr.tabname,updrule,delrule,
@@ -337,7 +334,6 @@ class ADODB_informix72 extends ADOConnection {
 		else return array($sql,$stmt);
 	}
 */
-	// returns query ID if successful, otherwise false
 	function _query($sql,$inputarr=false)
 	{
 	global $ADODB_COUNTRECS;
diff --git a/drivers/adodb-ldap.inc.php b/drivers/adodb-ldap.inc.php
index 323b358..305f6a7 100644
--- a/drivers/adodb-ldap.inc.php
+++ b/drivers/adodb-ldap.inc.php
@@ -157,7 +157,6 @@ class ADODB_ldap extends ADOConnection {
 		}
 	}
 
-	/* returns _queryID or false */
 	function _query($sql,$inputarr=false)
 	{
 		$rs = @ldap_search( $this->_connectionID, $this->database, $sql );
diff --git a/drivers/adodb-mssql.inc.php b/drivers/adodb-mssql.inc.php
index 3de3f8d..c0b714e 100644
--- a/drivers/adodb-mssql.inc.php
+++ b/drivers/adodb-mssql.inc.php
@@ -429,7 +429,7 @@ class ADODB_mssql extends ADOConnection {
 		return $indexes;
 	}
 
-	function MetaForeignKeys($table, $owner=false, $upper=false)
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 	global $ADODB_FETCH_MODE;
 
@@ -541,7 +541,6 @@ order by constraint_name, referenced_table_name, keyno";
 	function SelectDB($dbName)
 	{
 		$this->database = $dbName;
-		$this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
 		if ($this->_connectionID) {
 			return @mssql_select_db($dbName);
 		}
@@ -726,7 +725,6 @@ order by constraint_name, referenced_table_name, keyno";
 		return $this->Execute($sql) != false;
 	}
 
-	// returns query ID if successful, otherwise false
 	function _query($sql,$inputarr=false)
 	{
 		$this->_errorMsg = false;
diff --git a/drivers/adodb-mssqlnative.inc.php b/drivers/adodb-mssqlnative.inc.php
index 36dc543..f7e1bcc 100644
--- a/drivers/adodb-mssqlnative.inc.php
+++ b/drivers/adodb-mssqlnative.inc.php
@@ -151,6 +151,8 @@ class ADODB_mssqlnative extends ADOConnection {
 
 		$arrServerInfo = sqlsrv_server_info($this->_connectionID);
 		$ADODB_FETCH_MODE = $savem;
+
+		$arr = array();
 		$arr['description'] = $arrServerInfo['SQLServerName'].' connected to '.$arrServerInfo['CurrentDatabase'];
 		$arr['version'] = $arrServerInfo['SQLServerVersion'];//ADOConnection::_findvers($arr['description']);
 		return $arr;
@@ -182,8 +184,10 @@ class ADODB_mssqlnative extends ADOConnection {
 
 	function _affectedrows()
 	{
-		if ($this->_queryID)
-		return sqlsrv_rows_affected($this->_queryID);
+		if ($this->_queryID && is_resource($this->_queryID)) {
+			return sqlsrv_rows_affected($this->_queryID);
+		}
+		return false;
 	}
 
 	function GenID($seq='adodbseq',$start=1) {
@@ -436,7 +440,6 @@ class ADODB_mssqlnative extends ADOConnection {
 	function SelectDB($dbName)
 	{
 		$this->database = $dbName;
-		$this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
 		if ($this->_connectionID) {
 			$rs = $this->Execute('USE '.$dbName);
 			if($rs) {
@@ -643,13 +646,6 @@ class ADODB_mssqlnative extends ADOConnection {
 			$rez = sqlsrv_query($this->_connectionID, $sql);
 		}
 
-		if ($this->debug) {
-			ADOConnection::outp("<hr>running query: " . var_export($sql, true)
-				. "<hr>input array: " . var_export($inputarr, true)
-				. "<hr>result: " . var_export($rez, true)
-			);
-		}
-
 		$this->lastInsID = false;
 		if (!$rez) {
 			$rez = false;
@@ -659,23 +655,27 @@ class ADODB_mssqlnative extends ADOConnection {
 			// e.g. if triggers are involved (see #41)
 			while (sqlsrv_next_result($rez)) {
 				sqlsrv_fetch($rez);
-				$this->lastInsID = sqlsrv_get_field($rez, 0, SQLSRV_PHPTYPE_INT);
+				$this->lastInsID = sqlsrv_get_field($rez, 0);
 			}
 		}
 		return $rez;
 	}
 
-	// returns true or false
+	/**
+	 * Rolls back pending transactions and closes the connection.
+	 *
+	 * @return bool True, unless the connection id is invalid
+	 */
 	function _close()
 	{
 		if ($this->transCnt) {
 			$this->RollbackTrans();
 		}
-		if($this->_connectionID) {
-			$rez = sqlsrv_close($this->_connectionID);
+		if ($this->_connectionID) {
+			return sqlsrv_close($this->_connectionID);
 		}
 		$this->_connectionID = false;
-		return $rez;
+		return true;
 	}
 
 
@@ -719,7 +719,7 @@ class ADODB_mssqlnative extends ADOConnection {
 		return $indexes;
 	}
 
-	function MetaForeignKeys($table, $owner=false, $upper=false)
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 		global $ADODB_FETCH_MODE;
 
@@ -1009,6 +1009,37 @@ class ADODB_mssqlnative extends ADOConnection {
 		return $metaProcedures;
 	}
 
+	/**
+	* An SQL Statement that adds a specific number of
+	* days or part to local datetime
+	*
+	* @param float $dayFraction
+	* @param string $date
+	*
+	* @return string
+	*/
+	public function offsetDate($dayFraction, $date = false)
+	{
+		if (!$date)
+			/*
+			* Use GETDATE() via systTimestamp;
+			*/
+			$date = $this->sysTimeStamp;
+
+		/*
+		* seconds, number of seconds, date base
+		*/
+		$dateFormat = "DATEADD(s, %s, %s)";
+
+		/*
+		* Adjust the offset back to seconds
+		*/
+		$fraction = $dayFraction * 24 * 3600;
+
+		return sprintf($dateFormat,$fraction,$date);
+
+	}
+
 }
 
 /*--------------------------------------------------------------------------------------
diff --git a/drivers/adodb-mysql.inc.php b/drivers/adodb-mysql.inc.php
deleted file mode 100644
index f07c081..0000000
--- a/drivers/adodb-mysql.inc.php
+++ /dev/null
@@ -1,932 +0,0 @@
-<?php
-/**
- * MySQL driver
- *
- * @deprecated
- *
- * This driver only supports the original non-transactional MySQL driver,
- * which was deprecated in PHP version 5.5 and removed in PHP version 7.
- * It is deprecated as of ADOdb version 5.20.0, use the mysqli driver
- * instead, which supports both transactional and non-transactional updates.
- *
- * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
- *
- * @package ADOdb
- * @link https://adodb.org Project's web site and documentation
- * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
- *
- * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
- * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
- * any later version. This means you can use it in proprietary products.
- * See the LICENSE.md file distributed with this source code for details.
- * @license BSD-3-Clause
- * @license LGPL-2.1-or-later
- *
- * @copyright 2000-2013 John Lim
- * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
- */
-
-// security - hide paths
-if (!defined('ADODB_DIR')) die();
-
-if (! defined("_ADODB_MYSQL_LAYER")) {
-	define("_ADODB_MYSQL_LAYER", 1 );
-
-class ADODB_mysql extends ADOConnection {
-	var $databaseType = 'mysql';
-	var $dataProvider = 'mysql';
-	var $hasInsertID = true;
-	var $hasAffectedRows = true;
-	var $metaTablesSQL = "SELECT
-			TABLE_NAME,
-			CASE WHEN TABLE_TYPE = 'VIEW' THEN 'V' ELSE 'T' END
-		FROM INFORMATION_SCHEMA.TABLES
-		WHERE TABLE_SCHEMA=";
-	var $metaColumnsSQL = "SHOW COLUMNS FROM `%s`";
-	var $fmtTimeStamp = "'Y-m-d H:i:s'";
-	var $hasLimit = true;
-	var $hasMoveFirst = true;
-	var $hasGenID = true;
-	var $isoDates = true; // accepts dates in ISO format
-	var $sysDate = 'CURDATE()';
-	var $sysTimeStamp = 'NOW()';
-	var $hasTransactions = false;
-	var $forceNewConnect = false;
-	var $poorAffectedRows = true;
-	var $clientFlags = 0;
-	var $charSet = '';
-	var $substr = "substring";
-	var $nameQuote = '`';		/// string to use to quote identifiers and names
-	var $compat323 = false; 		// true if compat with mysql 3.23
-
-	/**
-	 * ADODB_mysql constructor.
-	 */
-	public function __construct() {
-		if(version_compare(PHP_VERSION, '7.0.0', '>=')) {
-			$this->outp_throw(
-				'mysql extension is not supported since PHP 7.0.0, use mysqli instead',
-				__METHOD__
-			);
-			die(1); // Stop execution even if not using Exceptions
-		} elseif(version_compare(PHP_VERSION, '5.5.0', '>=')) {
-			// If mysql extension is available just print a warning,
-			// otherwise die with an error message
-			if(function_exists('mysql_connect')) {
-				$this->outp('mysql extension is deprecated since PHP 5.5.0, consider using mysqli');
-			} else {
-				$this->outp_throw(
-					'mysql extension is not available, use mysqli instead',
-					__METHOD__
-				);
-				die(1); // Stop execution even if not using Exceptions
-			}
-		}
-	}
-
-	function setCharSet($charset)
-	{
-		if (!function_exists('mysql_set_charset')) {
-			return false;
-		}
-
-		if ($this->charSet !== $charset) {
-			$ok = @mysql_set_charset($charset,$this->_connectionID);
-			if ($ok) {
-				$this->charSet = $charset;
-				return true;
-			}
-			return false;
-		}
-		return true;
-	}
-
-	function serverInfo()
-	{
-		$arr['description'] = ADOConnection::GetOne("select version()");
-		$arr['version'] = ADOConnection::_findvers($arr['description']);
-		return $arr;
-	}
-
-	function ifNull( $field, $ifNull )
-	{
-		return " IFNULL($field, $ifNull) "; // if MySQL
-	}
-
-	function metaProcedures($NamePattern = false, $catalog = null, $schemaPattern = null)
-	{
-		// save old fetch mode
-		global $ADODB_FETCH_MODE;
-
-		$false = false;
-		$save = $ADODB_FETCH_MODE;
-		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
-
-		if ($this->fetchMode !== FALSE) {
-			$savem = $this->SetFetchMode(FALSE);
-		}
-
-		$procedures = array ();
-
-		// get index details
-
-		$likepattern = '';
-		if ($NamePattern) {
-			$likepattern = " LIKE '".$NamePattern."'";
-		}
-		$rs = $this->Execute('SHOW PROCEDURE STATUS'.$likepattern);
-		if (is_object($rs)) {
-
-			// parse index data into array
-			while ($row = $rs->FetchRow()) {
-				$procedures[$row[1]] = array(
-					'type' => 'PROCEDURE',
-					'catalog' => '',
-					'schema' => '',
-					'remarks' => $row[7],
-				);
-			}
-		}
-
-		$rs = $this->Execute('SHOW FUNCTION STATUS'.$likepattern);
-		if (is_object($rs)) {
-			// parse index data into array
-			while ($row = $rs->FetchRow()) {
-				$procedures[$row[1]] = array(
-					'type' => 'FUNCTION',
-					'catalog' => '',
-					'schema' => '',
-					'remarks' => $row[7]
-				);
-			}
-		}
-
-		// restore fetchmode
-		if (isset($savem)) {
-			$this->SetFetchMode($savem);
-		}
-		$ADODB_FETCH_MODE = $save;
-
-		return $procedures;
-	}
-
-	/**
-	 * Retrieves a list of tables based on given criteria
-	 *
-	 * @param string $ttype Table type = 'TABLE', 'VIEW' or false=both (default)
-	 * @param string $showSchema schema name, false = current schema (default)
-	 * @param string $mask filters the table by name
-	 *
-	 * @return array list of tables
-	 */
-	function metaTables($ttype=false,$showSchema=false,$mask=false)
-	{
-		$save = $this->metaTablesSQL;
-		if ($showSchema && is_string($showSchema)) {
-			$this->metaTablesSQL .= $this->qstr($showSchema);
-		} else {
-			$this->metaTablesSQL .= "schema()";
-		}
-
-		if ($mask) {
-			$mask = $this->qstr($mask);
-			$this->metaTablesSQL .= " AND table_name LIKE $mask";
-		}
-		$ret = ADOConnection::MetaTables($ttype,$showSchema);
-
-		$this->metaTablesSQL = $save;
-		return $ret;
-	}
-
-
-	function metaIndexes ($table, $primary = FALSE, $owner=false)
-	{
-		// save old fetch mode
-		global $ADODB_FETCH_MODE;
-
-		$false = false;
-		$save = $ADODB_FETCH_MODE;
-		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
-		if ($this->fetchMode !== FALSE) {
-			$savem = $this->SetFetchMode(FALSE);
-		}
-
-		// get index details
-		$rs = $this->Execute(sprintf('SHOW INDEX FROM %s',$table));
-
-		// restore fetchmode
-		if (isset($savem)) {
-			$this->SetFetchMode($savem);
-		}
-		$ADODB_FETCH_MODE = $save;
-
-		if (!is_object($rs)) {
-			return $false;
-		}
-
-		$indexes = array ();
-
-		// parse index data into array
-		while ($row = $rs->FetchRow()) {
-			if ($primary == FALSE AND $row[2] == 'PRIMARY') {
-				continue;
-			}
-
-			if (!isset($indexes[$row[2]])) {
-				$indexes[$row[2]] = array(
-					'unique' => ($row[1] == 0),
-					'columns' => array()
-				);
-			}
-
-			$indexes[$row[2]]['columns'][$row[3] - 1] = $row[4];
-		}
-
-		// sort columns by order in the index
-		foreach ( array_keys ($indexes) as $index )
-		{
-			ksort ($indexes[$index]['columns']);
-		}
-
-		return $indexes;
-	}
-
-
-	/**
-	 * Appropriately quotes strings with ' characters for insertion into the database.
-	 *
-	 * Relies on mysql_real_escape_string()
-	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:qstr
-	 *
-	 * @param string $s            The string to quote
-	 * @param bool   $magic_quotes This param is not used since 5.21.0.
-	 *                             It remains for backwards compatibility.
-	 *
-	 * @return string Quoted string
-	 */
-	function qStr($s, $magic_quotes=false)
-	{
-		if (is_null($s)) {
-			return 'NULL';
-		}
-
-		if (is_resource($this->_connectionID)) {
-			return "'" . mysql_real_escape_string($s, $this->_connectionID) . "'";
-		}
-
-		if ($this->replaceQuote[0] == '\\') {
-			$s = str_replace(array('\\', "\0"), array('\\\\', "\\\0"), $s);
-		}
-		return "'" . str_replace("'", $this->replaceQuote, $s) . "'";
-	}
-
-	protected function _insertID($table = '', $column = '')
-	{
-		return ADOConnection::GetOne('SELECT LAST_INSERT_ID()');
-		//return mysql_insert_id($this->_connectionID);
-	}
-
-	function getOne($sql,$inputarr=false)
-	{
-	global $ADODB_GETONE_EOF;
-		if ($this->compat323 == false && strncasecmp($sql,'sele',4) == 0) {
-			$rs = $this->SelectLimit($sql,1,-1,$inputarr);
-			if ($rs) {
-				$rs->Close();
-				if ($rs->EOF) return $ADODB_GETONE_EOF;
-				return reset($rs->fields);
-			}
-		} else {
-			return ADOConnection::GetOne($sql,$inputarr);
-		}
-		return false;
-	}
-
-	function beginTrans()
-	{
-		if ($this->debug) ADOConnection::outp("Transactions not supported in 'mysql' driver. Use 'mysqlt' or 'mysqli' driver");
-	}
-
-	function _affectedrows()
-	{
-			return mysql_affected_rows($this->_connectionID);
-	}
-
-	 // See http://www.mysql.com/doc/M/i/Miscellaneous_functions.html
-	// Reference on Last_Insert_ID on the recommended way to simulate sequences
-	var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);";
-	var $_genSeqSQL = "create table if not exists %s (id int not null)";
-	var $_genSeqCountSQL = "select count(*) from %s";
-	var $_genSeq2SQL = "insert into %s values (%s)";
-	var $_dropSeqSQL = "drop table if exists %s";
-
-	function createSequence($seqname='adodbseq',$startID=1)
-	{
-		if (empty($this->_genSeqSQL)) return false;
-		$u = strtoupper($seqname);
-
-		$ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname));
-		if (!$ok) return false;
-		return $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
-	}
-
-
-	function genID($seqname='adodbseq',$startID=1)
-	{
-		// post-nuke sets hasGenID to false
-		if (!$this->hasGenID) return false;
-
-		$savelog = $this->_logsql;
-		$this->_logsql = false;
-		$getnext = sprintf($this->_genIDSQL,$seqname);
-		$holdtransOK = $this->_transOK; // save the current status
-		$rs = @$this->Execute($getnext);
-		if (!$rs) {
-			if ($holdtransOK) $this->_transOK = true; //if the status was ok before reset
-			$u = strtoupper($seqname);
-			$this->Execute(sprintf($this->_genSeqSQL,$seqname));
-			$cnt = $this->GetOne(sprintf($this->_genSeqCountSQL,$seqname));
-			if (!$cnt) $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
-			$rs = $this->Execute($getnext);
-		}
-
-		if ($rs) {
-			$this->genID = mysql_insert_id($this->_connectionID);
-			$rs->Close();
-		} else
-			$this->genID = 0;
-
-		$this->_logsql = $savelog;
-		return $this->genID;
-	}
-
-	function metaDatabases()
-	{
-		$qid = mysql_list_dbs($this->_connectionID);
-		$arr = array();
-		$i = 0;
-		$max = mysql_num_rows($qid);
-		while ($i < $max) {
-			$db = mysql_tablename($qid,$i);
-			if ($db != 'mysql') $arr[] = $db;
-			$i += 1;
-		}
-		return $arr;
-	}
-
-
-	// Format date column in sql string given an input format that understands Y M D
-	function sqlDate($fmt, $col=false)
-	{
-		if (!$col) $col = $this->sysTimeStamp;
-		$s = 'DATE_FORMAT('.$col.",'";
-		$concat = false;
-		$len = strlen($fmt);
-		for ($i=0; $i < $len; $i++) {
-			$ch = $fmt[$i];
-			switch($ch) {
-
-			default:
-				if ($ch == '\\') {
-					$i++;
-					$ch = substr($fmt,$i,1);
-				}
-				/** FALL THROUGH */
-			case '-':
-			case '/':
-				$s .= $ch;
-				break;
-
-			case 'Y':
-			case 'y':
-				$s .= '%Y';
-				break;
-			case 'M':
-				$s .= '%b';
-				break;
-
-			case 'm':
-				$s .= '%m';
-				break;
-			case 'D':
-			case 'd':
-				$s .= '%d';
-				break;
-
-			case 'Q':
-			case 'q':
-				$s .= "'),Quarter($col)";
-
-				if ($len > $i+1) $s .= ",DATE_FORMAT($col,'";
-				else $s .= ",('";
-				$concat = true;
-				break;
-
-			case 'H':
-				$s .= '%H';
-				break;
-
-			case 'h':
-				$s .= '%I';
-				break;
-
-			case 'i':
-				$s .= '%i';
-				break;
-
-			case 's':
-				$s .= '%s';
-				break;
-
-			case 'a':
-			case 'A':
-				$s .= '%p';
-				break;
-
-			case 'w':
-				$s .= '%w';
-				break;
-
-			 case 'W':
-				$s .= '%U';
-				break;
-
-			case 'l':
-				$s .= '%W';
-				break;
-			}
-		}
-		$s.="')";
-		if ($concat) $s = "CONCAT($s)";
-		return $s;
-	}
-
-
-	// returns concatenated string
-	// much easier to run "mysqld --ansi" or "mysqld --sql-mode=PIPES_AS_CONCAT" and use || operator
-	function concat()
-	{
-		$s = "";
-		$arr = func_get_args();
-
-		// suggestion by andrew005@mnogo.ru
-		$s = implode(',',$arr);
-		if (strlen($s) > 0) return "CONCAT($s)";
-		else return '';
-	}
-
-	function offsetDate($dayFraction,$date=false)
-	{
-		if (!$date) $date = $this->sysDate;
-
-		$fraction = $dayFraction * 24 * 3600;
-		return '('. $date . ' + INTERVAL ' .	 $fraction.' SECOND)';
-
-//		return "from_unixtime(unix_timestamp($date)+$fraction)";
-	}
-
-	// returns true or false
-	function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
-	{
-		if (!empty($this->port))
-			$argHostname .= ":".$this->port;
-
-		$this->_connectionID =
-			mysql_connect($argHostname,
-						  $argUsername,
-						  $argPassword,
-						  $this->forceNewConnect,
-						  $this->clientFlags
-						  );
-
-
-		if ($this->_connectionID === false)
-			return false;
-		if ($argDatabasename)
-			return $this->SelectDB($argDatabasename);
-
-		return true;
-	}
-
-	// returns true or false
-	function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
-	{
-		if (!empty($this->port)) $argHostname .= ":".$this->port;
-
-		$this->_connectionID =
-			mysql_pconnect($argHostname,
-						   $argUsername,
-						   $argPassword,
-						   $this->clientFlags);
-
-		if ($this->_connectionID === false)
-			return false;
-		if ($this->autoRollback)
-			$this->RollbackTrans();
-		if ($argDatabasename)
-			return $this->SelectDB($argDatabasename);
-		return true;
-	}
-
-	function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
-	{
-		$this->forceNewConnect = true;
-		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
-	}
-
-	function metaColumns($table, $normalize=true)
-	{
-		$this->_findschema($table,$schema);
-		if ($schema) {
-			$dbName = $this->database;
-			$this->SelectDB($schema);
-		}
-		global $ADODB_FETCH_MODE;
-		$save = $ADODB_FETCH_MODE;
-		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
-
-		if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
-		$rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
-
-		if ($schema) {
-			$this->SelectDB($dbName);
-		}
-
-		if (isset($savem)) $this->SetFetchMode($savem);
-		$ADODB_FETCH_MODE = $save;
-		if (!is_object($rs)) {
-			$false = false;
-			return $false;
-		}
-
-		$retarr = array();
-		while (!$rs->EOF){
-			$fld = new ADOFieldObject();
-			$fld->name = $rs->fields[0];
-			$type = $rs->fields[1];
-
-			// split type into type(length):
-			$fld->scale = null;
-			if (preg_match("/^(.+)\((\d+),(\d+)/", $type, $query_array)) {
-				$fld->type = $query_array[1];
-				$fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
-				$fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1;
-			} elseif (preg_match("/^(.+)\((\d+)/", $type, $query_array)) {
-				$fld->type = $query_array[1];
-				$fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
-			} elseif (preg_match("/^(enum)\((.*)\)$/i", $type, $query_array)) {
-				$fld->type = $query_array[1];
-				$arr = explode(",",$query_array[2]);
-				$fld->enums = $arr;
-				$zlen = max(array_map("strlen",$arr)) - 2; // PHP >= 4.0.6
-				$fld->max_length = ($zlen > 0) ? $zlen : 1;
-			} else {
-				$fld->type = $type;
-				$fld->max_length = -1;
-			}
-			$fld->not_null = ($rs->fields[2] != 'YES');
-			$fld->primary_key = ($rs->fields[3] == 'PRI');
-			$fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false);
-			$fld->binary = (strpos($type,'blob') !== false || strpos($type,'binary') !== false);
-			$fld->unsigned = (strpos($type,'unsigned') !== false);
-			$fld->zerofill = (strpos($type,'zerofill') !== false);
-
-			if (!$fld->binary) {
-				$d = $rs->fields[4];
-				if ($d != '' && $d != 'NULL') {
-					$fld->has_default = true;
-					$fld->default_value = $d;
-				} else {
-					$fld->has_default = false;
-				}
-			}
-
-			if ($save == ADODB_FETCH_NUM) {
-				$retarr[] = $fld;
-			} else {
-				$retarr[strtoupper($fld->name)] = $fld;
-			}
-				$rs->MoveNext();
-			}
-
-			$rs->Close();
-			return $retarr;
-	}
-
-	// returns true or false
-	function selectDB($dbName)
-	{
-		$this->database = $dbName;
-		$this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
-		if ($this->_connectionID) {
-			return @mysql_select_db($dbName,$this->_connectionID);
-		}
-		else return false;
-	}
-
-	// parameters use PostgreSQL convention, not MySQL
-	function selectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs=0)
-	{
-		$nrows = (int) $nrows;
-		$offset = (int) $offset;
-		$offsetStr =($offset>=0) ? ((integer)$offset)."," : '';
-		// jason judge, see PHPLens Issue No: 9220
-		if ($nrows < 0) $nrows = '18446744073709551615';
-
-		if ($secs)
-			$rs = $this->CacheExecute($secs,$sql." LIMIT $offsetStr".((integer)$nrows),$inputarr);
-		else
-			$rs = $this->Execute($sql." LIMIT $offsetStr".((integer)$nrows),$inputarr);
-		return $rs;
-	}
-
-	// returns queryID or false
-	function _query($sql,$inputarr=false)
-	{
-
-	return mysql_query($sql,$this->_connectionID);
-	/*
-	global $ADODB_COUNTRECS;
-		if($ADODB_COUNTRECS)
-			return mysql_query($sql,$this->_connectionID);
-		else
-			return @mysql_unbuffered_query($sql,$this->_connectionID); // requires PHP >= 4.0.6
-	*/
-	}
-
-	/*	Returns: the last error message from previous database operation	*/
-	function errorMsg()
-	{
-
-		if ($this->_logsql) return $this->_errorMsg;
-		if (empty($this->_connectionID)) $this->_errorMsg = @mysql_error();
-		else $this->_errorMsg = @mysql_error($this->_connectionID);
-		return $this->_errorMsg;
-	}
-
-	/*	Returns: the last error number from previous database operation	*/
-	function errorNo()
-	{
-		if ($this->_logsql) return $this->_errorCode;
-		if (empty($this->_connectionID)) return @mysql_errno();
-		else return @mysql_errno($this->_connectionID);
-	}
-
-	// returns true or false
-	function _close()
-	{
-		@mysql_close($this->_connectionID);
-
-		$this->charSet = '';
-		$this->_connectionID = false;
-	}
-
-
-	/*
-	* Maximum size of C field
-	*/
-	function charMax()
-	{
-		return 255;
-	}
-
-	/*
-	* Maximum size of X field
-	*/
-	function textMax()
-	{
-		return 4294967295;
-	}
-
-	// "Innox - Juan Carlos Gonzalez" <jgonzalez#innox.com.mx>
-	function metaForeignKeys( $table, $owner = FALSE, $upper = FALSE, $associative = FALSE )
-	{
-	 global $ADODB_FETCH_MODE;
-		if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC || $this->fetchMode == ADODB_FETCH_ASSOC) $associative = true;
-
-		if ( !empty($owner) ) {
-			$table = "$owner.$table";
-		}
-		$a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE %s', $table));
-		if ($associative) {
-			$create_sql = isset($a_create_table["Create Table"]) ? $a_create_table["Create Table"] : $a_create_table["Create View"];
-		} else {
-			$create_sql = $a_create_table[1];
-		}
-
-		$matches = array();
-
-		if (!preg_match_all("/FOREIGN KEY \(`(.*?)`\) REFERENCES `(.*?)` \(`(.*?)`\)/", $create_sql, $matches)) return false;
-		$foreign_keys = array();
-		$num_keys = count($matches[0]);
-		for ( $i = 0; $i < $num_keys; $i ++ ) {
-			$my_field  = explode('`, `', $matches[1][$i]);
-			$ref_table = $matches[2][$i];
-			$ref_field = explode('`, `', $matches[3][$i]);
-
-			if ( $upper ) {
-				$ref_table = strtoupper($ref_table);
-			}
-
-			// see https://sourceforge.net/p/adodb/bugs/100/
-			if (!isset($foreign_keys[$ref_table])) {
-				$foreign_keys[$ref_table] = array();
-			}
-			$num_fields = count($my_field);
-			for ( $j = 0; $j < $num_fields; $j ++ ) {
-				if ( $associative ) {
-					$foreign_keys[$ref_table][$ref_field[$j]] = $my_field[$j];
-				} else {
-					$foreign_keys[$ref_table][] = "{$my_field[$j]}={$ref_field[$j]}";
-				}
-			}
-		}
-
-		return $foreign_keys;
-	}
-
-
-}
-
-/*--------------------------------------------------------------------------------------
-	 Class Name: Recordset
---------------------------------------------------------------------------------------*/
-
-
-class ADORecordSet_mysql extends ADORecordSet{
-
-	var $databaseType = "mysql";
-	var $canSeek = true;
-
-	function __construct($queryID,$mode=false)
-	{
-		if ($mode === false) {
-			global $ADODB_FETCH_MODE;
-			$mode = $ADODB_FETCH_MODE;
-		}
-		switch ($mode)
-		{
-		case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break;
-		case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break;
-		case ADODB_FETCH_DEFAULT:
-		case ADODB_FETCH_BOTH:
-		default:
-			$this->fetchMode = MYSQL_BOTH; break;
-		}
-		$this->adodbFetchMode = $mode;
-		parent::__construct($queryID);
-	}
-
-	function _initrs()
-	{
-	//GLOBAL $ADODB_COUNTRECS;
-	//	$this->_numOfRows = ($ADODB_COUNTRECS) ? @mysql_num_rows($this->_queryID):-1;
-		$this->_numOfRows = @mysql_num_rows($this->_queryID);
-		$this->_numOfFields = @mysql_num_fields($this->_queryID);
-	}
-
-	function fetchField($fieldOffset = -1)
-	{
-		if ($fieldOffset != -1) {
-			$o = @mysql_fetch_field($this->_queryID, $fieldOffset);
-			$f = @mysql_field_flags($this->_queryID,$fieldOffset);
-			if ($o) $o->max_length = @mysql_field_len($this->_queryID,$fieldOffset); // suggested by: Jim Nicholson (jnich#att.com)
-			//$o->max_length = -1; // mysql returns the max length less spaces -- so it is unrealiable
-			if ($o) $o->binary = (strpos($f,'binary')!== false);
-		}
-		else {	/*	The $fieldOffset argument is not provided thus its -1 	*/
-			$o = @mysql_fetch_field($this->_queryID);
-			//if ($o) $o->max_length = @mysql_field_len($this->_queryID); // suggested by: Jim Nicholson (jnich#att.com)
-			$o->max_length = -1; // mysql returns the max length less spaces -- so it is unrealiable
-		}
-
-		return $o;
-	}
-
-	function getRowAssoc($upper = ADODB_ASSOC_CASE)
-	{
-		if ($this->fetchMode == MYSQL_ASSOC && $upper == ADODB_ASSOC_CASE_LOWER) {
-			$row = $this->fields;
-		}
-		else {
-			$row = ADORecordSet::GetRowAssoc($upper);
-		}
-		return $row;
-	}
-
-	/* Use associative array to get fields array */
-	function fields($colname)
-	{
-		// added @ by "Michael William Miller" <mille562@pilot.msu.edu>
-		if ($this->fetchMode != MYSQL_NUM) return @$this->fields[$colname];
-
-		if (!$this->bind) {
-			$this->bind = array();
-			for ($i=0; $i < $this->_numOfFields; $i++) {
-				$o = $this->FetchField($i);
-				$this->bind[strtoupper($o->name)] = $i;
-			}
-		}
-		 return $this->fields[$this->bind[strtoupper($colname)]];
-	}
-
-	function _seek($row)
-	{
-		if ($this->_numOfRows == 0) return false;
-		return @mysql_data_seek($this->_queryID,$row);
-	}
-
-	function moveNext()
-	{
-		if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) {
-			$this->_updatefields();
-			$this->_currentRow += 1;
-			return true;
-		}
-		if (!$this->EOF) {
-			$this->_currentRow += 1;
-			$this->EOF = true;
-		}
-		return false;
-	}
-
-	function _fetch()
-	{
-		$this->fields = @mysql_fetch_array($this->_queryID,$this->fetchMode);
-		$this->_updatefields();
-		return is_array($this->fields);
-	}
-
-	function _close() {
-		@mysql_free_result($this->_queryID);
-		$this->_queryID = false;
-	}
-
-	function metaType($t,$len=-1,$fieldobj=false)
-	{
-		if (is_object($t)) {
-			$fieldobj = $t;
-			$t = $fieldobj->type;
-			$len = $fieldobj->max_length;
-		}
-
-		$len = -1; // mysql max_length is not accurate
-		switch (strtoupper($t)) {
-		case 'STRING':
-		case 'CHAR':
-		case 'VARCHAR':
-		case 'TINYBLOB':
-		case 'TINYTEXT':
-		case 'ENUM':
-		case 'SET':
-			if ($len <= $this->blobSize) return 'C';
-
-		case 'TEXT':
-		case 'LONGTEXT':
-		case 'MEDIUMTEXT':
-			return 'X';
-
-		// php_mysql extension always returns 'blob' even if 'text'
-		// so we have to check whether binary...
-		case 'IMAGE':
-		case 'LONGBLOB':
-		case 'BLOB':
-		case 'MEDIUMBLOB':
-		case 'BINARY':
-			return !empty($fieldobj->binary) ? 'B' : 'X';
-
-		case 'YEAR':
-		case 'DATE': return 'D';
-
-		case 'TIME':
-		case 'DATETIME':
-		case 'TIMESTAMP': return 'T';
-
-		case 'INT':
-		case 'INTEGER':
-		case 'BIGINT':
-		case 'TINYINT':
-		case 'MEDIUMINT':
-		case 'SMALLINT':
-
-			if (!empty($fieldobj->primary_key)) return 'R';
-			else return 'I';
-
-		default: return ADODB_DEFAULT_METATYPE;
-		}
-	}
-
-}
-
-/**
- * Class ADORecordSet_ext_mysql
- */
-class ADORecordSet_ext_mysql extends ADORecordSet_mysql {
-
-	function moveNext()
-	{
-		return @adodb_movenext($this);
-	}
-}
-
-}
diff --git a/drivers/adodb-mysqli.inc.php b/drivers/adodb-mysqli.inc.php
index 160f5fa..5271db1 100644
--- a/drivers/adodb-mysqli.inc.php
+++ b/drivers/adodb-mysqli.inc.php
@@ -75,12 +75,17 @@ class ADODB_mysqli extends ADOConnection {
 	var $ssl_capath = null;
 	var $ssl_cipher = null;
 
+	/** @var mysqli Identifier for the native database connection */
+	var $_connectionID = false;
+
 	/**
 	 * Tells the insert_id method how to obtain the last value, depending on whether
 	 * we are using a stored procedure or not
 	 */
 	private $usePreparedStatement = false;
 	private $useLastInsertStatement = false;
+	private $usingBoundVariables = false;
+	private $statementAffectedRows = -1;
 
 	/**
 	 * @var bool True if the last executed statement is a SELECT {@see _query()}
@@ -123,13 +128,12 @@ class ADODB_mysqli extends ADOConnection {
 	/**
 	 * Adds a parameter to the connection string.
 	 *
-	 * Parameter must be one of the the constants listed in mysqli_options().
+	 * Parameter must be one of the constants listed in mysqli_options().
 	 * @see https://www.php.net/manual/en/mysqli.options.php
 	 *
-	 * @param int $parameter The parameter to set
-	 * @param string $value The value of the parameter
+	 * @param int    $parameter The parameter to set
+	 * @param string $value     The value of the parameter
 	 *
-	 * @example, for mssqlnative driver ('CharacterSet','UTF-8')
 	 * @return bool
 	 */
 	public function setConnectionParameter($parameter, $value) {
@@ -137,8 +141,7 @@ class ADODB_mysqli extends ADOConnection {
 			$this->outp_throw("Invalid connection parameter '$parameter'", __METHOD__);
 			return false;
 		}
-		$this->connectionParameters[$parameter] = $value;
-		return true;
+		return parent::setConnectionParameter($parameter, $value);
 	}
 
 	/**
@@ -164,6 +167,14 @@ class ADODB_mysqli extends ADOConnection {
 		if(!extension_loaded("mysqli")) {
 			return null;
 		}
+		// Check for a function that only exists in mysqlnd
+		if (!function_exists('mysqli_stmt_get_result')) {
+			// @TODO This will be treated as if the mysqli extension were not available
+			// This could be misleading, so we output an additional error message.
+			// We should probably throw a specific exception instead.
+			$this->outp("MySQL Native Driver (msqlnd) required");
+			return null;
+		}
 		$this->_connectionID = @mysqli_init();
 
 		if (is_null($this->_connectionID)) {
@@ -184,13 +195,15 @@ class ADODB_mysqli extends ADOConnection {
 		}
 
 		// Now merge in the standard connection parameters setting
-		foreach ($this->connectionParameters as $parameter => $value) {
-			// Make sure parameter is numeric before calling mysqli_options()
-			// that to avoid Warning (or TypeError exception on PHP 8).
-			if (!is_numeric($parameter)
-				|| !mysqli_options($this->_connectionID, $parameter, $value)
-			) {
-				$this->outp_throw("Invalid connection parameter '$parameter'", __METHOD__);
+		foreach ($this->connectionParameters as $options) {
+			foreach ($options as $parameter => $value) {
+				// Make sure parameter is numeric before calling mysqli_options()
+				// to avoid Warning (or TypeError exception on PHP 8).
+				if (!is_numeric($parameter)
+					|| !mysqli_options($this->_connectionID, $parameter, $value)
+				) {
+					$this->outp_throw("Invalid connection parameter '$parameter'", __METHOD__);
+				}
 			}
 		}
 
@@ -202,9 +215,12 @@ class ADODB_mysqli extends ADOConnection {
 		// SSL Connections for MySQLI
 		if ($this->ssl_key || $this->ssl_cert || $this->ssl_ca || $this->ssl_capath || $this->ssl_cipher) {
 			mysqli_ssl_set($this->_connectionID, $this->ssl_key, $this->ssl_cert, $this->ssl_ca, $this->ssl_capath, $this->ssl_cipher);
+			$this->socket = MYSQLI_CLIENT_SSL;
+			$this->clientFlags = MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
 		}
 
 		#if (!empty($this->port)) $argHostname .= ":".$this->port;
+		/** @noinspection PhpCastIsUnnecessaryInspection */
 		$ok = @mysqli_real_connect($this->_connectionID,
 					$argHostname,
 					$argUsername,
@@ -252,15 +268,15 @@ class ADODB_mysqli extends ADOConnection {
 	 * @param string|null $argHostname The host to connect to.
 	 * @param string|null $argUsername The username to connect as.
 	 * @param string|null $argPassword The password to connect with.
-	 * @param string|null $argDatabasename The name of the database to start in when connected.
+	 * @param string|null $argDatabaseName The name of the database to start in when connected.
 	 *
 	 * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension
 	 * isn't currently loaded.
 	 */
-	function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
+	function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName)
 	{
 		$this->forceNewConnect = true;
-		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
+		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
 	}
 
 	/**
@@ -376,17 +392,17 @@ class ADODB_mysqli extends ADOConnection {
 	 *
 	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:rowlock
 	 *
-	 * @param string $tables The table(s) to lock rows for.
+	 * @param string $table The table(s) to lock rows for.
 	 * @param string $where (Optional) The WHERE clause to use to determine which rows to lock.
 	 * @param string $col (Optional) The columns to select.
 	 *
 	 * @return bool True if the locking SQL statement executed successfully, otherwise false.
 	 */
-	function RowLock($tables, $where = '', $col = '1 as adodbignore')
+	function RowLock($table, $where = '', $col = '1 as adodbignore')
 	{
 		if ($this->transCnt==0) $this->beginTrans();
 		if ($where) $where = ' where '.$where;
-		$rs = $this->execute("select $col from $tables $where for update");
+		$rs = $this->execute("select $col from $table $where for update");
 		return !empty($rs);
 	}
 
@@ -457,10 +473,17 @@ class ADODB_mysqli extends ADOConnection {
 			// the rowcount, but ADOdb does not do that.
 			return false;
 		}
-
-		$result =  @mysqli_affected_rows($this->_connectionID);
-		if ($result == -1) {
-			if ($this->debug) ADOConnection::outp("mysqli_affected_rows() failed : "  . $this->errorMsg());
+		else if ($this->statementAffectedRows >= 0)
+		{
+			$result = $this->statementAffectedRows;
+			$this->statementAffectedRows = -1;
+		}
+		else
+		{
+			$result =  @mysqli_affected_rows($this->_connectionID);
+			if ($result == -1) {
+				if ($this->debug) ADOConnection::outp("mysqli_affected_rows() failed : "  . $this->errorMsg());
+			}
 		}
 		return $result;
 	}
@@ -566,7 +589,6 @@ class ADODB_mysqli extends ADOConnection {
 		// save old fetch mode
 		global $ADODB_FETCH_MODE;
 
-		$false = false;
 		$save = $ADODB_FETCH_MODE;
 		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
 		if ($this->fetchMode !== FALSE) {
@@ -574,7 +596,7 @@ class ADODB_mysqli extends ADOConnection {
 		}
 
 		// get index details
-		$rs = $this->execute(sprintf('SHOW INDEXES FROM %s',$table));
+		$rs = $this->Execute(sprintf('SHOW INDEXES FROM `%s`',$table));
 
 		// restore fetchmode
 		if (isset($savem)) {
@@ -583,7 +605,7 @@ class ADODB_mysqli extends ADOConnection {
 		$ADODB_FETCH_MODE = $save;
 
 		if (!is_object($rs)) {
-			return $false;
+			return false;
 		}
 
 		$indexes = array ();
@@ -621,7 +643,7 @@ class ADODB_mysqli extends ADOConnection {
 	 * @param string $fmt The date format to use.
 	 * @param string|bool $col (Optional) The table column to date format, or if false, use NOW().
 	 *
-	 * @return bool|string The SQL DATE_FORMAT() string, or false if the provided date format was empty.
+	 * @return string The SQL DATE_FORMAT() string, or false if the provided date format was empty.
 	 */
 	function SQLDate($fmt, $col = false)
 	{
@@ -740,13 +762,13 @@ class ADODB_mysqli extends ADOConnection {
 	/**
 	 * Returns information about stored procedures and stored functions.
 	 *
-	 * @param string|bool $NamePattern (Optional) Only look for procedures/functions with a name matching this pattern.
+	 * @param string|bool $procedureNamePattern (Optional) Only look for procedures/functions with a name matching this pattern.
 	 * @param null $catalog (Optional) Unused.
 	 * @param null $schemaPattern (Optional) Unused.
 	 *
 	 * @return array
 	 */
-	function MetaProcedures($NamePattern = false, $catalog  = null, $schemaPattern  = null)
+	function MetaProcedures($procedureNamePattern = false, $catalog  = null, $schemaPattern  = null)
 	{
 		// save old fetch mode
 		global $ADODB_FETCH_MODE;
@@ -763,8 +785,8 @@ class ADODB_mysqli extends ADOConnection {
 		// get index details
 
 		$likepattern = '';
-		if ($NamePattern) {
-			$likepattern = " LIKE '".$NamePattern."'";
+		if ($procedureNamePattern) {
+			$likepattern = " LIKE '".$procedureNamePattern."'";
 		}
 		$rs = $this->execute('SHOW PROCEDURE STATUS'.$likepattern);
 		if (is_object($rs)) {
@@ -840,9 +862,8 @@ class ADODB_mysqli extends ADOConnection {
 	 *
 	 * @return array|bool An array of foreign keys, or false no foreign keys could be found.
 	 */
-	function MetaForeignKeys($table, $owner = false, $upper = false, $associative = false)
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
-
 		global $ADODB_FETCH_MODE;
 
 		if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC
@@ -856,7 +877,7 @@ class ADODB_mysqli extends ADOConnection {
 			$table = "$owner.$table";
 		}
 
-		$a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE %s', $table));
+		$a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE `%s`', $table));
 
 		$this->setFetchMode($savem);
 
@@ -919,7 +940,7 @@ class ADODB_mysqli extends ADOConnection {
 
 		$SQL = "SELECT column_name, column_type
 				  FROM information_schema.columns
-				 WHERE table_schema='{$this->databaseName}'
+				 WHERE table_schema='{$this->database}'
 				   AND table_name='$table'";
 
 		$schemaArray = $this->getAssoc($SQL);
@@ -935,7 +956,6 @@ class ADODB_mysqli extends ADOConnection {
 		while (!$rs->EOF) {
 			$fld = new ADOFieldObject();
 			$fld->name = $rs->fields[0];
-			$type = $rs->fields[1];
 
 			/*
 			* Type from information_schema returns
@@ -1005,7 +1025,6 @@ class ADODB_mysqli extends ADOConnection {
 	{
 //		$this->_connectionID = $this->mysqli_resolve_link($this->_connectionID);
 		$this->database = $dbName;
-		$this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
 
 		if ($this->_connectionID) {
 			$result = @mysqli_select_db($this->_connectionID, $dbName);
@@ -1027,7 +1046,7 @@ class ADODB_mysqli extends ADOConnection {
 	 * @param int $nrows (Optional) The limit for the number of records you want returned. By default, all results.
 	 * @param int $offset (Optional) The offset to use when selecting the results. By default, no offset.
 	 * @param array|bool $inputarr (Optional) Any parameter values required by the SQL statement, or false if none.
-	 * @param int $secs (Optional) If greater than 0, perform a cached execute. By default, normal execution.
+	 * @param int $secs2cache (Optional) If greater than 0, perform a cached execute. By default, normal execution.
 	 *
 	 * @return ADORecordSet|false The query results, or false if the query failed to execute.
 	 */
@@ -1035,15 +1054,15 @@ class ADODB_mysqli extends ADOConnection {
 						 $nrows = -1,
 						 $offset = -1,
 						 $inputarr = false,
-						 $secs = 0)
+						 $secs2cache = 0)
 	{
 		$nrows = (int) $nrows;
 		$offset = (int) $offset;
 		$offsetStr = ($offset >= 0) ? "$offset," : '';
 		if ($nrows < 0) $nrows = '18446744073709551615';
 
-		if ($secs)
-			$rs = $this->cacheExecute($secs, $sql . " LIMIT $offsetStr$nrows" , $inputarr );
+		if ($secs2cache)
+			$rs = $this->cacheExecute($secs2cache, $sql . " LIMIT $offsetStr$nrows" , $inputarr );
 		else
 			$rs = $this->execute($sql . " LIMIT $offsetStr$nrows" , $inputarr );
 
@@ -1052,6 +1071,7 @@ class ADODB_mysqli extends ADOConnection {
 
 	/**
 	 * Prepares an SQL statement and returns a handle to use.
+	 * This is not used by bound parameters anymore
 	 *
 	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:prepare
 	 * @todo update this function to handle prepared statements correctly
@@ -1079,15 +1099,102 @@ class ADODB_mysqli extends ADOConnection {
 		return array($sql,$stmt);
 	}
 
+	public function execute($sql, $inputarr = false)
+	{
+		if ($this->fnExecute) {
+			$fn = $this->fnExecute;
+			$ret = $fn($this, $sql, $inputarr);
+			if (isset($ret)) {
+				return $ret;
+			}
+		}
+
+		if ($inputarr === false || $inputarr === []) {
+			return $this->_execute($sql);
+		}
+
+		if (!is_array($inputarr)) {
+			$inputarr = array($inputarr);
+		}
+		else {
+			//remove alphanumeric placeholders
+			$inputarr = array_values($inputarr);
+		}
+
+		if (!is_array($sql)) {
+			// Check if we are bulkbinding. If so, $inputarr is a 2d array,
+			// and we make a gross assumption that all rows have the same number
+			// of columns of the same type, and use the elements of the first row
+			// to determine the MySQL bind param types.
+			if (is_array($inputarr[0])) {
+				if (!$this->bulkBind) {
+					$this->outp_throw(
+						"2D Array of values sent to execute and 'ADOdb_mysqli::bulkBind' not set",
+						'Execute'
+					);
+					return false;
+				}
+
+				$bulkTypeArray = [];
+				foreach ($inputarr as $v) {
+					if (is_string($this->bulkBind)) {
+						$typeArray = array_merge((array)$this->bulkBind, $v);
+					} else {
+						$typeArray = $this->getBindParamWithType($v);
+					}
+					$bulkTypeArray[] = $typeArray;
+				}
+				$this->bulkBind = false;
+				$ret = $this->_execute($sql, $bulkTypeArray);
+			} else {
+				$typeArray = $this->getBindParamWithType($inputarr);
+				$ret = $this->_execute($sql, $typeArray);
+			}
+		} else {
+			$ret = $this->_execute($sql, $inputarr);
+		}
+		return $ret;
+	}
+
 	/**
-	 * Return the query id.
+	 * Inserts the bind param type string at the front of the parameter array.
 	 *
-	 * @param string|array $sql
-	 * @param array $inputarr
+	 * @see https://www.php.net/manual/en/mysqli-stmt.bind-param.php
 	 *
-	 * @return bool|mysqli_result
+	 * @param array $inputArr
+	 * @return array
 	 */
-	function _query($sql, $inputarr)
+	private function getBindParamWithType($inputArr): array
+	{
+		$typeString = '';
+		foreach ($inputArr as $v) {
+			if (is_integer($v) || is_bool($v)) {
+				$typeString .= 'i';
+			} elseif (is_float($v)) {
+				$typeString .= 'd';
+			} elseif (is_object($v)) {
+				// Assume a blob
+				$typeString .= 'b';
+			} else {
+				$typeString .= 's';
+			}
+		}
+
+		// Place the field type list at the front of the parameter array.
+		// This is the mysql specific format
+		array_unshift($inputArr, $typeString);
+		return $inputArr;
+	}
+
+	/**
+	 * Execute a query.
+	 *
+	 * @param string|array $sql        Query to execute.
+	 * @param array        $inputarr   An optional array of parameters.
+	 *
+	 * @return mysqli_result|bool
+	*/
+	function _query($sql, $inputarr = false)
 	{
 		global $ADODB_COUNTRECS;
 		// Move to the next recordset, or return false if there is none. In a stored proc
@@ -1097,6 +1204,14 @@ class ADODB_mysqli extends ADOConnection {
 		// Commented out for reasons of performance. You should retrieve every recordset yourself.
 		//	if (!mysqli_next_result($this->connection->_connectionID))	return false;
 
+		// When SQL is empty, mysqli_query() throws exception on PHP 8 (#945)
+		if (!$sql) {
+			if ($this->debug) {
+				ADOConnection::outp("Empty query");
+			}
+			return false;
+		}
+
 		if (is_array($sql)) {
 
 			// Prepare() not supported because mysqli_stmt_execute does not return a recordset, but
@@ -1104,7 +1219,7 @@ class ADODB_mysqli extends ADOConnection {
 
 			$stmt = $sql[1];
 			$a = '';
-			foreach($inputarr as $k => $v) {
+			foreach($inputarr as $v) {
 				if (is_string($v)) $a .= 's';
 				else if (is_integer($v)) $a .= 'i';
 				else $a .= 'd';
@@ -1118,8 +1233,109 @@ class ADODB_mysqli extends ADOConnection {
 
 			$fnarr = array_merge( array($stmt,$a) , $inputarr);
 			call_user_func_array('mysqli_stmt_bind_param',$fnarr);
-			$ret = mysqli_stmt_execute($stmt);
-			return $ret;
+			return mysqli_stmt_execute($stmt);
+		}
+		else if (is_string($sql) && is_array($inputarr))
+		{
+
+			/*
+			* This is support for true prepared queries
+			* with bound parameters
+			*
+			* set prepared statement flags
+			*/
+			$this->usePreparedStatement = true;
+			$this->usingBoundVariables = true;
+
+			$bulkBindArray = array();
+			if (is_array($inputarr[0]))
+			{
+				$bulkBindArray = $inputarr;
+				$inputArrayCount = count($inputarr[0]) - 1;
+			}
+			else
+			{
+				$bulkBindArray[] = $inputarr;
+				$inputArrayCount = count($inputarr) - 1;
+			}
+
+
+			/*
+			* Prepare the statement with the placeholders,
+			* prepare will fail if the statement is invalid
+			* so we trap and error if necessary. Note that we
+			* are calling MySQL prepare here, not ADOdb
+			*/
+			$stmt = $this->_connectionID->prepare($sql);
+			if ($stmt === false)
+			{
+				$this->outp_throw(
+					"SQL Statement failed on preparation: " . htmlspecialchars($sql) . "'",
+					'Execute'
+				);
+				return false;
+			}
+			/*
+			* Make sure the number of parameters provided in the input
+			* array matches what the query expects. We must discount
+			* the first parameter which contains the data types in
+			* our inbound parameters
+			*/
+			$nparams = $stmt->param_count;
+
+			if ($nparams  != $inputArrayCount)
+			{
+
+				$this->outp_throw(
+					"Input array has " . $inputArrayCount .
+					" params, does not match query: '" . htmlspecialchars($sql) . "'",
+					'Execute'
+				);
+				return false;
+			}
+
+			foreach ($bulkBindArray as $inputarr)
+			{
+				/*
+				* Must pass references into call_user_func_array
+				*/
+				$paramsByReference = array();
+				foreach($inputarr as $key => $value) {
+					/** @noinspection PhpArrayAccessCanBeReplacedWithForeachValueInspection */
+					$paramsByReference[$key] = &$inputarr[$key];
+				}
+
+				/*
+				* Bind the params
+				*/
+				call_user_func_array(array($stmt, 'bind_param'), $paramsByReference);
+
+				/*
+				* Execute
+				*/
+
+				$ret = mysqli_stmt_execute($stmt);
+
+				// Store error code and message
+				$this->_errorCode = $stmt->errno;
+				$this->_errorMsg = $stmt->error;
+
+				/*
+				* Did we throw an error?
+				*/
+				if ($ret == false)
+					return false;
+			}
+
+			// Tells affected_rows to be compliant
+			$this->isSelectStatement = $stmt->affected_rows == -1;
+			if (!$this->isSelectStatement) {
+				$this->statementAffectedRows = $stmt->affected_rows;
+				return true;
+			}
+
+			// Turn the statement into a result set and return it
+			return $stmt->get_result();
 		}
 		else
 		{
@@ -1129,6 +1345,10 @@ class ADODB_mysqli extends ADOConnection {
 			*/
 			$this->usePreparedStatement   = false;
 			$this->useLastInsertStatement = false;
+
+			// Reset error code and message
+			$this->_errorCode = 0;
+			$this->_errorMsg = '';
 		}
 
 		/*
@@ -1144,7 +1364,7 @@ class ADODB_mysqli extends ADOConnection {
 			$rs = mysqli_multi_query($this->_connectionID, $sql.';');
 			if ($rs) {
 				$rs = ($ADODB_COUNTRECS) ? @mysqli_store_result( $this->_connectionID ) : @mysqli_use_result( $this->_connectionID );
-				return $rs ? $rs : true; // mysqli_more_results( $this->_connectionID )
+				return $rs ?: true; // mysqli_more_results( $this->_connectionID )
 			}
 		} else {
 			$rs = mysqli_query($this->_connectionID, $sql, $ADODB_COUNTRECS ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT);
@@ -1170,10 +1390,13 @@ class ADODB_mysqli extends ADOConnection {
 	 */
 	function ErrorMsg()
 	{
-		if (empty($this->_connectionID))
-			$this->_errorMsg = @mysqli_connect_error();
-		else
-			$this->_errorMsg = @mysqli_error($this->_connectionID);
+		if (!$this->_errorMsg) {
+			if (empty($this->_connectionID)) {
+				$this->_errorMsg = mysqli_connect_error();
+			} else {
+				$this->_errorMsg = $this->_connectionID->error ?? $this->_connectionID->connect_error;
+			}
+		}
 		return $this->_errorMsg;
 	}
 
@@ -1184,10 +1407,14 @@ class ADODB_mysqli extends ADOConnection {
 	 */
 	function ErrorNo()
 	{
-		if (empty($this->_connectionID))
-			return @mysqli_connect_errno();
-		else
-			return @mysqli_errno($this->_connectionID);
+		if (!$this->_errorCode) {
+			if (empty($this->_connectionID)) {
+				$this->_errorCode = mysqli_connect_errno();
+			} else {
+				$this->_errorCode = $this->_connectionID->errno ?? $this->_connectionID->connect_errno;
+			}
+		}
+		return $this->_errorCode;
 	}
 
 	/**
@@ -1237,6 +1464,9 @@ class ADODB_mysqli extends ADOConnection {
 		return $this->charSet ?: false;
 	}
 
+	/**
+	 * @deprecated 5.21.0 Use {@see setConnectionParameter()} instead
+	 */
 	function setCharSet($charset)
 	{
 		if (!$this->_connectionID || !method_exists($this->_connectionID,'set_charset')) {
@@ -1262,6 +1492,12 @@ class ADORecordSet_mysqli extends ADORecordSet{
 	var $databaseType = "mysqli";
 	var $canSeek = true;
 
+	/** @var ADODB_mysqli The parent connection */
+	var $connection = false;
+
+	/** @var mysqli_result result link identifier */
+	var $_queryID;
+
 	function __construct($queryID, $mode = false)
 	{
 		if ($mode === false) {
@@ -1368,8 +1604,7 @@ class ADORecordSet_mysqli extends ADORecordSet{
 		if ($this->fetchMode == MYSQLI_ASSOC && $upper == ADODB_ASSOC_CASE_LOWER) {
 			return $this->fields;
 		}
-		$row = ADORecordSet::getRowAssoc($upper);
-		return $row;
+		return ADORecordSet::getRowAssoc($upper);
 	}
 
 	/**
@@ -1524,6 +1759,7 @@ class ADORecordSet_mysqli extends ADORecordSet{
 12 = MYSQLI_TYPE_DATETIME
 13 = MYSQLI_TYPE_YEAR
 14 = MYSQLI_TYPE_NEWDATE
+245 = MYSQLI_TYPE_JSON
 247 = MYSQLI_TYPE_ENUM
 248 = MYSQLI_TYPE_SET
 249 = MYSQLI_TYPE_TINY_BLOB
@@ -1544,7 +1780,7 @@ class ADORecordSet_mysqli extends ADORecordSet{
 	 *
 	 * @return string The MetaType
 	 */
-	function MetaType($t, $len = -1, $fieldobj = false)
+	function metaType($t, $len = -1, $fieldobj = false)
 	{
 		if (is_object($t)) {
 			$fieldobj = $t;
@@ -1552,8 +1788,16 @@ class ADORecordSet_mysqli extends ADORecordSet{
 			$len = $fieldobj->max_length;
 		}
 
+		$t = strtoupper($t);
+		/*
+		* Add support for custom actual types. We do this
+		* first, that allows us to override existing types
+		*/
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
 		$len = -1; // mysql max_length is not accurate
-		switch (strtoupper($t)) {
+		switch ($t) {
 			case 'STRING':
 			case 'CHAR':
 			case 'VARCHAR':
@@ -1631,6 +1875,8 @@ class ADORecordSet_mysqli extends ADORecordSet{
 			case 'DEC':
 			case 'FIXED':
 			default:
+
+
 				//if (!is_numeric($t)) echo "<p>--- Error in type matching $t -----</p>";
 				return 'N';
 		}
@@ -1661,8 +1907,14 @@ class ADORecordSet_array_mysqli extends ADORecordSet_array
 			$len = $fieldobj->max_length;
 		}
 
+		$t = strtoupper($t);
+
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
 		$len = -1; // mysql max_length is not accurate
-		switch (strtoupper($t)) {
+
+		switch ($t) {
 			case 'STRING':
 			case 'CHAR':
 			case 'VARCHAR':
diff --git a/drivers/adodb-mysqlpo.inc.php b/drivers/adodb-mysqlpo.inc.php
deleted file mode 100644
index 8aa7779..0000000
--- a/drivers/adodb-mysqlpo.inc.php
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-/**
- * Portable MySQL driver
- *
- * @deprecated
- *
- * Extends the deprecated mysql driver, and was originally designed to be a
- * portable driver in the same manner as oci8po and mssqlpo. Its functionality
- * is exactly duplicated in the mysqlt driver, which is itself deprecated.
- * This driver will be removed in ADOdb version 6.0.0.
- *
- * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
- *
- * @package ADOdb
- * @link https://adodb.org Project's web site and documentation
- * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
- *
- * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
- * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
- * any later version. This means you can use it in proprietary products.
- * See the LICENSE.md file distributed with this source code for details.
- * @license BSD-3-Clause
- * @license LGPL-2.1-or-later
- *
- * @copyright 2000-2013 John Lim
- * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
- */
-
-// security - hide paths
-if (!defined('ADODB_DIR')) die();
-
-include_once(ADODB_DIR."/drivers/adodb-mysql.inc.php");
-
-
-class ADODB_mysqlt extends ADODB_mysql {
-	var $databaseType = 'mysqlt';
-	var $ansiOuter = true; // for Version 3.23.17 or later
-	var $hasTransactions = true;
-	var $autoRollback = true; // apparently mysql does not autorollback properly
-
-	function BeginTrans()
-	{
-		if ($this->transOff) return true;
-		$this->transCnt += 1;
-		$this->Execute('SET AUTOCOMMIT=0');
-		$this->Execute('BEGIN');
-		return true;
-	}
-
-	function CommitTrans($ok=true)
-	{
-		if ($this->transOff) return true;
-		if (!$ok) return $this->RollbackTrans();
-
-		if ($this->transCnt) $this->transCnt -= 1;
-		$this->Execute('COMMIT');
-		$this->Execute('SET AUTOCOMMIT=1');
-		return true;
-	}
-
-	function RollbackTrans()
-	{
-		if ($this->transOff) return true;
-		if ($this->transCnt) $this->transCnt -= 1;
-		$this->Execute('ROLLBACK');
-		$this->Execute('SET AUTOCOMMIT=1');
-		return true;
-	}
-
-	function RowLock($tables,$where='',$col='1 as adodbignore')
-	{
-		if ($this->transCnt==0) $this->BeginTrans();
-		if ($where) $where = ' where '.$where;
-		$rs = $this->Execute("select $col from $tables $where for update");
-		return !empty($rs);
-	}
-
-}
-
-class ADORecordSet_mysqlt extends ADORecordSet_mysql{
-	var $databaseType = "mysqlt";
-
-	function __construct($queryID,$mode=false)
-	{
-		if ($mode === false) {
-			global $ADODB_FETCH_MODE;
-			$mode = $ADODB_FETCH_MODE;
-		}
-
-		switch ($mode)
-		{
-		case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break;
-		case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break;
-
-		case ADODB_FETCH_DEFAULT:
-		case ADODB_FETCH_BOTH:
-		default: $this->fetchMode = MYSQL_BOTH; break;
-		}
-
-		$this->adodbFetchMode = $mode;
-		parent::__construct($queryID);
-	}
-
-	function MoveNext()
-	{
-		if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) {
-			$this->_currentRow += 1;
-			return true;
-		}
-		if (!$this->EOF) {
-			$this->_currentRow += 1;
-			$this->EOF = true;
-		}
-		return false;
-	}
-}
-
-class ADORecordSet_ext_mysqlt extends ADORecordSet_mysqlt {
-
-	function MoveNext()
-	{
-		return adodb_movenext($this);
-	}
-}
diff --git a/drivers/adodb-mysqlt.inc.php b/drivers/adodb-mysqlt.inc.php
deleted file mode 100644
index cccd93f..0000000
--- a/drivers/adodb-mysqlt.inc.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-/**
- * MySQL driver in transactional mode
- *
- * @deprecated
- *
- * This driver only supports the original MySQL driver in transactional mode. It
- * is deprecated in PHP version 5.5 and removed in PHP version 7. It is deprecated
- * as of ADOdb version 5.20.0. Use the mysqli driver instead, which supports both
- * transactional and non-transactional updates
- *
- * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
- *
- * @package ADOdb
- * @link https://adodb.org Project's web site and documentation
- * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
- *
- * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
- * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
- * any later version. This means you can use it in proprietary products.
- * See the LICENSE.md file distributed with this source code for details.
- * @license BSD-3-Clause
- * @license LGPL-2.1-or-later
- *
- * @copyright 2000-2013 John Lim
- * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
- */
-
-// security - hide paths
-if (!defined('ADODB_DIR')) die();
-
-include_once(ADODB_DIR."/drivers/adodb-mysql.inc.php");
-
-
-class ADODB_mysqlt extends ADODB_mysql {
-	var $databaseType = 'mysqlt';
-	var $ansiOuter = true; // for Version 3.23.17 or later
-	var $hasTransactions = true;
-	var $autoRollback = true; // apparently mysql does not autorollback properly
-
-	/* set transaction mode
-
-	SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL
-{ READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
-
-	*/
-	function SetTransactionMode( $transaction_mode )
-	{
-		$this->_transmode  = $transaction_mode;
-		if (empty($transaction_mode)) {
-			$this->Execute('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ');
-			return;
-		}
-		if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
-		$this->Execute("SET SESSION TRANSACTION ".$transaction_mode);
-	}
-
-	function BeginTrans()
-	{
-		if ($this->transOff) return true;
-		$this->transCnt += 1;
-		$this->Execute('SET AUTOCOMMIT=0');
-		$this->Execute('BEGIN');
-		return true;
-	}
-
-	function CommitTrans($ok=true)
-	{
-		if ($this->transOff) return true;
-		if (!$ok) return $this->RollbackTrans();
-
-		if ($this->transCnt) $this->transCnt -= 1;
-		$ok = $this->Execute('COMMIT');
-		$this->Execute('SET AUTOCOMMIT=1');
-		return $ok ? true : false;
-	}
-
-	function RollbackTrans()
-	{
-		if ($this->transOff) return true;
-		if ($this->transCnt) $this->transCnt -= 1;
-		$ok = $this->Execute('ROLLBACK');
-		$this->Execute('SET AUTOCOMMIT=1');
-		return $ok ? true : false;
-	}
-
-	function RowLock($tables,$where='',$col='1 as adodbignore')
-	{
-		if ($this->transCnt==0) $this->BeginTrans();
-		if ($where) $where = ' where '.$where;
-		$rs = $this->Execute("select $col from $tables $where for update");
-		return !empty($rs);
-	}
-
-}
-
-class ADORecordSet_mysqlt extends ADORecordSet_mysql{
-	var $databaseType = "mysqlt";
-
-	function __construct($queryID,$mode=false)
-	{
-		if ($mode === false) {
-			global $ADODB_FETCH_MODE;
-			$mode = $ADODB_FETCH_MODE;
-		}
-
-		switch ($mode)
-		{
-		case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break;
-		case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break;
-
-		case ADODB_FETCH_DEFAULT:
-		case ADODB_FETCH_BOTH:
-		default: $this->fetchMode = MYSQL_BOTH; break;
-		}
-
-		$this->adodbFetchMode = $mode;
-		parent::__construct($queryID);
-	}
-
-	function MoveNext()
-	{
-		if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) {
-			$this->_currentRow += 1;
-			return true;
-		}
-		if (!$this->EOF) {
-			$this->_currentRow += 1;
-			$this->EOF = true;
-		}
-		return false;
-	}
-}
-
-class ADORecordSet_ext_mysqlt extends ADORecordSet_mysqlt {
-
-	function MoveNext()
-	{
-		return adodb_movenext($this);
-	}
-}
diff --git a/drivers/adodb-oci8.inc.php b/drivers/adodb-oci8.inc.php
index e541b6b..f361d68 100644
--- a/drivers/adodb-oci8.inc.php
+++ b/drivers/adodb-oci8.inc.php
@@ -321,10 +321,6 @@ END;
 
 	protected function _insertID($table = '', $column = '')
 	{
-
-		if (!$this->seqField)
-			return false;
-
 		if ($this->schema)
 		{
 			$t = strpos($table,'.');
@@ -791,6 +787,11 @@ END;
 					$nrows += $offset;
 				}
 				$sql = "select * from (".$sql.") where rownum <= :adodb_offset";
+
+				// If non-bound statement, $inputarr is false
+				if (!$inputarr) {
+					$inputarr = array();
+				}
 				$inputarr['adodb_offset'] = $nrows;
 				$nrows = -1;
 			}
@@ -970,13 +971,6 @@ END;
 		return $rez;
 	}
 
-	/**
-	 * Execute SQL
-	 *
-	 * @param sql		SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text)
-	 * @param [inputarr]	holds the input data to bind to. Null elements will be set to null.
-	 * @return 		RecordSet or false
-	 */
 	function Execute($sql,$inputarr=false)
 	{
 		if ($this->fnExecute) {
@@ -1094,6 +1088,22 @@ END;
 		return array($sql,$stmt,0,$BINDNUM);
 	}
 
+	function releaseStatement(&$stmt)
+	{
+		if (is_array($stmt)
+			&& isset($stmt[1])
+			&& is_resource($stmt[1])
+			&& oci_free_statement($stmt[1])
+		) {
+			// Clearing the resource to avoid it being of type Unknown
+			$stmt[1] = null;
+			return true;
+		}
+
+		// Not a valid prepared statement
+		return false;
+	}
+
 	/*
 		Call an oracle stored procedure and returns a cursor variable as a recordset.
 		Concept by Robert Tuttle robert@ud.com
@@ -1127,10 +1137,11 @@ END;
 		} else
 			$hasref = false;
 
+		/** @var ADORecordset_oci8 $rs */
 		$rs = $this->Execute($stmt);
 		if ($rs) {
 			if ($rs->databaseType == 'array') {
-				oci_free_cursor($stmt[4]);
+				oci_free_statement($stmt[4]);
 			}
 			elseif ($hasref) {
 				$rs->_refcursor = $stmt[4];
@@ -1241,12 +1252,12 @@ END;
 	 *    $db->Parameter($stmt,$group,'group');
 	 *    $db->Execute($stmt);
 	 *
-	 * @param $stmt Statement returned by Prepare() or PrepareSP().
+	 * @param $stmt Statement returned by {@see Prepare()} or {@see PrepareSP()}.
 	 * @param $var PHP variable to bind to
 	 * @param $name Name of stored procedure variable name to bind to.
-	 * @param [$isOutput] Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
-	 * @param [$maxLen] Holds an maximum length of the variable.
-	 * @param [$type] The data type of $var. Legal values depend on driver.
+	 * @param bool $isOutput Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
+	 * @param int $maxLen Holds an maximum length of the variable.
+	 * @param mixed $type The data type of $var. Legal values depend on driver.
 	 *
 	 * @link http://php.net/oci_bind_by_name
 	*/
@@ -1261,7 +1272,8 @@ END;
 	}
 
 	/**
-	 * returns query ID if successful, otherwise false
+	 * Execute a query.
+	 *
 	 * this version supports:
 	 *
 	 * 1. $db->execute('select * from table');
@@ -1274,6 +1286,11 @@ END;
 	 * 4. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)');
 	 *    $db->bind($stmt,1); $db->bind($stmt,2); $db->bind($stmt,3);
 	 *    $db->execute($stmt);
+	 *
+	 * @param string|array $sql        Query to execute.
+	 * @param array        $inputarr   An optional array of parameters.
+	 *
+	 * @return mixed|bool Query identifier or true if execution successful, false if failed.
 	 */
 	function _query($sql,$inputarr=false)
 	{
@@ -1484,16 +1501,17 @@ SELECT /*+ RULE */ distinct b.column_name
 	}
 
 	/**
-	 * returns assoc array where keys are tables, and values are foreign keys
+	 * Returns a list of Foreign Keys associated with a specific table.
 	 *
-	 * @param	str		$table
-	 * @param	str		$owner	[optional][default=NULL]
-	 * @param	bool	$upper	[optional][discarded]
-	 * @return	mixed[]			Array of foreign key information
+	 * @param string $table
+	 * @param string $owner
+	 * @param bool   $upper       discarded
+	 * @param bool   $associative discarded
 	 *
-	 * @link http://gis.mit.edu/classes/11.521/sqlnotes/referential_integrity.html
+	 * @return string[]|false An array where keys are tables, and values are foreign keys;
+	 *                        false if no foreign keys could be found.
 	 */
-	function MetaForeignKeys($table, $owner=false, $upper=false)
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 		global $ADODB_FETCH_MODE;
 
@@ -1579,6 +1597,9 @@ class ADORecordset_oci8 extends ADORecordSet {
 	var $bind=false;
 	var $_fieldobjs;
 
+	/** @var resource Cursor reference */
+	var $_refcursor;
+
 	function __construct($queryID,$mode=false)
 	{
 		if ($mode === false) {
@@ -1810,7 +1831,12 @@ class ADORecordset_oci8 extends ADORecordSet {
 			$len = $fieldobj->max_length;
 		}
 
-		switch (strtoupper($t)) {
+		$t = strtoupper($t);
+
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
+		switch ($t) {
 		case 'VARCHAR':
 		case 'VARCHAR2':
 		case 'CHAR':
diff --git a/drivers/adodb-oci8po.inc.php b/drivers/adodb-oci8po.inc.php
index 50630ca..71c203b 100644
--- a/drivers/adodb-oci8po.inc.php
+++ b/drivers/adodb-oci8po.inc.php
@@ -70,7 +70,16 @@ class ADODB_oci8po extends ADODB_oci8 {
 		return ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
 	}
 
-	// emulate handling of parameters ? ?, replacing with :bind0 :bind1
+	/**
+	 * Execute a query.
+	 *
+	 * Emulate handling of parameters ? ?, replacing with :bind0 :bind1
+	 *
+	 * @param string|array $sql        Query to execute.
+	 * @param array        $inputarr   An optional array of parameters.
+	 *
+	 * @return mixed|bool Query identifier or true if execution successful, false if failed.
+	 */
 	function _query($sql,$inputarr=false)
 	{
 		if (is_array($inputarr)) {
@@ -85,21 +94,21 @@ class ADODB_oci8po extends ADODB_oci8 {
 		}
 		return ADODB_oci8::_query($sql,$inputarr);
 	}
-	
+
 	/**
 	* Replaces compatibility bind markers with oracle ones and returns a
 	* valid sql statement
 	*
 	* This replaces a regexp based section of code that has been subject
 	* to numerous tweaks, as more extreme test cases have appeared. This
-	* is now done this like this to help maintainability and avoid the 
+	* is now done this like this to help maintainability and avoid the
 	* need to rely on regexp experienced maintainers
 	*
 	* @param	string		$sql		The sql statement
 	* @param	string[]	$inputarr	The bind array
 	*
 	* @return	string	The modified statement
-	*/	
+	*/
 	private function extractBinds($sql,$inputarr)
 	{
 		$inString  = false;
@@ -107,14 +116,13 @@ class ADODB_oci8po extends ADODB_oci8 {
 		$sqlLength = strlen($sql) - 1;
 		$newSql    = '';
 		$bindCount = 0;
-		
+
 		/*
 		* inputarr is the passed in bind list, which is associative, but
 		* we only want the keys here
 		*/
 		$inputKeys = array_keys($inputarr);
-		
-		
+
 		for ($i=0;$i<=$sqlLength;$i++)
 		{
 			/*
@@ -137,7 +145,7 @@ class ADODB_oci8po extends ADODB_oci8 {
 				* We found the end of the string
 				*/
 				$inString = false;
-			
+
 			if ($escaped == 2)
 				$escaped = 0;
 
@@ -151,7 +159,7 @@ class ADODB_oci8po extends ADODB_oci8 {
 				* Add the current character the pile
 				*/
 				$newSql .= $c;
-			
+
 			if ($escaped == 1)
 				/*
 				* We have just found an escape character, make sure we ignore the
@@ -159,9 +167,9 @@ class ADODB_oci8po extends ADODB_oci8 {
 				*/
 				$escaped = 2;
 		}
-		
+
 		return $newSql;
-			
+
 	}
 }
 
@@ -192,14 +200,14 @@ class ADORecordset_oci8po extends ADORecordset_oci8 {
 	{
 		$fld = new ADOFieldObject;
 		$fieldOffset += 1;
-		$fld->name = OCIcolumnname($this->_queryID, $fieldOffset);
+		$fld->name = oci_field_name($this->_queryID, $fieldOffset);
 		if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) {
 			$fld->name = strtolower($fld->name);
 		}
-		$fld->type = OCIcolumntype($this->_queryID, $fieldOffset);
-		$fld->max_length = OCIcolumnsize($this->_queryID, $fieldOffset);
+		$fld->type = oci_field_type($this->_queryID, $fieldOffset);
+		$fld->max_length = oci_field_size($this->_queryID, $fieldOffset);
 		if ($fld->type == 'NUMBER') {
-			$sc = OCIColumnScale($this->_queryID, $fieldOffset);
+			$sc = oci_field_scale($this->_queryID, $fieldOffset);
 			if ($sc == 0) {
 				$fld->type = 'INT';
 			}
@@ -231,7 +239,6 @@ class ADORecordset_oci8po extends ADORecordset_oci8 {
 		return false;
 	}
 
-	/* Optimize SelectLimit() by using OCIFetch() instead of OCIFetchInto() */
 	function GetArrayLimit($nrows,$offset=-1)
 	{
 		if ($offset <= 0) {
@@ -239,7 +246,7 @@ class ADORecordset_oci8po extends ADORecordset_oci8 {
 			return $arr;
 		}
 		for ($i=1; $i < $offset; $i++)
-			if (!@OCIFetch($this->_queryID)) {
+			if (!@oci_fetch($this->_queryID)) {
 				$arr = array();
 				return $arr;
 			}
diff --git a/drivers/adodb-odbc.inc.php b/drivers/adodb-odbc.inc.php
index a9705e2..56db8b0 100644
--- a/drivers/adodb-odbc.inc.php
+++ b/drivers/adodb-odbc.inc.php
@@ -26,7 +26,7 @@ if (!defined('ADODB_DIR')) die();
 
 /*
  * These constants are used to set define MetaColumns() method's behavior.
- * - METACOLUMNS_RETURNS_ACTUAL makes the driver return the actual type, 
+ * - METACOLUMNS_RETURNS_ACTUAL makes the driver return the actual type,
  *   like all other drivers do (default)
  * - METACOLUMNS_RETURNS_META is provided for legacy compatibility (makes
  *   driver behave as it did prior to v5.21)
@@ -35,7 +35,7 @@ if (!defined('ADODB_DIR')) die();
  */
 DEFINE('METACOLUMNS_RETURNS_ACTUAL', 0);
 DEFINE('METACOLUMNS_RETURNS_META', 1);
-	
+
 /*--------------------------------------------------------------------------------------
 --------------------------------------------------------------------------------------*/
 
@@ -57,7 +57,7 @@ class ADODB_odbc extends ADOConnection {
 	var $_autocommit = true;
 	var $_lastAffectedRows = 0;
 	var $uCaseTables = true; // for meta* functions, uppercase table names
-	
+
 	/*
 	 * Tells the metaColumns feature whether to return actual or meta type
 	 */
@@ -81,7 +81,9 @@ class ADODB_odbc extends ADOConnection {
 		if ($this->curmode === false) $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword);
 		else $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword,$this->curmode);
 		$this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
-		if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+		if ($this->connectStmt) {
+			$this->Execute($this->connectStmt);
+		}
 
 		return $this->_connectionID != false;
 	}
@@ -102,7 +104,9 @@ class ADODB_odbc extends ADOConnection {
 
 		$this->_errorMsg = $this->getChangedErrorMsg($last_php_error);
 		if ($this->_connectionID && $this->autoRollback) @odbc_rollback($this->_connectionID);
-		if (isset($this->connectStmt)) $this->Execute($this->connectStmt);
+		if ($this->connectStmt) {
+			$this->Execute($this->connectStmt);
+		}
 
 		return $this->_connectionID != false;
 	}
@@ -469,7 +473,7 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/od
 				$fld = new ADOFieldObject();
 				$fld->name = $rs->fields[3];
 				if ($this->metaColumnsReturnType == METACOLUMNS_RETURNS_META)
-					/* 
+					/*
 				    * This is the broken, original value
 					*/
 					$fld->type = $this->ODBCTypes($rs->fields[4]);
@@ -514,7 +518,6 @@ See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/od
 		return array($sql,$stmt,false);
 	}
 
-	/* returns queryID or false */
 	function _query($sql,$inputarr=false)
 	{
 		$last_php_error = $this->resetLastError();
diff --git a/drivers/adodb-odbc_mssql.inc.php b/drivers/adodb-odbc_mssql.inc.php
index 37185aa..1f8b98d 100644
--- a/drivers/adodb-odbc_mssql.inc.php
+++ b/drivers/adodb-odbc_mssql.inc.php
@@ -49,8 +49,12 @@ class  ADODB_odbc_mssql extends ADODB_odbc {
 	var $ansiOuter = true; // for mssql7 or later
 	var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000
 	var $hasInsertID = true;
-	var $connectStmt = 'SET CONCAT_NULL_YIELDS_NULL OFF'; # When SET CONCAT_NULL_YIELDS_NULL is ON,
-														  # concatenating a null value with a string yields a NULL result
+	/**
+	 * When SET CONCAT_NULL_YIELDS_NULL is ON, concatenating a null value with
+	 * a string yields a NULL result.
+	 * @var string SQL statement executed after successful connection.
+	 */
+	public $connectStmt = 'SET CONCAT_NULL_YIELDS_NULL OFF'; #
 
 	// crashes php...
 	function ServerInfo()
@@ -81,8 +85,7 @@ class  ADODB_odbc_mssql extends ADODB_odbc {
 			return $this->GetOne($this->identitySQL);
 	}
 
-
-	function MetaForeignKeys($table, $owner=false, $upper=false)
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 	global $ADODB_FETCH_MODE;
 
diff --git a/drivers/adodb-odbc_mssql2012.inc.php b/drivers/adodb-odbc_mssql2012.inc.php
index 79fa325..86f7f8c 100644
--- a/drivers/adodb-odbc_mssql2012.inc.php
+++ b/drivers/adodb-odbc_mssql2012.inc.php
@@ -26,10 +26,11 @@ include_once(ADODB_DIR."/drivers/adodb-odbc_mssql.inc.php");
 
 class  ADODB_odbc_mssql2012 extends ADODB_odbc_mssql
 {
-	/*
-	* Makes behavior similar to prior versions of SQL Server
-	*/
-	var $connectStmt = 'SET CONCAT_NULL_YIELDS_NULL ON';
+	/**
+	 * Makes behavior similar to prior versions of SQL Server.
+	 * @var string SQL statement executed after successful connection.
+	 */
+	public $connectStmt = 'SET CONCAT_NULL_YIELDS_NULL ON';
 }
 
 class  ADORecordSet_odbc_mssql2012 extends ADORecordSet_odbc_mssql
diff --git a/drivers/adodb-odbtp.inc.php b/drivers/adodb-odbtp.inc.php
index bd167a1..e3f8148 100644
--- a/drivers/adodb-odbtp.inc.php
+++ b/drivers/adodb-odbtp.inc.php
@@ -46,6 +46,15 @@ class ADODB_odbtp extends ADOConnection{
 	var $_canPrepareSP = false;
 	var $_dontPoolDBC = true;
 
+	/** @var string DBMS name. */
+	var $odbc_name;
+
+	/** @var bool */
+	var $_canSelectDb = false;
+
+	/** @var mixed */
+	var $_lastAffectedRows;
+
 	function ServerInfo()
 	{
 		return array('description' => @odbtp_get_attr( ODB_ATTR_DBMSNAME, $this->_connectionID),
@@ -304,7 +313,6 @@ class ADODB_odbtp extends ADOConnection{
 			return false;
 		}
 		$this->database = $dbName;
-		$this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
 		return true;
 	}
 
@@ -393,7 +401,7 @@ class ADODB_odbtp extends ADOConnection{
 		return $arr2;
 	}
 
-	function MetaForeignKeys($table, $owner='', $upper=false)
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 	global $ADODB_FETCH_MODE;
 
diff --git a/drivers/adodb-oracle.inc.php b/drivers/adodb-oracle.inc.php
index 1a2735b..dc0462f 100644
--- a/drivers/adodb-oracle.inc.php
+++ b/drivers/adodb-oracle.inc.php
@@ -181,7 +181,6 @@ class ADODB_oracle extends ADOConnection {
 		}
 
 
-		// returns query ID if successful, otherwise false
 		function _query($sql,$inputarr=false)
 		{
             // <G. Giunta 2003/03/03/> Reset error messages before executing
diff --git a/drivers/adodb-pdo.inc.php b/drivers/adodb-pdo.inc.php
index cf8e4e2..d3ce12d 100644
--- a/drivers/adodb-pdo.inc.php
+++ b/drivers/adodb-pdo.inc.php
@@ -82,10 +82,17 @@ class ADODB_pdo extends ADOConnection {
 	var $_errormsg = false;
 	var $_errorno = false;
 
-	var $dsnType = '';
-	var $stmt = false;
+	var $_stmt = false;
+
+	/** @var ADODB_pdo_base */
 	var $_driver;
 
+	/** @var PDO */
+	var $_connectionID;
+
+	/** @var PDOStatement */
+	var $_queryID;
+
 	/*
 	* Describe parameters passed directly to the PDO driver
 	*
@@ -231,11 +238,31 @@ class ADODB_pdo extends ADOConnection {
 	function Concat()
 	{
 		$args = func_get_args();
-		if(method_exists($this->_driver, 'Concat')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'Concat')) {
 			return call_user_func_array(array($this->_driver, 'Concat'), $args);
 		}
 
-		return call_user_func_array('parent::Concat', $args);
+		return call_user_func_array(parent::class . '::Concat', $args);
+	}
+
+	/**
+	 * Triggers a driver-specific request for a bind parameter
+	 *
+	 * @param string $name
+	 * @param string $type
+	 *
+	 * @return string
+	 */
+	public function param($name,$type='C') {
+
+		$args = func_get_args();
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'param')) {
+			// Return the driver specific entry, that mimics the native driver
+			return call_user_func_array(array($this->_driver, 'param'), $args);
+		}
+
+		// No driver specific method defined, use mysql format '?'
+		return call_user_func_array(parent::class . '::param', $args);
 	}
 
 	// returns true or false
@@ -294,18 +321,19 @@ class ADODB_pdo extends ADOConnection {
 	}
 
 	/**
-	 * Returns a list of Foreign Keys for a specified table.
+	 * Returns a list of Foreign Keys associated with a specific table.
 	 *
 	 * @param string   $table
-	 * @param bool     $owner      (optional) not used in this driver
+	 * @param string   $owner      (optional) not used in this driver
 	 * @param bool     $upper
 	 * @param bool     $associative
 	 *
-	 * @return string[] where keys are tables, and values are foreign keys
+	 * @return string[]|false An array where keys are tables, and values are foreign keys;
+	 *                        false if no foreign keys could be found.
 	 */
-	public function metaForeignKeys($table, $owner=false, $upper=false,$associative=false) {
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false) {
 		if (method_exists($this->_driver,'metaForeignKeys'))
-			return $this->_driver->metaForeignKeys($table,$owner,$upper,$associative);
+			return $this->_driver->metaForeignKeys($table, $owner, $upper, $associative);
 	}
 
 	/**
@@ -421,14 +449,14 @@ class ADODB_pdo extends ADOConnection {
 	 */
 	function SetAutoCommit($auto_commit)
 	{
-		if(method_exists($this->_driver, 'SetAutoCommit')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'SetAutoCommit')) {
 			$this->_driver->SetAutoCommit($auto_commit);
 		}
 	}
 
 	function SetTransactionMode($transaction_mode)
 	{
-		if(method_exists($this->_driver, 'SetTransactionMode')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'SetTransactionMode')) {
 			return $this->_driver->SetTransactionMode($transaction_mode);
 		}
 
@@ -437,7 +465,7 @@ class ADODB_pdo extends ADOConnection {
 
 	function beginTrans()
 	{
-		if(method_exists($this->_driver, 'beginTrans')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'beginTrans')) {
 			return $this->_driver->beginTrans();
 		}
 
@@ -457,7 +485,7 @@ class ADODB_pdo extends ADOConnection {
 	function commitTrans($ok=true)
 	{
 
-		if(method_exists($this->_driver, 'commitTrans')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'commitTrans')) {
 			return $this->_driver->commitTrans($ok);
 		}
 
@@ -482,7 +510,7 @@ class ADODB_pdo extends ADOConnection {
 
 	function RollbackTrans()
 	{
-		if(method_exists($this->_driver, 'RollbackTrans')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'RollbackTrans')) {
 			return $this->_driver->RollbackTrans();
 		}
 
@@ -524,7 +552,7 @@ class ADODB_pdo extends ADOConnection {
 
 	public function createSequence($seqname='adodbseq',$startID=1)
 	{
-		if(method_exists($this->_driver, 'createSequence')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'createSequence')) {
 			return $this->_driver->createSequence($seqname, $startID);
 		}
 
@@ -533,7 +561,7 @@ class ADODB_pdo extends ADOConnection {
 
 	function DropSequence($seqname='adodbseq')
 	{
-		if(method_exists($this->_driver, 'DropSequence')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'DropSequence')) {
 			return $this->_driver->DropSequence($seqname);
 		}
 
@@ -542,7 +570,7 @@ class ADODB_pdo extends ADOConnection {
 
 	function GenID($seqname='adodbseq',$startID=1)
 	{
-		if(method_exists($this->_driver, 'GenID')) {
+		if($this->_driver instanceof ADODB_pdo && method_exists($this->_driver, 'GenID')) {
 			return $this->_driver->GenID($seqname, $startID);
 		}
 
@@ -550,7 +578,6 @@ class ADODB_pdo extends ADOConnection {
 	}
 
 
-	/* returns queryID or false */
 	function _query($sql,$inputarr=false)
 	{
 		$ok = false;
@@ -565,6 +592,7 @@ class ADODB_pdo extends ADOConnection {
 				$this->_driver->debug = $this->debug;
 			}
 			if ($inputarr) {
+
 				/*
 				* inputarr must be numeric
 				*/
@@ -640,6 +668,9 @@ class ADODB_pdo extends ADOConnection {
 
 }
 
+/**
+ * Base class for Database-specific PDO drivers.
+ */
 class ADODB_pdo_base extends ADODB_pdo {
 
 	var $sysDate = "'?'";
@@ -758,6 +789,9 @@ class ADORecordSet_pdo extends ADORecordSet {
 	var $databaseType = "pdo";
 	var $dataProvider = "pdo";
 
+	/** @var PDOStatement */
+	var $_queryID;
+
 	function __construct($id,$mode=false)
 	{
 		if ($mode === false) {
@@ -898,4 +932,7 @@ class ADORecordSet_pdo extends ADORecordSet {
 
 }
 
-class ADORecordSet_array_pdo extends ADORecordSet_array {}
+class ADORecordSet_array_pdo extends ADORecordSet_array {
+	/** @var PDOStatement */
+	var $_queryID;
+}
diff --git a/drivers/adodb-pdo_firebird.inc.php b/drivers/adodb-pdo_firebird.inc.php
index 0ee5e02..f8847c3 100644
--- a/drivers/adodb-pdo_firebird.inc.php
+++ b/drivers/adodb-pdo_firebird.inc.php
@@ -32,14 +32,6 @@ class ADODB_pdo_firebird extends ADODB_pdo
 
 	var $arrayClass = 'ADORecordSet_array_pdo_firebird';
 
-	function _init($parentDriver)
-	{
-		$this->pdoDriver = $parentDriver;
-		//$parentDriver->_bindInputArray = true;
-		//$parentDriver->hasTransactions = false; // // should be set to false because of PDO SQLite driver not supporting changing autocommit mode
-		//$parentDriver->hasInsertID = true;
-	}
-
 	/**
 	 * Gets the version iformation from the server
 	 *
@@ -243,12 +235,6 @@ class ADODB_pdo_firebird extends ADODB_pdo
 		return $this->Execute("DROP SEQUENCE $seqname");
 	}
 
-
-	public function _affectedrows()
-	{
-		return fbird_affected_rows($this->_transactionID ? $this->_transactionID : $this->_connectionID);
-	}
-
 	public function genId($seqname = 'adodbseq', $startID = 1)
 	{
 		$getnext = ("SELECT Gen_ID($seqname,1) FROM RDB\$DATABASE");
diff --git a/drivers/adodb-pdo_mysql.inc.php b/drivers/adodb-pdo_mysql.inc.php
index c881245..a00c859 100644
--- a/drivers/adodb-pdo_mysql.inc.php
+++ b/drivers/adodb-pdo_mysql.inc.php
@@ -57,7 +57,7 @@ class ADODB_pdo_mysql extends ADODB_pdo {
 		return $date . ' + INTERVAL ' .	$fraction . ' SECOND';
 //		return "from_unixtime(unix_timestamp($date)+$fraction)";
 	}
-	
+
 	/**
 	 * Get a list of indexes on the specified table.
 	 *
@@ -266,7 +266,6 @@ class ADODB_pdo_mysql extends ADODB_pdo {
 	function SelectDB($dbName)
 	{
 		$this->database = $dbName;
-		$this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
 		$try = $this->Execute('use ' . $dbName);
 		return ($try !== false);
 	}
@@ -275,7 +274,7 @@ class ADODB_pdo_mysql extends ADODB_pdo {
 	function SelectLimit($sql, $nrows=-1, $offset=-1, $inputarr=false, $secs=0)
 	{
 		$nrows = (int) $nrows;
-		$offset = (int) $offset;		
+		$offset = (int) $offset;
 		$offsetStr =($offset>=0) ? "$offset," : '';
 		// jason judge, see PHPLens Issue No: 9220
 		if ($nrows < 0) {
diff --git a/drivers/adodb-pdo_oci.inc.php b/drivers/adodb-pdo_oci.inc.php
index 9a05ee6..20b47de 100644
--- a/drivers/adodb-pdo_oci.inc.php
+++ b/drivers/adodb-pdo_oci.inc.php
@@ -75,15 +75,15 @@ class ADODB_pdo_oci extends ADODB_pdo_base {
 		$retarr = array();
 		while (!$rs->EOF) { //print_r($rs->fields);
 			$fld = new ADOFieldObject();
-	   		$fld->name = $rs->fields[0];
-	   		$fld->type = $rs->fields[1];
-	   		$fld->max_length = $rs->fields[2];
+			$fld->name = $rs->fields[0];
+			$fld->type = $rs->fields[1];
+			$fld->max_length = $rs->fields[2];
 			$fld->scale = $rs->fields[3];
 			if ($rs->fields[1] == 'NUMBER' && $rs->fields[3] == 0) {
 				$fld->type ='INT';
-	     		$fld->max_length = $rs->fields[4];
-	    	}
-		   	$fld->not_null = (strncmp($rs->fields[5], 'NOT',3) === 0);
+				$fld->max_length = $rs->fields[4];
+			}
+			$fld->not_null = (strncmp($rs->fields[5], 'NOT',3) === 0);
 			$fld->binary = (strpos($fld->type,'BLOB') !== false);
 			$fld->default_value = $rs->fields[6];
 
@@ -98,12 +98,25 @@ class ADODB_pdo_oci extends ADODB_pdo_base {
 			return $retarr;
 	}
 
-    /**
-     * @param bool $auto_commit
-     * @return void
-     */
-    function SetAutoCommit($auto_commit)
-    {
-        $this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT, $auto_commit);
-    }
+	/**
+	 * @param bool $auto_commit
+	 * @return void
+	 */
+	function SetAutoCommit($auto_commit)
+	{
+		$this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT, $auto_commit);
+	}
+
+	/**
+	 * Returns a driver-specific format for a bind parameter
+	 *
+	 * @param string $name
+	 * @param string $type (ignored in driver)
+	 *
+	 * @return string
+	 */
+	public function param($name,$type='C')
+	{
+		return sprintf(':%s', $name);
+	}
 }
diff --git a/drivers/adodb-pdo_pgsql.inc.php b/drivers/adodb-pdo_pgsql.inc.php
index 0212d4f..6c06ac1 100644
--- a/drivers/adodb-pdo_pgsql.inc.php
+++ b/drivers/adodb-pdo_pgsql.inc.php
@@ -21,11 +21,11 @@
 
 class ADODB_pdo_pgsql extends ADODB_pdo {
 	var $metaDatabasesSQL = "select datname from pg_database where datname not in ('template0','template1') order by 1";
-    var $metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%'
-	and tablename not in ('sql_features', 'sql_implementation_info', 'sql_languages',
-	 'sql_packages', 'sql_sizing', 'sql_sizing_profiles')
-	union
-        select viewname,'V' from pg_views where viewname not like 'pg\_%'";
+	var $metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%'
+		and tablename not in ('sql_features', 'sql_implementation_info', 'sql_languages',
+			'sql_packages', 'sql_sizing', 'sql_sizing_profiles')
+		union
+		select viewname,'V' from pg_views where viewname not like 'pg\_%'";
 	//"select tablename from pg_tables where tablename not like 'pg_%' order by 1";
 	var $isoDates = true; // accepts dates in ISO format
 	var $sysDate = "CURRENT_DATE";
@@ -34,15 +34,15 @@ class ADODB_pdo_pgsql extends ADODB_pdo {
 	var $metaColumnsSQL = "SELECT a.attname,t.typname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,a.attnum
 		FROM pg_class c, pg_attribute a,pg_type t
 		WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) and a.attname not like '....%%'
-AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
+		AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
 
 	// used when schema defined
 	var $metaColumnsSQL1 = "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum
-FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n
-WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s'))
- and c.relnamespace=n.oid and n.nspname='%s'
-	and a.attname not like '....%%' AND a.attnum > 0
-	AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
+		FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n
+		WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s'))
+		and c.relnamespace=n.oid and n.nspname='%s'
+		and a.attname not like '....%%' AND a.attnum > 0
+		AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum";
 
 	// get primary key etc -- from Freek Dijkstra
 	var $metaKeySQL = "SELECT ic.relname AS index_name, a.attname AS column_name,i.indisunique AS unique_key, i.indisprimary AS primary_key
@@ -97,23 +97,27 @@ WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s'))
 	{
 		$info = $this->ServerInfo();
 		if ($info['version'] >= 7.3) {
-	    	$this->metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%'
-			  and schemaname  not in ( 'pg_catalog','information_schema')
-	union
-        select viewname,'V' from pg_views where viewname not like 'pg\_%'  and schemaname  not in ( 'pg_catalog','information_schema') ";
+			$this->metaTablesSQL = "
+select tablename,'T' from pg_tables
+where tablename not like 'pg\_%' and schemaname  not in ( 'pg_catalog','information_schema')
+union
+select viewname,'V' from pg_views
+where viewname not like 'pg\_%'  and schemaname  not in ( 'pg_catalog','information_schema')";
 		}
 		if ($mask) {
 			$save = $this->metaTablesSQL;
 			$mask = $this->qstr(strtolower($mask));
 			if ($info['version']>=7.3)
 				$this->metaTablesSQL = "
-select tablename,'T' from pg_tables where tablename like $mask and schemaname not in ( 'pg_catalog','information_schema')
- union
-select viewname,'V' from pg_views where viewname like $mask and schemaname  not in ( 'pg_catalog','information_schema')  ";
+select tablename,'T' from pg_tables
+where tablename like $mask and schemaname not in ( 'pg_catalog','information_schema')
+union
+select viewname,'V' from pg_views
+where viewname like $mask and schemaname  not in ( 'pg_catalog','information_schema')";
 			else
 				$this->metaTablesSQL = "
 select tablename,'T' from pg_tables where tablename like $mask
- union
+union
 select viewname,'V' from pg_views where viewname like $mask";
 		}
 		$ret = ADOConnection::MetaTables($ttype,$showSchema);
@@ -297,4 +301,23 @@ select viewname,'V' from pg_views where viewname like $mask";
 		if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
 		$this->_connectionID->query("SET TRANSACTION ".$transaction_mode);
 	}
+
+	/**
+	 * Returns a driver-specific format for a bind parameter
+	 *
+	 * Unlike the native driver, we use :name parameters
+	 * instead of offsets
+	 *
+	 * @param string $name
+	 * @param string $type (ignored in driver)
+	 *
+	 * @return string
+	 */
+	public function param($name,$type='C') {
+		if (!$name) {
+			return '';
+		}
+
+		return sprintf(':%s', $name);
+	}
 }
diff --git a/drivers/adodb-pdo_sqlite.inc.php b/drivers/adodb-pdo_sqlite.inc.php
index b62ca35..dab1de9 100644
--- a/drivers/adodb-pdo_sqlite.inc.php
+++ b/drivers/adodb-pdo_sqlite.inc.php
@@ -34,7 +34,7 @@ class ADODB_pdo_sqlite extends ADODB_pdo {
 	var $_genSeq2SQL     = 'INSERT INTO %s VALUES(%s)';
 	var $_dropSeqSQL     = 'DROP TABLE %s';
 	var $concat_operator = '||';
-    var $pdoDriver       = false;
+	var $pdoDriver       = false;
 	var $random='abs(random())';
 
 	function _init($parentDriver)
@@ -156,40 +156,48 @@ class ADODB_pdo_sqlite extends ADODB_pdo {
     // mark newnham
 	function MetaColumns($tab,$normalize=true)
 	{
-	  global $ADODB_FETCH_MODE;
-
-	  $parent = $this->pdoDriver;
-	  $false = false;
-	  $save = $ADODB_FETCH_MODE;
-	  $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
-	  if ($parent->fetchMode !== false) $savem = $parent->SetFetchMode(false);
-	  $rs = $parent->Execute("PRAGMA table_info('$tab')");
-	  if (isset($savem)) $parent->SetFetchMode($savem);
-	  if (!$rs) {
-	    $ADODB_FETCH_MODE = $save;
-	    return $false;
-	  }
-	  $arr = array();
-	  while ($r = $rs->FetchRow()) {
-	    $type = explode('(',$r['type']);
-	    $size = '';
-	    if (sizeof($type)==2)
-	    $size = trim($type[1],')');
-	    $fn = strtoupper($r['name']);
-	    $fld = new ADOFieldObject;
-	    $fld->name = $r['name'];
-	    $fld->type = $type[0];
-	    $fld->max_length = $size;
-	    $fld->not_null = $r['notnull'];
-	    $fld->primary_key = $r['pk'];
-	    $fld->default_value = $r['dflt_value'];
-	    $fld->scale = 0;
-	    if ($save == ADODB_FETCH_NUM) $arr[] = $fld;
-	    else $arr[strtoupper($fld->name)] = $fld;
-	  }
-	  $rs->Close();
-	  $ADODB_FETCH_MODE = $save;
-	  return $arr;
+		global $ADODB_FETCH_MODE;
+
+		$parent = $this->pdoDriver;
+		$false = false;
+		$save = $ADODB_FETCH_MODE;
+		$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+		if ($parent->fetchMode !== false) {
+			$savem = $parent->SetFetchMode(false);
+		}
+		$rs = $parent->Execute("PRAGMA table_info('$tab')");
+		if (isset($savem)) {
+			$parent->SetFetchMode($savem);
+		}
+		if (!$rs) {
+			$ADODB_FETCH_MODE = $save;
+			return $false;
+		}
+		$arr = array();
+		while ($r = $rs->FetchRow()) {
+			$type = explode('(', $r['type']);
+			$size = '';
+			if (sizeof($type) == 2) {
+				$size = trim($type[1], ')');
+			}
+			$fn = strtoupper($r['name']);
+			$fld = new ADOFieldObject;
+			$fld->name = $r['name'];
+			$fld->type = $type[0];
+			$fld->max_length = $size;
+			$fld->not_null = $r['notnull'];
+			$fld->primary_key = $r['pk'];
+			$fld->default_value = $r['dflt_value'];
+			$fld->scale = 0;
+			if ($save == ADODB_FETCH_NUM) {
+				$arr[] = $fld;
+			} else {
+				$arr[strtoupper($fld->name)] = $fld;
+			}
+		}
+		$rs->Close();
+		$ADODB_FETCH_MODE = $save;
+		return $arr;
 	}
 
 	function MetaTables($ttype=false,$showSchema=false,$mask=false)
@@ -208,5 +216,18 @@ class ADODB_pdo_sqlite extends ADODB_pdo {
 			$this->metaTablesSQL = $save;
 		}
 		return $ret;
-   }
+	}
+
+	/**
+	 * Returns a driver-specific format for a bind parameter
+	 *
+	 * @param string $name
+	 * @param string $type (ignored in driver)
+	 *
+	 * @return string
+	 */
+	public function param($name,$type='C')
+	{
+		return sprintf(':%s', $name);
+	}
 }
diff --git a/drivers/adodb-pdo_sqlsrv.inc.php b/drivers/adodb-pdo_sqlsrv.inc.php
index ed73f3a..de5fbba 100644
--- a/drivers/adodb-pdo_sqlsrv.inc.php
+++ b/drivers/adodb-pdo_sqlsrv.inc.php
@@ -36,10 +36,15 @@ class ADODB_pdo_sqlsrv extends ADODB_pdo
 		$parentDriver->fmtDate = "'Y-m-d'";
 	}
 
-	function BeginTrans()
+	function setTransactionMode( $transaction_mode )
 	{
-		$returnval = parent::BeginTrans();
-		return $returnval;
+		$this->_transmode  = $transaction_mode;
+		if (empty($transaction_mode)) {
+			$this->_connectionID->query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+			return;
+		}
+		if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
+		$this->_connectionID->query("SET TRANSACTION ".$transaction_mode);
 	}
 
 	function MetaColumns($table, $normalize = true)
@@ -54,8 +59,7 @@ class ADODB_pdo_sqlsrv extends ADODB_pdo
 
 	function SelectLimit($sql, $nrows = -1, $offset = -1, $inputarr = false, $secs2cache = 0)
 	{
-		$ret = ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
-		return $ret;
+		return ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
 	}
 
 	function ServerInfo()
@@ -167,15 +171,5 @@ class ADORecordSet_array_pdo_sqlsrv extends ADORecordSet_array_pdo
 
 		return $o;
 	}
-	
-	function SetTransactionMode( $transaction_mode )
-	{
-		$this->_transmode  = $transaction_mode;
-		if (empty($transaction_mode)) {
-			$this->_connectionID->query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
-			return;
-		}
-		if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
-		$this->_connectionID->query("SET TRANSACTION ".$transaction_mode);
-	}
+
 }
diff --git a/drivers/adodb-postgres64.inc.php b/drivers/adodb-postgres64.inc.php
index 3f351f9..9bf9e71 100644
--- a/drivers/adodb-postgres64.inc.php
+++ b/drivers/adodb-postgres64.inc.php
@@ -26,6 +26,7 @@ class ADODB_postgres64 extends ADOConnection{
 	var $databaseType = 'postgres64';
 	var $dataProvider = 'postgres';
 	var $hasInsertID = true;
+	/** @var PgSql\Connection|resource|false */
 	var $_resultid = false;
 	var $concat_operator='||';
 	var $metaDatabasesSQL = "select datname from pg_database where datname not in ('template0','template1') order by 1";
@@ -83,6 +84,9 @@ class ADODB_postgres64 extends ADOConnection{
 	/** @var int $_pnum Number of the last assigned query parameter {@see param()} */
 	var $_pnum = 0;
 
+	var $version;
+	var $_nestedSQL = false;
+
 	// The last (fmtTimeStamp is not entirely correct:
 	// PostgreSQL also has support for time zones,
 	// and writes these time in this format: "2001-03-01 18:59:26+02".
@@ -154,7 +158,7 @@ class ADODB_postgres64 extends ADOConnection{
 	 */
 	protected function _insertID($table = '', $column = '')
 	{
-		if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false;
+		if ($this->_resultid === false) return false;
 		$oid = pg_last_oid($this->_resultid);
 		// to really return the id, we need the table and column-name, else we can only return the oid != id
 		return empty($table) || empty($column) ? $oid : $this->GetOne("SELECT $column FROM $table WHERE oid=".(int)$oid);
@@ -162,13 +166,13 @@ class ADODB_postgres64 extends ADOConnection{
 
 	function _affectedrows()
 	{
-		if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false;
+		if ($this->_resultid === false) return false;
 		return pg_affected_rows($this->_resultid);
 	}
 
 
 	/**
-	 * @return true/false
+	 * @return bool
 	 */
 	function BeginTrans()
 	{
@@ -254,7 +258,9 @@ class ADODB_postgres64 extends ADOConnection{
 		if ($this->_connectionID) {
 			return "'" . pg_escape_string($this->_connectionID, $s) . "'";
 		} else {
-			return "'" . pg_escape_string($s) . "'";
+			// Fall back to emulated escaping when there is no database connection.
+			// Avoids errors when using setSessionVariables() in the load balancer.
+			return parent::qStr( $s );
 		}
 	}
 
@@ -378,7 +384,7 @@ class ADODB_postgres64 extends ADOConnection{
 	function BlobDelete( $blob )
 	{
 		pg_query($this->_connectionID, 'begin');
-		$result = @pg_lo_unlink($blob);
+		$result = @pg_lo_unlink($this->_connectionID, $blob);
 		pg_query($this->_connectionID, 'commit');
 		return( $result );
 	}
@@ -655,14 +661,16 @@ class ADODB_postgres64 extends ADOConnection{
 			return $false;
 		}
 
+		// Get column names indexed by attnum so we can lookup the index key
 		$col_names = $this->MetaColumnNames($table,true,true);
-		// 3rd param is use attnum,
-		// see https://sourceforge.net/p/adodb/bugs/45/
 		$indexes = array();
 		while ($row = $rs->FetchRow()) {
 			$columns = array();
 			foreach (explode(' ', $row[2]) as $col) {
-				$columns[] = $col_names[$col];
+				// When index attribute (pg_index.indkey) is an expression, $col == 0
+				// @see https://www.postgresql.org/docs/current/catalog-pg-index.html
+				// so there is no matching column name - set it to null (see #940).
+				$columns[] = $col_names[$col] ?? null;
 			}
 
 			$indexes[$row[0]] = array(
@@ -751,8 +759,7 @@ class ADODB_postgres64 extends ADOConnection{
 		# PHP does not handle 'hex' properly ('x74657374' is returned as 't657374')
 		# https://bugs.php.net/bug.php?id=59831 states this is in fact not a bug,
 		# so we manually set bytea_output
-		if (!empty($this->connection->noBlobs)
-			&& version_compare($info['version'], '9.0', '>=')
+		if (version_compare($info['version'], '9.0', '>=')
 			&& version_compare($info['client'], '9.2', '<')
 		) {
 			$this->Execute('set bytea_output=escape');
@@ -777,7 +784,6 @@ class ADODB_postgres64 extends ADOConnection{
 	}
 
 
-	// returns queryID or false
 	function _query($sql,$inputarr=false)
 	{
 		$this->_pnum = 0;
@@ -808,8 +814,7 @@ class ADODB_postgres64 extends ADOConnection{
 			if ($execp) $exsql = "EXECUTE $plan ($execp)";
 			else $exsql = "EXECUTE $plan";
 
-
-			$rez = @pg_execute($this->_connectionID,$exsql);
+			$rez = @pg_query($this->_connectionID, $exsql);
 			if (!$rez) {
 			# Perhaps plan does not exist? Prepare/compile plan.
 				$params = '';
@@ -833,18 +838,18 @@ class ADODB_postgres64 extends ADOConnection{
 				}
 				$s = "PREPARE $plan ($params) AS ".substr($sql,0,strlen($sql)-2);
 				//adodb_pr($s);
-				$rez = pg_execute($this->_connectionID,$s);
+				$rez = pg_query($this->_connectionID, $s);
 				//echo $this->ErrorMsg();
 			}
 			if ($rez)
-				$rez = pg_execute($this->_connectionID,$exsql);
+				$rez = pg_query($this->_connectionID, $exsql);
 		} else {
 			//adodb_backtrace();
-			$rez = pg_query($this->_connectionID,$sql);
+			$rez = pg_query($this->_connectionID, $sql);
 		}
 		// check if no data returned, then no need to create real recordset
 		if ($rez && pg_num_fields($rez) <= 0) {
-			if (is_resource($this->_resultid) && get_resource_type($this->_resultid) === 'pgsql result') {
+			if ($this->_resultid !== false) {
 				pg_free_result($this->_resultid);
 			}
 			$this->_resultid = $rez;
@@ -1020,8 +1025,20 @@ class ADORecordSet_postgres64 extends ADORecordSet{
 		return pg_unescape_bytea($blob);
 	}
 
-	function _fixblobs()
+	/**
+	 * Fetches and prepares the RecordSet's fields.
+	 *
+	 * Fixes the blobs if there are any.
+	 */
+	protected function _prepFields()
 	{
+		$this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
+
+		// Check prerequisites and bail early if we do not have what we need.
+		if (!isset($this->_blobArr) || $this->fields === false) {
+			return;
+		}
+
 		if ($this->fetchMode == PGSQL_NUM || $this->fetchMode == PGSQL_BOTH) {
 			foreach($this->_blobArr as $k => $v) {
 				$this->fields[$k] = ADORecordSet_postgres64::_decode($this->fields[$k]);
@@ -1040,9 +1057,8 @@ class ADORecordSet_postgres64 extends ADORecordSet{
 		if (!$this->EOF) {
 			$this->_currentRow++;
 			if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) {
-				$this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
-				if (is_array($this->fields) && $this->fields) {
-					if (isset($this->_blobArr)) $this->_fixblobs();
+				$this->_prepfields();
+				if ($this->fields !== false) {
 					return true;
 				}
 			}
@@ -1054,22 +1070,17 @@ class ADORecordSet_postgres64 extends ADORecordSet{
 
 	function _fetch()
 	{
-
-		if ($this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0)
+		if ($this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0) {
 			return false;
+		}
 
-		$this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
-
-		if ($this->fields && isset($this->_blobArr)) $this->_fixblobs();
-
-		return (is_array($this->fields));
+		$this->_prepfields();
+		return $this->fields !== false;
 	}
 
 	function _close()
 	{
-		if (!is_resource($this->_queryID)
-			|| get_resource_type($this->_queryID) != 'pgsql result'
-		) {
+		if ($this->_queryID === false || $this->_queryID == self::DUMMY_QUERY_ID) {
 			return true;
 		}
 		return pg_free_result($this->_queryID);
@@ -1082,7 +1093,13 @@ class ADORecordSet_postgres64 extends ADORecordSet{
 			$t = $fieldobj->type;
 			$len = $fieldobj->max_length;
 		}
-		switch (strtoupper($t)) {
+
+		$t = strtoupper($t);
+
+		if (array_key_exists($t,$this->connection->customActualTypes))
+			return  $this->connection->customActualTypes[$t];
+
+		switch ($t) {
 				case 'MONEY': // stupid, postgres expects money to be a string
 				case 'INTERVAL':
 				case 'CHAR':
@@ -1094,6 +1111,7 @@ class ADORecordSet_postgres64 extends ADORecordSet{
 				case 'CIDR':
 				case 'INET':
 				case 'MACADDR':
+				case 'UUID':
 					if ($len <= $this->blobSize) return 'C';
 
 				case 'TEXT':
@@ -1134,6 +1152,12 @@ class ADORecordSet_postgres64 extends ADORecordSet{
 				case 'SERIAL':
 					return 'R';
 
+				case 'NUMERIC':
+				case 'DECIMAL':
+				case 'FLOAT4':
+				case 'FLOAT8':
+					return 'N';
+
 				default:
 					return ADODB_DEFAULT_METATYPE;
 			}
diff --git a/drivers/adodb-postgres7.inc.php b/drivers/adodb-postgres7.inc.php
index 59174f6..b5e63ac 100644
--- a/drivers/adodb-postgres7.inc.php
+++ b/drivers/adodb-postgres7.inc.php
@@ -154,10 +154,7 @@ class ADODB_postgres7 extends ADODB_postgres64 {
 		}
 	}
 
-	/**
-	 * @returns assoc array where keys are tables, and values are foreign keys
-	 */
-	function MetaForeignKeys($table, $owner=false, $upper=false)
+	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
 	{
 		# Regex isolates the 2 terms between parenthesis using subexpressions
 		$regex = '^.*\((.*)\).*\((.*)\).*$';
@@ -193,50 +190,18 @@ class ADODB_postgres7 extends ADODB_postgres64 {
 
 		$a = array();
 		while (!$rs->EOF) {
+			$lookup_table = $rs->fields('lookup_table');
+			$fields = $rs->fields('dep_field') . '=' . $rs->fields('lookup_field');
 			if ($upper) {
-				$a[strtoupper($rs->Fields('lookup_table'))][] = strtoupper(str_replace('"','',$rs->Fields('dep_field').'='.$rs->Fields('lookup_field')));
-			} else {
-				$a[$rs->Fields('lookup_table')][] = str_replace('"','',$rs->Fields('dep_field').'='.$rs->Fields('lookup_field'));
+				$lookup_table = strtoupper($lookup_table);
+				$fields = strtoupper($fields);
 			}
+			$a[$lookup_table][] = str_replace('"','', $fields);
+
 			$rs->MoveNext();
 		}
 
 		return $a;
-
-	}
-
-	// from  Edward Jaramilla, improved version - works on pg 7.4
-	function _old_MetaForeignKeys($table, $owner=false, $upper=false)
-	{
-		$sql = 'SELECT t.tgargs as args
-		FROM
-		pg_trigger t,pg_class c,pg_proc p
-		WHERE
-		t.tgenabled AND
-		t.tgrelid = c.oid AND
-		t.tgfoid = p.oid AND
-		p.proname = \'RI_FKey_check_ins\' AND
-		c.relname = \''.strtolower($table).'\'
-		ORDER BY
-			t.tgrelid';
-
-		$rs = $this->Execute($sql);
-
-		if (!$rs || $rs->EOF) return false;
-
-		$arr = $rs->GetArray();
-		$a = array();
-		foreach($arr as $v) {
-			$data = explode(chr(0), $v['args']);
-			$size = count($data)-1; //-1 because the last node is empty
-			for($i = 4; $i < $size; $i++) {
-				if ($upper)
-					$a[strtoupper($data[2])][] = strtoupper($data[$i].'='.$data[++$i]);
-				else
-					$a[$data[2]][] = $data[$i].'='.$data[++$i];
-			}
-		}
-		return $a;
 	}
 
 	function _query($sql,$inputarr=false)
@@ -266,7 +231,7 @@ class ADODB_postgres7 extends ADODB_postgres64 {
 		}
 		// check if no data returned, then no need to create real recordset
 		if ($rez && pg_num_fields($rez) <= 0) {
-			if (is_resource($this->_resultid) && get_resource_type($this->_resultid) === 'pgsql result') {
+			if ($this->_resultid !== false) {
 				pg_free_result($this->_resultid);
 			}
 			$this->_resultid = $rez;
@@ -334,10 +299,8 @@ class ADORecordSet_postgres7 extends ADORecordSet_postgres64{
 		if (!$this->EOF) {
 			$this->_currentRow++;
 			if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) {
-				$this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
-
-				if (is_array($this->fields)) {
-					if ($this->fields && isset($this->_blobArr)) $this->_fixblobs();
+				$this->_prepfields();
+				if ($this->fields !== false) {
 					return true;
 				}
 			}
@@ -360,14 +323,13 @@ class ADORecordSet_assoc_postgres7 extends ADORecordSet_postgres64{
 			return false;
 		}
 
-		$this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
-
-		if ($this->fields) {
-			if (isset($this->_blobArr)) $this->_fixblobs();
+		$this->_prepfields();
+		if ($this->fields !== false) {
 			$this->_updatefields();
+			return true;
 		}
 
-		return (is_array($this->fields));
+		return false;
 	}
 
 	function MoveNext()
@@ -375,19 +337,13 @@ class ADORecordSet_assoc_postgres7 extends ADORecordSet_postgres64{
 		if (!$this->EOF) {
 			$this->_currentRow++;
 			if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) {
-				$this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode);
-
-				if (is_array($this->fields)) {
-					if ($this->fields) {
-						if (isset($this->_blobArr)) $this->_fixblobs();
-
-						$this->_updatefields();
-					}
+				$this->_prepfields();
+				if ($this->fields !== false) {
+					$this->_updatefields();
 					return true;
 				}
 			}
 
-
 			$this->fields = false;
 			$this->EOF = true;
 		}
diff --git a/drivers/adodb-postgres8.inc.php b/drivers/adodb-postgres8.inc.php
index 37c2aae..58df47c 100644
--- a/drivers/adodb-postgres8.inc.php
+++ b/drivers/adodb-postgres8.inc.php
@@ -45,11 +45,23 @@ class ADODB_postgres8 extends ADODB_postgres7
 	 * @return int last inserted ID for given table/column, or the most recently
 	 *             returned one if $table or $column are empty
 	 */
-	protected function _insertID($table = '', $column = '')
+	protected function _insertID( $table = '', $column = '' )
 	{
-		return empty($table) || empty($column)
-			? $this->GetOne("SELECT lastval()")
-			: $this->GetOne("SELECT currval(pg_get_serial_sequence('$table', '$column'))");
+		global $ADODB_GETONE_EOF;
+
+		$sql = empty($table) || empty($column)
+			? 'SELECT lastval()'
+			: "SELECT currval(pg_get_serial_sequence('$table', '$column'))";
+
+		// Squelch "ERROR:  lastval is not yet defined in this session" (see #978)
+		$result = @$this->GetOne($sql);
+		if ($result === false || $result == $ADODB_GETONE_EOF) {
+			if ($this->debug) {
+				ADOConnection::outp(__FUNCTION__ . "() failed : " . $this->errorMsg());
+			}
+			return false;
+		}
+		return $result;
 	}
 }
 
diff --git a/drivers/adodb-sqlanywhere.inc.php b/drivers/adodb-sqlanywhere.inc.php
index 88897af..8d909fd 100644
--- a/drivers/adodb-sqlanywhere.inc.php
+++ b/drivers/adodb-sqlanywhere.inc.php
@@ -116,7 +116,7 @@ if (!defined('ADODB_SYBASE_SQLANYWHERE')){
   $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
   $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
  */
-  function UpdateBlob($table,$column,&$val,$where,$blobtype='BLOB')
+  function updateBlob($table, $column, $val, $where, $blobtype = 'BLOB')
   {
    $blobVarName = 'hold_blob';
    $this->create_blobvar($blobVarName);
diff --git a/drivers/adodb-sqlite.inc.php b/drivers/adodb-sqlite.inc.php
index 0711f1d..d41829c 100644
--- a/drivers/adodb-sqlite.inc.php
+++ b/drivers/adodb-sqlite.inc.php
@@ -211,7 +211,6 @@ class ADODB_sqlite extends ADOConnection {
 		return true;
 	}
 
-	// returns query ID if successful, otherwise false
 	function _query($sql,$inputarr=false)
 	{
 		$rez = sqlite_query($sql,$this->_connectionID);
diff --git a/drivers/adodb-sqlite3.inc.php b/drivers/adodb-sqlite3.inc.php
index 548727d..7623a3c 100644
--- a/drivers/adodb-sqlite3.inc.php
+++ b/drivers/adodb-sqlite3.inc.php
@@ -24,6 +24,9 @@
 // security - hide paths
 if (!defined('ADODB_DIR')) die();
 
+/**
+ * Class ADODB_sqlite3
+ */
 class ADODB_sqlite3 extends ADOConnection {
 	var $databaseType = "sqlite3";
 	var $dataProvider = "sqlite";
@@ -34,10 +37,13 @@ class ADODB_sqlite3 extends ADOConnection {
 	var $hasInsertID = true; 		/// supports autoincrement ID?
 	var $hasAffectedRows = true; 	/// supports affected rows for update/delete?
 	var $metaTablesSQL = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
-	var $sysDate = "adodb_date('Y-m-d')";
-	var $sysTimeStamp = "adodb_date('Y-m-d H:i:s')";
+	var $sysDate = "DATE('now','localtime')";
+	var $sysTimeStamp = "DATETIME('now','localtime')";
 	var $fmtTimeStamp = "'Y-m-d H:i:s'";
 
+	/** @var SQLite3 */
+	var $_connectionID;
+
 	function ServerInfo()
 	{
 		$version = SQLite3::version();
@@ -51,7 +57,7 @@ class ADODB_sqlite3 extends ADOConnection {
 		if ($this->transOff) {
 			return true;
 		}
-		$ret = $this->Execute("BEGIN TRANSACTION");
+		$this->Execute("BEGIN TRANSACTION");
 		$this->transCnt += 1;
 		return true;
 	}
@@ -95,6 +101,9 @@ class ADODB_sqlite3 extends ADOConnection {
 
 		$t = strtoupper($t);
 
+		if (array_key_exists($t,$this->customActualTypes))
+			return  $this->customActualTypes[$t];
+
 		/*
 		* We are using the Sqlite affinity method here
 		* @link https://www.sqlite.org/datatype3.html
@@ -197,7 +206,7 @@ class ADODB_sqlite3 extends ADOConnection {
 		return $arr;
 	}
 
-	function metaForeignKeys( $table, $owner = FALSE, $upper = FALSE, $associative = FALSE )
+	public function metaForeignKeys($table, $owner = '', $upper =  false, $associative =  false)
 	{
 	    global $ADODB_FETCH_MODE;
 		if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC
@@ -214,7 +223,7 @@ class ADODB_sqlite3 extends ADOConnection {
 			          )
 				WHERE type != 'meta'
 				  AND sql NOTNULL
-		          AND LOWER(name) ='" . strtolower($table) . "'";
+				  AND LOWER(name) ='" . strtolower($table) . "'";
 
 		$tableSql = $this->getOne($sql);
 
@@ -304,8 +313,7 @@ class ADODB_sqlite3 extends ADOConnection {
 		$this->_connectionID->createFunction('adodb_date2', 'adodb_date2', 2);
 	}
 
-
-	// returns true or false
+	/** @noinspection PhpUnusedParameterInspection */
 	function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
 	{
 		if (empty($argHostname) && $argDatabasename) {
@@ -317,14 +325,12 @@ class ADODB_sqlite3 extends ADOConnection {
 		return true;
 	}
 
-	// returns true or false
 	function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
 	{
 		// There's no permanent connect in SQLite3
 		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
 	}
 
-	// returns query ID if successful, otherwise false
 	function _query($sql,$inputarr=false)
 	{
 		$rez = $this->_connectionID->query($sql);
@@ -394,7 +400,7 @@ class ADODB_sqlite3 extends ADOConnection {
 		return false;
 	}
 
-	function CreateSequence($seqname='adodbseq',$start=1)
+	function createSequence($seqname='adodbseq', $startID=1)
 	{
 		if (empty($this->_genSeqSQL)) {
 			return false;
@@ -403,8 +409,8 @@ class ADODB_sqlite3 extends ADOConnection {
 		if (!$ok) {
 			return false;
 		}
-		$start -= 1;
-		return $this->Execute("insert into $seqname values($start)");
+		$startID -= 1;
+		return $this->Execute("insert into $seqname values($startID)");
 	}
 
 	var $_dropSeqSQL = 'drop table %s';
@@ -559,14 +565,13 @@ class ADODB_sqlite3 extends ADOConnection {
 	 *
 	 * This uses the more efficient strftime native function to process
 	 *
-	 * @param 	str		$fld	The name of the field to process
+	 * @param string $fld	The name of the field to process
 	 *
-	 * @return	str				The SQL Statement
+	 * @return string The SQL Statement
 	 */
 	function month($fld)
 	{
-		$x = "strftime('%m',$fld)";
-		return $x;
+		return "strftime('%m',$fld)";
 	}
 
 	/**
@@ -574,13 +579,12 @@ class ADODB_sqlite3 extends ADOConnection {
 	 *
 	 * This uses the more efficient strftime native function to process
 	 *
-	 * @param 	str		$fld	The name of the field to process
+	 * @param string $fld	The name of the field to process
 	 *
-	 * @return	str				The SQL Statement
+	 * @return string The SQL Statement
 	 */
 	function day($fld) {
-		$x = "strftime('%d',$fld)";
-		return $x;
+		return "strftime('%d',$fld)";
 	}
 
 	/**
@@ -588,14 +592,116 @@ class ADODB_sqlite3 extends ADOConnection {
 	 *
 	 * This uses the more efficient strftime native function to process
 	 *
-	 * @param 	str		$fld	The name of the field to process
+	 * @param string $fld	The name of the field to process
 	 *
-	 * @return	str				The SQL Statement
+	 * @return string The SQL Statement
 	 */
 	function year($fld)
 	{
-		$x = "strftime('%Y',$fld)";
-		return $x;
+		return "strftime('%Y',$fld)";
+	}
+
+	/**
+	 * SQLite update for blob
+	 *
+	 * SQLite must be a fully prepared statement (all variables must be bound),
+	 * so $where can either be an array (array params) or a string that we will
+	 * do our best to unpack and turn into a prepared statement.
+	 *
+	 * @param string $table
+	 * @param string $column
+	 * @param string $val      Blob value to set
+	 * @param mixed  $where    An array of parameters (key => value pairs),
+	 *                         or a string (where clause).
+	 * @param string $blobtype ignored
+	 *
+	 * @return bool success
+	 */
+	function updateBlob($table, $column, $val, $where, $blobtype = 'BLOB')
+	{
+		if (is_array($where)) {
+			// We were passed a set of key=>value pairs
+			$params = $where;
+		} else {
+			// Given a where clause string, we have to disassemble the
+			// statements into keys and values
+			$params = array();
+			$temp = preg_split('/(where|and)/i', $where);
+			$where = array_filter($temp);
+
+			foreach ($where as $wValue) {
+				$wTemp = preg_split('/[= \']+/', $wValue);
+				$wTemp = array_filter($wTemp);
+				$wTemp = array_values($wTemp);
+				$params[$wTemp[0]] = $wTemp[1];
+			}
+		}
+
+		$paramWhere = array();
+		foreach ($params as $bindKey => $bindValue) {
+			$paramWhere[] = $bindKey . '=?';
+		}
+
+		$sql = "UPDATE $table SET $column=? WHERE "
+			. implode(' AND ', $paramWhere);
+
+		// Prepare the statement
+		$stmt = $this->_connectionID->prepare($sql);
+
+		// Set the first bind value equal to value we want to update
+		if (!$stmt->bindValue(1, $val, SQLITE3_BLOB)) {
+			return false;
+		}
+
+		// Build as many keys as available
+		$bindIndex = 2;
+		foreach ($params as $bindValue) {
+			if (is_integer($bindValue) || is_bool($bindValue) || is_float($bindValue)) {
+				$type = SQLITE3_NUM;
+			} elseif (is_object($bindValue)) {
+				// Assume a blob, this should never appear in
+				// the binding for a where statement anyway
+				$type = SQLITE3_BLOB;
+			} else {
+				$type = SQLITE3_TEXT;
+			}
+
+			if (!$stmt->bindValue($bindIndex, $bindValue, $type)) {
+				return false;
+			}
+
+			$bindIndex++;
+		}
+
+		// Now execute the update. NB this is SQLite execute, not ADOdb
+		$ok = $stmt->execute();
+		return is_object($ok);
+	}
+
+	/**
+	 * SQLite update for blob from a file
+	 *
+	 * @param string $table
+	 * @param string $column
+	 * @param string $path      Filename containing blob data
+	 * @param mixed  $where    {@see updateBlob()}
+	 * @param string $blobtype ignored
+	 *
+	 * @return bool success
+	 */
+	function updateBlobFile($table, $column, $path, $where, $blobtype = 'BLOB')
+	{
+		if (!file_exists($path)) {
+			return false;
+		}
+
+		// Read file information
+		$fileContents = file_get_contents($path);
+		if ($fileContents === false)
+			// Distinguish between an empty file and failure
+			return false;
+
+		return $this->updateBlob($table, $column, $fileContents, $where, $blobtype);
 	}
 
 }
@@ -609,9 +715,12 @@ class ADORecordset_sqlite3 extends ADORecordSet {
 	var $databaseType = "sqlite3";
 	var $bind = false;
 
+	/** @var SQLite3Result */
+	var $_queryID;
+
+	/** @noinspection PhpMissingParentConstructorInspection */
 	function __construct($queryID,$mode=false)
 	{
-
 		if ($mode === false) {
 			global $ADODB_FETCH_MODE;
 			$mode = $ADODB_FETCH_MODE;
diff --git a/drivers/adodb-sybase.inc.php b/drivers/adodb-sybase.inc.php
index b8db074..79fb82e 100644
--- a/drivers/adodb-sybase.inc.php
+++ b/drivers/adodb-sybase.inc.php
@@ -95,7 +95,9 @@ class ADODB_sybase extends ADOConnection {
 	// http://www.isug.com/Sybase_FAQ/ASE/section6.1.html#6.1.4
 	function RowLock($tables,$where,$col='top 1 null as ignore')
 	{
-		if (!$this->_hastrans) $this->BeginTrans();
+		if (!$this->hasTransactions) {
+			$this->BeginTrans();
+		}
 		$tables = str_replace(',',' HOLDLOCK,',$tables);
 		return $this->GetOne("select $col from $tables HOLDLOCK where $where");
 
@@ -104,7 +106,6 @@ class ADODB_sybase extends ADOConnection {
 	function SelectDB($dbName)
 	{
 		$this->database = $dbName;
-		$this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
 		if ($this->_connectionID) {
 			return @sybase_select_db($dbName);
 		}
@@ -169,7 +170,6 @@ class ADODB_sybase extends ADOConnection {
 		return true;
 	}
 
-	// returns query ID if successful, otherwise false
 	function _query($sql,$inputarr=false)
 	{
 	global $ADODB_COUNTRECS;
diff --git a/drivers/adodb-text.inc.php b/drivers/adodb-text.inc.php
index 77ad06a..36a1a0d 100644
--- a/drivers/adodb-text.inc.php
+++ b/drivers/adodb-text.inc.php
@@ -129,14 +129,21 @@ class ADODB_text extends ADOConnection {
 		return true;
 	}
 
-
-
-	// returns queryID or false
-	// We presume that the select statement is on the same table (what else?),
-	// with the only difference being the order by.
-	//You can filter by using $eval and each clause is stored in $arr .eg. $arr[1] == 'name'
-	// also supports SELECT [DISTINCT] COL FROM ... -- only 1 col supported
-	function _query($sql,$input_arr,$eval=false)
+	/**
+	 * Execute a query.
+	 *
+	 * We presume that the select statement is on the same table (what else?),
+	 * with the only difference being the order by.
+	 * You can filter by using $eval and each clause is stored in $arr e.g. $arr[1] == 'name'
+	 * also supports SELECT [DISTINCT] COL FROM ... -- only 1 col supported
+	 *
+	 * @param string|array $sql      Query to execute.
+	 * @param array        $inputarr An optional array of parameters.
+	 * @param string       $eval     Optional eval string
+	 *
+	 * @return mixed|bool Query identifier or true if execution successful, false if failed.
+	 */
+	function _query($sql, $inputarr=false, $eval=false)
 	{
 		if ($this->_origarray === false) return false;
 
diff --git a/perf/perf-mysql.inc.php b/perf/perf-mysql.inc.php
index dae4422..ed92c80 100644
--- a/perf/perf-mysql.inc.php
+++ b/perf/perf-mysql.inc.php
@@ -125,7 +125,7 @@ class perf_mysql extends adodb_perf{
 
 	/**
 	 * Returns a list of table statuses.
-	 * 
+	 *
 	 * @param  string  $orderby  Unused (compatibility with parent method)
 	 * @return string A formatted set of recordsets
 	 */
@@ -299,7 +299,7 @@ class perf_mysql extends adodb_perf{
     var $optimizeTableHigh = 'OPTIMIZE TABLE %s';
 
     /**
-     * @see adodb_perf#optimizeTable
+     * @see adodb_perf::optimizeTable()
      */
      function optimizeTable( $table, $mode = ADODB_OPT_LOW)
      {
diff --git a/perf/perf-oci8.inc.php b/perf/perf-oci8.inc.php
index c11b261..345d892 100644
--- a/perf/perf-oci8.inc.php
+++ b/perf/perf-oci8.inc.php
@@ -23,7 +23,7 @@
 if (!defined('ADODB_DIR')) die();
 
 
-class perf_oci8 extends ADODB_perf{
+class perf_oci8 extends adodb_perf{
 
 	var $noShowIxora = 15; // if the sql for suspicious sql is taking too long, then disable ixora
 
diff --git a/perf/perf-postgres.inc.php b/perf/perf-postgres.inc.php
index 315c17f..16b767d 100644
--- a/perf/perf-postgres.inc.php
+++ b/perf/perf-postgres.inc.php
@@ -102,7 +102,7 @@ class perf_postgres extends adodb_perf{
 	var $optimizeTableHigh = 'VACUUM ANALYZE %s';
 
 /**
- * @see adodb_perf#optimizeTable
+ * @see adodb_perf::optimizeTable()
  */
 
 	function optimizeTable($table, $mode = ADODB_OPT_LOW)
@@ -131,7 +131,7 @@ class perf_postgres extends adodb_perf{
 
 		if ($partial) {
 			$sqlq = $this->conn->qstr($sql.'%');
-			$arr = $this->conn->GetArray("select distinct distinct sql1 from adodb_logsql where sql1 like $sqlq");
+			$arr = $this->conn->getArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq");
 			if ($arr) {
 				foreach($arr as $row) {
 					$sql = reset($row);
diff --git a/perf/perf-sqlite3.inc.php b/perf/perf-sqlite3.inc.php
new file mode 100644
index 0000000..19198d5
--- /dev/null
+++ b/perf/perf-sqlite3.inc.php
@@ -0,0 +1,40 @@
+<?php
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+class perf_sqlite3 extends adodb_perf{
+
+	var $tablesSQL = "SELECT * FROM sqlite_master WHERE type='table'";
+
+	var $createTableSQL = "CREATE TABLE adodb_logsql (
+		created datetime NOT NULL,
+		sql0 varchar(250) NOT NULL,
+		sql1 text NOT NULL,
+		params text NOT NULL,
+		tracer text NOT NULL,
+		timer decimal(16,6) NOT NULL
+		)";
+
+	var $settings = array();
+
+	function __construct(&$conn)
+	{
+		$this->conn = $conn;
+	}
+
+	function tables($orderby='1')
+	{
+		if (!$this->tablesSQL){
+			return false;
+		}
+
+		$rs = $this->conn->execute($this->tablesSQL);
+		if (!$rs) {
+			return false;
+		}
+
+		$html = rs2html($rs, false, false, false, false);
+		return $html;
+	}
+}
diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml
new file mode 100644
index 0000000..bbddd3d
--- /dev/null
+++ b/phpdoc.dist.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<phpdocumentor
+	configVersion="3"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns="https://www.phpdoc.org"
+	xsi:noNamespaceSchemaLocation="https://docs.phpdoc.org/latest/phpdoc.xsd"
+>
+	<paths>
+		<output>docs/api</output>
+		<cache>docs/cache</cache>
+	</paths>
+	<version number="latest">
+		<api>
+			<source dsn=".">
+			</source>
+			<ignore>
+				<path>scripts</path>
+				<path>tests</path>
+				<path>vendor/**/*</path>
+				<path>xsl</path>
+			</ignore>
+		</api>
+	</version>
+</phpdocumentor>
diff --git a/server.php b/server.php
deleted file mode 100644
index 76e757f..0000000
--- a/server.php
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-/**
- * ADOdb Proxy Server.
- *
- * @deprecated 5.21.0
- *
- * Security warning - use with extreme caution !
- * Depending on how it is setup, this feature can potentially expose the
- * database to attacks, particularly if used with a privileged user account.
- *
- * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
- *
- * @package ADOdb
- * @link https://adodb.org Project's web site and documentation
- * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
- *
- * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
- * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
- * any later version. This means you can use it in proprietary products.
- * See the LICENSE.md file distributed with this source code for details.
- * @license BSD-3-Clause
- * @license LGPL-2.1-or-later
- *
- * @copyright 2000-2013 John Lim
- * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
- */
-
-/* Documentation on usage is at https://adodb.org/dokuwiki/doku.php?id=v5:proxy:proxy_index
- *
- * Legal query string parameters:
- *
- * sql = holds sql string
- * nrows = number of rows to return
- * offset = skip offset rows of data
- * fetch = $ADODB_FETCH_MODE
- *
- * example:
- *
- * http://localhost/php/server.php?sql=select+*+from+table&nrows=10&offset=2
- */
-
-
-/*
- * Define the IP address you want to accept requests from
- * as a security measure. If blank we accept anyone promisciously!
- */
-$ACCEPTIP = '127.0.0.1';
-
-/*
- * Connection parameters
- */
-$driver = 'mysqli';
-$host = 'localhost'; // DSN for odbc
-$uid = 'root';
-$pwd = 'garbase-it-is';
-$database = 'test';
-
-/*============================ DO NOT MODIFY BELOW HERE =================================*/
-// $sep must match csv2rs() in adodb.inc.php
-$sep = ' :::: ';
-
-include('./adodb.inc.php');
-include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
-
-function err($s)
-{
-	die('**** '.$s.' ');
-}
-
-///////////////////////////////////////// DEFINITIONS
-
-
-$remote = $_SERVER["REMOTE_ADDR"];
-
-
-if (!empty($ACCEPTIP))
- if ($remote != '127.0.0.1' && $remote != $ACCEPTIP)
- 	err("Unauthorised client: '$remote'");
-
-
-if (empty($_REQUEST['sql'])) err('No SQL');
-
-
-$conn = ADONewConnection($driver);
-
-if (!$conn->connect($host,$uid,$pwd,$database)) err($conn->errorNo(). $sep . $conn->errorMsg());
-$sql = $_REQUEST['sql'];
-
-if (isset($_REQUEST['fetch']))
-	$ADODB_FETCH_MODE = $_REQUEST['fetch'];
-
-if (isset($_REQUEST['nrows'])) {
-	$nrows = $_REQUEST['nrows'];
-	$offset = isset($_REQUEST['offset']) ? $_REQUEST['offset'] : -1;
-	$rs = $conn->selectLimit($sql,$nrows,$offset);
-} else
-	$rs = $conn->execute($sql);
-if ($rs){
-	//$rs->timeToLive = 1;
-	echo _rs2serialize($rs,$conn,$sql);
-	$rs->close();
-} else
-	err($conn->errorNo(). $sep .$conn->errorMsg());
diff --git a/session/adodb-session2.php b/session/adodb-session2.php
index bf64b41..1979396 100644
--- a/session/adodb-session2.php
+++ b/session/adodb-session2.php
@@ -28,15 +28,14 @@ if (defined('ADODB_SESSION')) return 1;
 define('ADODB_SESSION', dirname(__FILE__));
 define('ADODB_SESSION2', ADODB_SESSION);
 
-/*
-	Unserialize session data manually. See PHPLens Issue No: 9821
-
-	From Kerr Schere, to unserialize session data stored via ADOdb.
-	1. Pull the session data from the db and loop through it.
-	2. Inside the loop, you will need to urldecode the data column.
-	3. After urldecode, run the serialized string through this function:
-
-*/
+/**
+ * 	Unserialize session data manually. See PHPLens Issue No: 9821
+ *
+ * From Kerr Schere, to unserialize session data stored via ADOdb.
+ * 1. Pull the session data from the db and loop through it.
+ * 2. Inside the loop, you will need to urldecode the data column.
+ * 3. After urldecode, run the serialized string through this function:
+ */
 function adodb_unserialize( $serialized_string )
 {
 	$variables = array( );
@@ -47,10 +46,13 @@ function adodb_unserialize( $serialized_string )
 	return( $variables );
 }
 
-/*
-	Thanks Joe Li. See PHPLens Issue No: 11487&x=1
-	Since adodb 4.61.
-*/
+/**
+ * Regenerate session id
+ *
+ * Thanks Joe Li. See PHPLens Issue No: 11487&x=1
+ *
+ * @since 4.61
+ */
 function adodb_session_regenerate_id()
 {
 	$conn = ADODB_Session::_conn();
@@ -79,45 +81,52 @@ function adodb_session_regenerate_id()
 	return true;
 }
 
-/*
-    Generate database table for session data
-    @see PHPLens Issue No: 12280
-    @return 0 if failure, 1 if errors, 2 if successful.
-	@author Markus Staab http://www.public-4u.de
-*/
+/**
+ * Generate database table for session data.
+ * @see PHPLens Issue No: 12280
+ *
+ * @return int 0 if failure, 1 if errors, 2 if successful.
+ *
+ * @author Markus Staab http://www.public-4u.de
+ */
 function adodb_session_create_table($schemaFile=null,$conn = null)
 {
-    // set default values
-    if ($schemaFile===null) $schemaFile = ADODB_SESSION . '/session_schema2.xml';
-    if ($conn===null) $conn = ADODB_Session::_conn();
+	// set default values
+	if ($schemaFile===null) $schemaFile = ADODB_SESSION . '/session_schema2.xml';
+	if ($conn===null) $conn = ADODB_Session::_conn();
 
 	if (!$conn) return 0;
 
-    $schema = new adoSchema($conn);
-    $schema->ParseSchema($schemaFile);
-    return $schema->ExecuteSchema();
+	$schema = new adoSchema($conn);
+	$schema->ParseSchema($schemaFile);
+	return $schema->ExecuteSchema();
 }
 
-/*!
-	\static
-*/
+/**
+ * ADOdb Session v2 class.
+ */
 class ADODB_Session {
+
+	/**
+	 * Session Connection's Database provider.
+	 *
+	 * Populated when opening the database connection.
+	 * @see ADODB_Session::open()}.
+	 *
+	 * @var string
+	 */
+	protected static $provider;
+
 	/////////////////////
 	// getter/setter methods
 	/////////////////////
 
-	/*
-
-	function Lock($lock=null)
-	{
-	static $_lock = false;
-
-		if (!is_null($lock)) $_lock = $lock;
-		return $lock;
-	}
-	*/
-	/*!
-	*/
+	/**
+	 * Get/Set Database driver.
+	 *
+	 * @param string $driver
+	 * @return string
+	 */
 	static function driver($driver = null)
 	{
 		static $_driver = 'mysqli';
@@ -136,8 +145,12 @@ class ADODB_Session {
 		return $_driver;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Database hostname.
+	 *
+	 * @param string $host
+	 * @return string
+	 */
 	static function host($host = null) {
 		static $_host = 'localhost';
 		static $set = false;
@@ -155,8 +168,12 @@ class ADODB_Session {
 		return $_host;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Database connection user.
+	 *
+	 * @param string $user
+	 * @return string
+	 */
 	static function user($user = null)
 	{
 		static $_user = 'root';
@@ -175,8 +192,12 @@ class ADODB_Session {
 		return $_user;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Database connection password.
+	 *
+	 * @param null $password
+	 * @return string
+	 */
 	static function password($password = null)
 	{
 		static $_password = '';
@@ -195,8 +216,12 @@ class ADODB_Session {
 		return $_password;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Database name.
+	 *
+	 * @param null $database
+	 * @return string
+	 */
 	static function database($database = null)
 	{
 		static $_database = '';
@@ -214,8 +239,12 @@ class ADODB_Session {
 		return $_database;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Connection's persistence mode.
+	 *
+	 * @param $persist
+	 * @return string|true
+	 */
 	static function persist($persist = null)
 	{
 		static $_persist = true;
@@ -227,8 +256,12 @@ class ADODB_Session {
 		return $_persist;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Connection's lifetime.
+	 *
+	 * @param int $lifetime
+	 * @return int
+	 */
 	static function lifetime($lifetime = null)
 	{
 		static $_lifetime;
@@ -255,8 +288,12 @@ class ADODB_Session {
 		return $_lifetime;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Connection's debug mode.
+	 *
+	 * @param bool $debug
+	 * @return bool
+	 */
 	static function debug($debug = null)
 	{
 		static $_debug = false;
@@ -264,11 +301,6 @@ class ADODB_Session {
 
 		if (!is_null($debug)) {
 			$_debug = (bool) $debug;
-
-			$conn = ADODB_Session::_conn();
-			if ($conn) {
-				#$conn->debug = $_debug;
-			}
 			$set = true;
 		} elseif (!$set) {
 			// backwards compatibility
@@ -280,8 +312,12 @@ class ADODB_Session {
 		return $_debug;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set garbage collection function.
+	 *
+	 * @param callable $expire_notify Function name
+	 * @return callable|false
+	 */
 	static function expireNotify($expire_notify = null)
 	{
 		static $_expire_notify;
@@ -300,8 +336,12 @@ class ADODB_Session {
 		return $_expire_notify;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set Sessions table name.
+	 *
+	 * @param string $table Session table name (defaults to 'sessions2')
+	 * @return string
+	 */
 	static function table($table = null)
 	{
 		static $_table = 'sessions2';
@@ -320,8 +360,15 @@ class ADODB_Session {
 		return $_table;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set table optimization mode.
+	 *
+	 * If true, with MySQL and PostgreSQL databases, the Sessions table will
+	 * be optimized when garbage collection is performed.
+	 *
+	 * @param bool $optimize
+	 * @return bool
+	 */
 	static function optimize($optimize = null)
 	{
 		static $_optimize = false;
@@ -340,16 +387,21 @@ class ADODB_Session {
 		return $_optimize;
 	}
 
-	/*!
-	*/
+	/**
+	 * No longer used, kept for backwards-compatibility only.
+	 *
+	 * @param int $sync_seconds
+	 * @return int
+	 *
+	 * @deprecated
+	 */
 	static function syncSeconds($sync_seconds = null) {
-		//echo ("<p>WARNING: ADODB_SESSION::syncSeconds is longer used, please remove this function for your code</p>");
-
 		return 0;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set if CLOBs are available to store session data.
+	 */
 	static function clob($clob = null) {
 		static $_clob = false;
 		static $set = false;
@@ -367,15 +419,24 @@ class ADODB_Session {
 		return $_clob;
 	}
 
-	/*!
-	*/
+	/**
+	 * No longer used, kept for backwards-compatibility only.
+	 *
+	 * @param string $data_field_name
+	 * @return string
+	 *
+	 * @deprecated
+	 */
 	static function dataFieldName($data_field_name = null) {
-		//echo ("<p>WARNING: ADODB_SESSION::dataFieldName() is longer used, please remove this function for your code</p>");
 		return '';
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set session data filter.
+	 *
+	 * @param array $filter
+	 * @return array
+	 */
 	static function filter($filter = null) {
 		static $_filter = array();
 
@@ -389,8 +450,12 @@ class ADODB_Session {
 		return $_filter;
 	}
 
-	/*!
-	*/
+	/**
+	 * Get/Set the encryption key if encrypted sessions are in use.
+	 *
+	 * @param string $encryption_key
+	 * @return string
+	 */
 	static function encryptionKey($encryption_key = null) {
 		static $_encryption_key = 'CRYPTED ADODB SESSIONS ROCK!';
 
@@ -405,14 +470,19 @@ class ADODB_Session {
 	// private methods
 	/////////////////////
 
-	/*!
-	*/
+	/**
+	 * Returns the Session's Database Connection.
+	 *
+	 * @return ADOConnection|false
+	 */
 	static function _conn($conn=null) {
 		return isset($GLOBALS['ADODB_SESS_CONN']) ? $GLOBALS['ADODB_SESS_CONN'] : false;
 	}
 
-	/*!
-	*/
+	/**
+	 * @param $crc
+	 * @return false|mixed
+	 */
 	static function _crc($crc = null) {
 		static $_crc = false;
 
@@ -423,8 +493,9 @@ class ADODB_Session {
 		return $_crc;
 	}
 
-	/*!
-	*/
+	/**
+	 * Initialize session handler.
+	 */
 	static function _init() {
 		session_set_save_handler(
 			array('ADODB_Session', 'open'),
@@ -437,16 +508,19 @@ class ADODB_Session {
 	}
 
 
-	/*!
-	*/
+	/**
+	 * Create the encryption key for crypted sessions.
+	 *
+	 * Crypt the used key, ADODB_Session::encryptionKey() as key and
+	 * session_id() as salt.
+	 */
 	static function _sessionKey() {
-		// use this function to create the encryption key for crypted sessions
-		// crypt the used key, ADODB_Session::encryptionKey() as key and session_id() as salt
 		return crypt(ADODB_Session::encryptionKey(), session_id());
 	}
 
-	/*!
-	*/
+	/**
+	 * Dump recordset.
+	 */
 	static function _dumprs(&$rs) {
 		$conn	= ADODB_Session::_conn();
 		$debug	= ADODB_Session::debug();
@@ -476,10 +550,39 @@ class ADODB_Session {
 		$rs->MoveFirst();
 	}
 
+	/**
+	 * Check if Session Connection's DB type is MySQL.
+	 *
+	 * @return bool
+	 */
+	static protected function isConnectionMysql() {
+		return self::$provider == 'mysql';
+	}
+
+	/**
+	 * Check if Session Connection's DB type is PostgreSQL.
+	 *
+	 * @return bool
+	 */
+	static protected function isConnectionPostgres() {
+		return self::$provider == 'postgres';
+	}
+
 	/////////////////////
 	// public methods
 	/////////////////////
 
+	/**
+	 * Establishes a connection to the database for session management.
+	 *
+	 * @param string $host
+	 * @param string $driver
+	 * @param string $user
+	 * @param string $password
+	 * @param string $database
+	 * @param array $options
+	 * @return void
+	 */
 	static function config($driver, $host, $user, $password, $database=false,$options=false)
 	{
 		ADODB_Session::driver($driver);
@@ -495,11 +598,17 @@ class ADODB_Session {
 		if (isset($options['debug'])) ADODB_Session::debug($options['debug']);
 	}
 
-	/*!
-		Create the connection to the database.
-
-		If $conn already exists, reuse that connection
-	*/
+	/**
+	 * Create the connection to the database.
+	 *
+	 * If $conn already exists, reuse that connection.
+	 *
+	 * @param string $save_path
+	 * @param string $session_name
+	 * @param bool $persist
+	 *
+	 * @return bool
+	 */
 	static function open($save_path, $session_name, $persist = null)
 	{
 		$conn = ADODB_Session::_conn();
@@ -521,10 +630,6 @@ class ADODB_Session {
 			$persist = ADODB_Session::persist();
 		}
 
-# these can all be defaulted to in php.ini
-#		assert('$database');
-#		assert('$driver');
-#		assert('$host');
 		if (strpos($driver, 'pdo_') === 0){
 			$conn = ADONewConnection('pdo');
 			$driver = str_replace('pdo_', '', $driver);
@@ -562,8 +667,15 @@ class ADODB_Session {
 			}
 		}
 
+		if ($ok) {
+			$GLOBALS['ADODB_SESS_CONN'] = $conn;
 
-		if ($ok) $GLOBALS['ADODB_SESS_CONN'] = $conn;
+			// Initialize Session data provider
+			self::$provider = $conn->dataProvider;
+			if (self::$provider == 'pdo') {
+				self::$provider = $conn->dsnType == 'pgsql' ? 'postgres' : $conn->dsnType;
+			}
+		}
 		else
 			ADOConnection::outp('<p>Session: connection failed</p>', false);
 
@@ -571,9 +683,9 @@ class ADODB_Session {
 		return $ok;
 	}
 
-	/*!
-		Close the connection
-	*/
+	/**
+	 * Close the connection
+	 */
 	static function close()
 	{
 /*
@@ -583,9 +695,12 @@ class ADODB_Session {
 		return true;
 	}
 
-	/*
-		Slurp in the session variables and return the serialized string
-	*/
+	/**
+	 * Slurp in the session variables and return the serialized string.
+	 *
+	 * @param string $key
+	 * @return string
+	 */
 	static function read($key)
 	{
 		$conn	= ADODB_Session::_conn();
@@ -596,9 +711,7 @@ class ADODB_Session {
 			return '';
 		}
 
-		//assert('$table');
-
-		$binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+		$binary = ADODB_Session::isConnectionMysql() ? '/*! BINARY */' : '';
 
 		global $ADODB_SESSION_SELECT_FIELDS;
 		if (!isset($ADODB_SESSION_SELECT_FIELDS)) $ADODB_SESSION_SELECT_FIELDS = 'sessdata';
@@ -635,16 +748,22 @@ class ADODB_Session {
 		return '';
 	}
 
-	/*!
-		Write the serialized data to a database.
-
-		If the data has not been modified since the last read(), we do not write.
-	*/
+	/**
+	 * Write the serialized data to a database.
+	 *
+	 * If the data has not been modified since the last read(), we do not write.
+	 *
+	 * @param string $key
+	 * @param string $oval
+	 *
+	 * @return bool
+	 */
 	static function write($key, $oval)
 	{
-	global $ADODB_SESSION_READONLY;
-
-		if (!empty($ADODB_SESSION_READONLY)) return;
+		global $ADODB_SESSION_READONLY;
+		if (!empty($ADODB_SESSION_READONLY)) {
+			return false;
+		}
 
 		$clob			= ADODB_Session::clob();
 		$conn			= ADODB_Session::_conn();
@@ -662,11 +781,9 @@ class ADODB_Session {
 		if ($debug) $conn->debug = 1;
 		$sysTimeStamp = $conn->sysTimeStamp;
 
-		//assert('$table');
-
 		$expiry = $conn->OffsetDate($lifetime/(24*3600),$sysTimeStamp);
 
-		$binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+		$binary = ADODB_Session::isConnectionMysql() ? '/*! BINARY */' : '';
 
 		// crc32 optimization since adodb 2.1
 		// now we only update expiry date, thx to sebastian thom in adodb 2.32
@@ -684,8 +801,9 @@ class ADODB_Session {
 				}
 			}
 
-
-			$sql = "UPDATE $table SET expiry = $expiry ,expireref=".$conn->Param('0').", modified = $sysTimeStamp WHERE $binary sesskey = ".$conn->Param('1')." AND expiry >= $sysTimeStamp";
+			$sql = "UPDATE $table SET expiry = $expiry, expireref=" . $conn->Param('0')
+				. ", modified = $sysTimeStamp WHERE sesskey = $binary " . $conn->Param('1')
+				. " AND expiry >= $sysTimeStamp";
 			$rs = $conn->Execute($sql,array($expirevar,$key));
 			return true;
 		}
@@ -705,7 +823,8 @@ class ADODB_Session {
 			}
 		}
 
-		if (!$clob) {	// no lobs, simply use replace()
+		if (!$clob) {
+			// no lobs, simply use replace()
 			$rs = $conn->Execute("SELECT COUNT(*) AS cnt FROM $table WHERE $binary sesskey = ".$conn->Param(0),array($key));
 			if ($rs) $rs->Close();
 
@@ -717,7 +836,6 @@ class ADODB_Session {
 					VALUES ($expiry,".$conn->Param('0').", ". $conn->Param('1').", ".$conn->Param('2').", $sysTimeStamp, $sysTimeStamp)";
 			}
 
-
 			$rs = $conn->Execute($sql,array($val,$expireref,$key));
 
 		} else {
@@ -737,14 +855,12 @@ class ADODB_Session {
 					VALUES ($expiry,$lob_value, ". $conn->Param('0').", ".$conn->Param('1').", $sysTimeStamp, $sysTimeStamp)";
 			}
 
-			$rs = $conn->Execute($sql,array($expireref,$key));
+			$conn->Execute($sql,array($expireref,$key));
 
 			$qkey = $conn->qstr($key);
-			$rs2 = $conn->UpdateBlob($table, 'sessdata', $val, " sesskey=$qkey", strtoupper($clob));
+			$conn->UpdateBlob($table, 'sessdata', $val, " sesskey=$qkey", strtoupper($clob));
 			if ($debug) echo "<hr>",htmlspecialchars($oval), "<hr>";
 			$rs = @$conn->CompleteTrans();
-
-
 		}
 
 		if (!$rs) {
@@ -768,8 +884,12 @@ class ADODB_Session {
 		return $rs ? true : false;
 	}
 
-	/*!
-	*/
+	/**
+	 * Destroy session.
+	 *
+	 * @param string $key
+	 * @return bool
+	 */
 	static function destroy($key) {
 		$conn			= ADODB_Session::_conn();
 		$table			= ADODB_Session::table();
@@ -780,16 +900,15 @@ class ADODB_Session {
 		}
 		$debug			= ADODB_Session::debug();
 		if ($debug) $conn->debug = 1;
-		//assert('$table');
 
 		$qkey = $conn->quote($key);
-		$binary = $conn->dataProvider === 'mysql' || $conn->dataProvider === 'pdo' ? '/*! BINARY */' : '';
+		$binary = ADODB_Session::isConnectionMysql() ? '/*! BINARY */' : '';
 
 		if ($expire_notify) {
 			reset($expire_notify);
 			$fn = next($expire_notify);
 			$savem = $conn->SetFetchMode(ADODB_FETCH_NUM);
-			$sql = "SELECT expireref, sesskey FROM $table WHERE $binary sesskey = $qkey";
+			$sql = "SELECT expireref, sesskey FROM $table WHERE sesskey = $binary $qkey";
 			$rs = $conn->Execute($sql);
 			ADODB_Session::_dumprs($rs);
 			$conn->SetFetchMode($savem);
@@ -799,24 +918,26 @@ class ADODB_Session {
 			if (!$rs->EOF) {
 				$ref = $rs->fields[0];
 				$key = $rs->fields[1];
-				//assert('$ref');
-				//assert('$key');
 				$fn($ref, $key);
 			}
 			$rs->Close();
 		}
 
-		$sql = "DELETE FROM $table WHERE $binary sesskey = $qkey";
+		$sql = "DELETE FROM $table WHERE sesskey = $binary $qkey";
 		$rs = $conn->Execute($sql);
 		if ($rs) {
 			$rs->Close();
 		}
 
-		return $rs ? true : false;
+		return (bool)$rs;
 	}
 
-	/*!
-	*/
+	/**
+	 * Perform garbage collection.
+	 *
+	 * @param int $maxlifetime
+	 * @return bool
+	 */
 	static function gc($maxlifetime)
 	{
 		$conn			= ADODB_Session::_conn();
@@ -829,8 +950,6 @@ class ADODB_Session {
 			return false;
 		}
 
-
-		$debug			= ADODB_Session::debug();
 		if ($debug) {
 			$conn->debug = 1;
 			$COMMITNUM = 2;
@@ -838,10 +957,8 @@ class ADODB_Session {
 			$COMMITNUM = 20;
 		}
 
-		//assert('$table');
-
 		$time = $conn->OffsetDate(-$maxlifetime/24/3600,$conn->sysTimeStamp);
-		$binary = $conn->dataProvider === 'mysql' ? '/*! BINARY */' : '';
+		$binary = ADODB_Session::isConnectionMysql() ? '/*! BINARY */' : '';
 
 		if ($expire_notify) {
 			reset($expire_notify);
@@ -858,13 +975,12 @@ class ADODB_Session {
 		if ($rs) {
 			$tr = $conn->hasTransactions;
 			if ($tr) $conn->BeginTrans();
-			$keys = array();
 			$ccnt = 0;
 			while (!$rs->EOF) {
 				$ref = $rs->fields[0];
 				$key = $rs->fields[1];
 				if ($fn) $fn($ref, $key);
-				$del = $conn->Execute("DELETE FROM $table WHERE sesskey=".$conn->Param('0'),array($key));
+				$conn->Execute("DELETE FROM $table WHERE sesskey = $binary " . $conn->Param('0'), array($key));
 				$rs->MoveNext();
 				$ccnt += 1;
 				if ($tr && $ccnt % $COMMITNUM == 0) {
@@ -881,12 +997,9 @@ class ADODB_Session {
 
 		// suggested by Cameron, "GaM3R" <gamr@outworld.cx>
 		if ($optimize) {
-			$driver = ADODB_Session::driver();
-
-			if (preg_match('/mysql/i', $driver)) {
+			if (ADODB_Session::isConnectionMysql()) {
 				$sql = "OPTIMIZE TABLE $table";
-			}
-			if (preg_match('/postgres/i', $driver)) {
+			} elseif (ADODB_Session::isConnectionPostgres()) {
 				$sql = "VACUUM $table";
 			}
 			if (!empty($sql)) {
@@ -903,12 +1016,16 @@ ADODB_Session::_init();
 if (empty($ADODB_SESSION_READONLY))
 	register_shutdown_function('session_write_close');
 
-// for backwards compatibility only
+/**
+ * @deprecated for backwards compatibility only
+ */
 function adodb_sess_open($save_path, $session_name, $persist = true) {
 	return ADODB_Session::open($save_path, $session_name, $persist);
 }
 
-// for backwards compatibility only
+/**
+ * @deprecated for backwards compatibility only
+ */
 function adodb_sess_gc($t)
 {
 	return ADODB_Session::gc($t);
diff --git a/session/session_schema2.xml b/session/session_schema2.xml
index 89cb4f2..a8a1b22 100644
--- a/session/session_schema2.xml
+++ b/session/session_schema2.xml
@@ -1,37 +1,33 @@
 <?xml version="1.0"?>
 <schema version="0.3">
   <table name="sessions2">
-    <descr>table for ADOdb session-management</descr>
-
+    <descr>Table for ADOdb session management</descr>
     <field name="SESSKEY" type="C" size="64">
-      <descr>session key</descr>
+      <descr>Session key to identify a user's browser session.</descr>
       <KEY/>
       <NOTNULL/>
     </field>
-
-
-
     <field name="EXPIRY" type="T">
-      <descr></descr>
+      <descr>Slightly redundant as it can be dynamically calculated by NOW() and MODIFIED field,
+        but it enables forcing a fixed timeout for specific sessions.
+      </descr>
       <NOTNULL/>
     </field>
-
-	 	<field name="CREATED" type="T">
-	     <descr></descr>
+    <field name="CREATED" type="T">
+      <descr>New session creation Timestamp.</descr>
       <NOTNULL/>
     </field>
-
-	 	<field name="MODIFIED" type="T">
-	     <descr></descr>
+    <field name="MODIFIED" type="T">
+      <descr>Timestamp which is usually updated when the user interacts with a site, to extend the expire time.</descr>
       <NOTNULL/>
     </field>
-
     <field name="EXPIREREF" type="C" size="250">
-      <descr></descr>
+      <descr>Usually a User Id or unique username of your application.
+        The name EXPIREREF is a bit weird, it may be better to call it USERREF?
+      </descr>
     </field>
-
     <field name="SESSDATA" type="XL">
-      <descr></descr>
+      <descr>PHP's serialized session data or encrypted serialized session data.</descr>
       <NOTNULL/>
     </field>
   </table>
diff --git a/tohtml.inc.php b/tohtml.inc.php
index e92c8b4..6e9a628 100644
--- a/tohtml.inc.php
+++ b/tohtml.inc.php
@@ -113,7 +113,8 @@ GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND;
 				else
 					$v = round($v,$ADODB_ROUND);
 			case 'I':
-				$vv = stripslashes((trim($v)));
+				$vv = $v ? stripslashes(trim($v)) : '';
+				$vv = $vv ?: '&nbsp;';
 				if (strlen($vv) == 0) $vv .= '&nbsp;';
 				$s .= "	<TD align=right>".$vv ."</TD>\n";
 
@@ -139,11 +140,17 @@ GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND;
 			*/
 
 			default:
-				if ($htmlspecialchars) $v = htmlspecialchars(trim($v));
-				$v = trim($v);
-				if (strlen($v) == 0) $v = '&nbsp;';
-				$s .= "	<TD>". str_replace("\n",'<br>',stripslashes($v)) ."</TD>\n";
-
+				if ($v) {
+					$v = trim($v);
+					if ($htmlspecialchars) {
+						$v = htmlspecialchars($v);
+					}
+				} elseif ($v === null) {
+					$v = '(NULL)';
+				} else {
+					$v = '&nbsp;';
+				}
+				$s .= "	<TD>" . str_replace("\n", '<br>', $v) . "</TD>\n";
 			}
 		} // for
 		$s .= "</TR>\n\n";

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/php/adodb/perf/perf-sqlite3.inc.php

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/php/adodb/drivers/adodb-mysql.inc.php
-rw-r--r--  root/root   /usr/share/php/adodb/drivers/adodb-mysqlpo.inc.php
-rw-r--r--  root/root   /usr/share/php/adodb/drivers/adodb-mysqlt.inc.php
-rw-r--r--  root/root   /usr/share/php/adodb/server.php

No differences were encountered in the control files

More details

Full run details