From 2f326cef12e570c46ed4c764fca4e3acb8c5f6ed Mon Sep 17 00:00:00 2001 From: stronk7 Date: Sun, 3 May 2009 22:33:18 +0000 Subject: [PATCH] MDL-19055 Upgrade to AdoDB 5.08 + local patches reapplied --- lib/adodb/adodb-active-record.inc.php | 361 +- lib/adodb/adodb-active-recordx.inc.php | 1421 +++++ lib/adodb/adodb-csvlib.inc.php | 6 +- lib/adodb/adodb-datadict.inc.php | 19 +- lib/adodb/adodb-error.inc.php | 2 +- lib/adodb/adodb-errorhandler.inc.php | 2 +- lib/adodb/adodb-errorpear.inc.php | 2 +- lib/adodb/adodb-exceptions.inc.php | 2 +- lib/adodb/adodb-iterator.inc.php | 2 +- lib/adodb/adodb-lib.inc.php | 57 +- lib/adodb/adodb-memcache.lib.inc.php | 246 +- lib/adodb/adodb-pager.inc.php | 4 +- lib/adodb/adodb-pear.inc.php | 4 +- lib/adodb/adodb-perf.inc.php | 95 +- lib/adodb/adodb-php4.inc.php | 2 +- lib/adodb/adodb-time.inc.php | 1 - lib/adodb/adodb-xmlschema.inc.php | 4442 +++++++-------- lib/adodb/adodb-xmlschema03.inc.php | 4805 +++++++++-------- lib/adodb/adodb.inc.php | 418 +- lib/adodb/datadict/datadict-access.inc.php | 4 +- lib/adodb/datadict/datadict-db2.inc.php | 4 +- lib/adodb/datadict/datadict-firebird.inc.php | 4 +- lib/adodb/datadict/datadict-generic.inc.php | 2 +- lib/adodb/datadict/datadict-ibase.inc.php | 2 +- lib/adodb/datadict/datadict-informix.inc.php | 4 +- lib/adodb/datadict/datadict-mssql.inc.php | 6 +- .../datadict/datadict-mssqlnative.inc.php | 282 + lib/adodb/datadict/datadict-mysql.inc.php | 4 +- lib/adodb/datadict/datadict-oci8.inc.php | 19 +- lib/adodb/datadict/datadict-postgres.inc.php | 4 +- lib/adodb/datadict/datadict-sapdb.inc.php | 4 +- lib/adodb/datadict/datadict-sybase.inc.php | 4 +- lib/adodb/drivers/adodb-access.inc.php | 2 +- lib/adodb/drivers/adodb-ado.inc.php | 26 +- lib/adodb/drivers/adodb-ado5.inc.php | 40 +- lib/adodb/drivers/adodb-ado_access.inc.php | 2 +- lib/adodb/drivers/adodb-ado_mssql.inc.php | 2 +- lib/adodb/drivers/adodb-borland_ibase.inc.php | 2 +- lib/adodb/drivers/adodb-csv.inc.php | 2 +- lib/adodb/drivers/adodb-db2.inc.php | 14 +- lib/adodb/drivers/adodb-fbsql.inc.php | 4 +- lib/adodb/drivers/adodb-firebird.inc.php | 2 +- lib/adodb/drivers/adodb-ibase.inc.php | 2 +- lib/adodb/drivers/adodb-informix.inc.php | 2 +- lib/adodb/drivers/adodb-informix72.inc.php | 4 +- lib/adodb/drivers/adodb-ldap.inc.php | 18 +- lib/adodb/drivers/adodb-mssql.inc.php | 40 +- lib/adodb/drivers/adodb-mssql_n.inc.php | 4 +- lib/adodb/drivers/adodb-mssqlnative.inc.php | 922 ++++ lib/adodb/drivers/adodb-mssqlpo.inc.php | 4 +- lib/adodb/drivers/adodb-mysql.inc.php | 21 +- lib/adodb/drivers/adodb-mysqli.inc.php | 77 +- lib/adodb/drivers/adodb-mysqlpo.inc.php | 2 +- lib/adodb/drivers/adodb-mysqlt.inc.php | 10 +- lib/adodb/drivers/adodb-netezza.inc.php | 2 +- lib/adodb/drivers/adodb-oci8.inc.php | 70 +- lib/adodb/drivers/adodb-oci8.old.inc.php | 1532 ++++++ lib/adodb/drivers/adodb-oci805.inc.php | 2 +- lib/adodb/drivers/adodb-oci8po.inc.php | 7 +- lib/adodb/drivers/adodb-odbc.inc.php | 2 +- lib/adodb/drivers/adodb-odbc_db2.inc.php | 2 +- lib/adodb/drivers/adodb-odbc_mssql.inc.php | 4 +- lib/adodb/drivers/adodb-odbc_oracle.inc.php | 2 +- lib/adodb/drivers/adodb-odbtp.inc.php | 97 +- lib/adodb/drivers/adodb-odbtp_unicode.inc.php | 2 +- lib/adodb/drivers/adodb-oracle.inc.php | 2 +- lib/adodb/drivers/adodb-pdo.inc.php | 57 +- lib/adodb/drivers/adodb-pdo_mssql.inc.php | 6 +- lib/adodb/drivers/adodb-pdo_mysql.inc.php | 18 +- lib/adodb/drivers/adodb-pdo_oci.inc.php | 4 +- lib/adodb/drivers/adodb-pdo_pgsql.inc.php | 2 +- lib/adodb/drivers/adodb-pdo_sqlite.inc.php | 190 + lib/adodb/drivers/adodb-postgres.inc.php | 2 +- lib/adodb/drivers/adodb-postgres64.inc.php | 7 +- lib/adodb/drivers/adodb-postgres7.inc.php | 57 +- lib/adodb/drivers/adodb-postgres8.inc.php | 2 +- lib/adodb/drivers/adodb-proxy.inc.php | 2 +- lib/adodb/drivers/adodb-sapdb.inc.php | 2 +- lib/adodb/drivers/adodb-sqlanywhere.inc.php | 2 +- lib/adodb/drivers/adodb-sqlite.inc.php | 2 +- lib/adodb/drivers/adodb-sqlitepo.inc.php | 2 +- lib/adodb/drivers/adodb-sybase.inc.php | 42 +- lib/adodb/drivers/adodb-sybase_ase.inc.php | 2 +- lib/adodb/drivers/adodb-vfp.inc.php | 2 +- lib/adodb/lang/adodb-fa.inc.php | 35 + lib/adodb/perf/perf-db2.inc.php | 2 +- lib/adodb/perf/perf-informix.inc.php | 2 +- lib/adodb/perf/perf-mssql.inc.php | 2 +- lib/adodb/perf/perf-mssqlnative.inc.php | 164 + lib/adodb/perf/perf-mysql.inc.php | 533 +- lib/adodb/perf/perf-oci8.inc.php | 26 +- lib/adodb/perf/perf-postgres.inc.php | 2 +- lib/adodb/pivottable.inc.php | 2 +- lib/adodb/readme_moodle.txt | 14 +- lib/adodb/rsfilter.inc.php | 2 +- lib/adodb/toexport.inc.php | 7 +- lib/adodb/tohtml.inc.php | 22 +- 97 files changed, 10873 insertions(+), 5501 deletions(-) create mode 100644 lib/adodb/adodb-active-recordx.inc.php create mode 100644 lib/adodb/datadict/datadict-mssqlnative.inc.php create mode 100644 lib/adodb/drivers/adodb-mssqlnative.inc.php create mode 100644 lib/adodb/drivers/adodb-oci8.old.inc.php create mode 100644 lib/adodb/drivers/adodb-pdo_sqlite.inc.php create mode 100644 lib/adodb/lang/adodb-fa.inc.php create mode 100644 lib/adodb/perf/perf-mssqlnative.inc.php diff --git a/lib/adodb/adodb-active-record.inc.php b/lib/adodb/adodb-active-record.inc.php index 328eaa8b63..796713b06e 100644 --- a/lib/adodb/adodb-active-record.inc.php +++ b/lib/adodb/adodb-active-record.inc.php @@ -1,7 +1,7 @@ db = $db; $obj->tables = array(); - $_ADODB_ACTIVE_DBS[] = $obj; + if ($index == false) $index = sizeof($_ADODB_ACTIVE_DBS); + + + $_ADODB_ACTIVE_DBS[$index] = $obj; return sizeof($_ADODB_ACTIVE_DBS)-1; } class ADODB_Active_Record { + static $_changeNames = true; // dynamically pluralize table names + static $_foreignSuffix = '_id'; // var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat] var $_table; // tablename, if set in class definition then use it as table name var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat] @@ -70,7 +81,9 @@ class ADODB_Active_Record { var $_saved = false; // indicates whether data is already inserted. var $_lasterr = false; // last error message var $_original = false; // the original values loaded or inserted, refreshed on update - + + var $foreignName; // CFR: class name when in a relationship + static function UseDefaultValues($bool=null) { global $ADODB_ACTIVE_DEFVALS; @@ -79,9 +92,9 @@ class ADODB_Active_Record { } // should be static - static function SetDatabaseAdapter(&$db) + static function SetDatabaseAdapter(&$db, $index=false) { - return ADODB_SetDatabaseAdapter($db); + return ADODB_SetDatabaseAdapter($db, $index); } @@ -105,16 +118,18 @@ class ADODB_Active_Record { if (!empty($this->_table)) $table = $this->_table; else $table = $this->_pluralize(get_class($this)); } + $this->foreignName = strtolower(get_class($this)); // CFR: default foreign name if ($db) { $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db); - } else - $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1; - - - if ($this->_dbat < 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor'); - + } else if (!isset($this->_dbat)) { + if (sizeof($_ADODB_ACTIVE_DBS) == 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor'); + end($_ADODB_ACTIVE_DBS); + $this->_dbat = key($_ADODB_ACTIVE_DBS); + } + $this->_table = $table; $this->_tableat = $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future + $this->UpdateActiveTable($pkeyarr); } @@ -126,6 +141,8 @@ class ADODB_Active_Record { function _pluralize($table) { + if (!ADODB_Active_Record::$_changeNames) return $table; + $ut = strtoupper($table); $len = strlen($table); $lastc = $ut[$len-1]; @@ -145,13 +162,178 @@ class ADODB_Active_Record { } } + // CFR Lamest singular inflector ever - @todo Make it real! + // Note: There is an assumption here...and it is that the argument's length >= 4 + function _singularize($tables) + { + + if (!ADODB_Active_Record::$_changeNames) return $table; + + $ut = strtoupper($tables); + $len = strlen($tables); + if($ut[$len-1] != 'S') + return $tables; // I know...forget oxen + if($ut[$len-2] != 'E') + return substr($tables, 0, $len-1); + switch($ut[$len-3]) + { + case 'S': + case 'X': + return substr($tables, 0, $len-2); + case 'I': + return substr($tables, 0, $len-3) . 'y'; + case 'H'; + if($ut[$len-4] == 'C' || $ut[$len-4] == 'S') + return substr($tables, 0, $len-2); + default: + return substr($tables, 0, $len-1); // ? + } + } + + function hasMany($foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record') + { + $ar = new $foreignClass($foreignRef); + $ar->foreignName = $foreignRef; + $ar->UpdateActiveTable(); + $ar->foreignKey = ($foreignKey) ? $foreignKey : $foreignRef.ADODB_Active_Record::$_foreignSuffix; + $table =& $this->TableInfo(); + $table->_hasMany[$foreignRef] = $ar; + # $this->$foreignRef = $this->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get() + } + + // use when you don't want ADOdb to auto-pluralize tablename + static function TableHasMany($table, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record') + { + $ar = new ADODB_Active_Record($table); + $ar->hasMany($foreignRef, $foreignKey, $foreignClass); + } + + // use when you don't want ADOdb to auto-pluralize tablename + static function TableKeyHasMany($table, $tablePKey, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record') + { + if (!is_array($tablePKey)) $tablePKey = array($tablePKey); + $ar = new ADODB_Active_Record($table,$tablePKey); + $ar->hasMany($foreignRef, $foreignKey, $foreignClass); + } + + + // use when you want ADOdb to auto-pluralize tablename for you. Note that the class must already be defined. + // e.g. class Person will generate relationship for table Persons + static function ClassHasMany($parentclass, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record') + { + $ar = new $parentclass(); + $ar->hasMany($foreignRef, $foreignKey, $foreignClass); + } + + + function belongsTo($foreignRef,$foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record') + { + global $inflector; + + $ar = new $parentClass($this->_pluralize($foreignRef)); + $ar->foreignName = $foreignRef; + $ar->parentKey = $parentKey; + $ar->UpdateActiveTable(); + $ar->foreignKey = ($foreignKey) ? $foreignKey : $foreignRef.ADODB_Active_Record::$_foreignSuffix; + + $table =& $this->TableInfo(); + $table->_belongsTo[$foreignRef] = $ar; + # $this->$foreignRef = $this->_belongsTo[$foreignRef]; + } + + static function ClassBelongsTo($class, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record') + { + $ar = new $class(); + $ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass); + } + + static function TableBelongsTo($table, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record') + { + $ar = new ADOdb_Active_Record($table); + $ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass); + } + + static function TableKeyBelongsTo($table, $tablePKey, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record') + { + if (!is_array($tablePKey)) $tablePKey = array($tablePKey); + $ar = new ADOdb_Active_Record($table, $tablePKey); + $ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass); + } + + + /** + * __get Access properties - used for lazy loading + * + * @param mixed $name + * @access protected + * @return mixed + */ + function __get($name) + { + return $this->LoadRelations($name, '', -1, -1); + } + + /** + * @param string $name + * @param string $whereOrderBy : eg. ' AND field1 = value ORDER BY field2' + * @param offset + * @param limit + * @return mixed + */ + function LoadRelations($name, $whereOrderBy='', $offset=-1,$limit=-1) + { + $extras = array(); + $table = $this->TableInfo(); + if ($limit >= 0) $extras['limit'] = $limit; + if ($offset >= 0) $extras['offset'] = $offset; + + if (strlen($whereOrderBy)) + if (!preg_match('/^[ \n\r]*AND/i',$whereOrderBy)) + if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i',$whereOrderBy)) + $whereOrderBy = 'AND '.$whereOrderBy; + + if(!empty($table->_belongsTo[$name])) + { + $obj = $table->_belongsTo[$name]; + $columnName = $obj->foreignKey; + if(empty($this->$columnName)) + $this->$name = null; + else + { + if ($obj->parentKey) $key = $obj->parentKey; + else $key = reset($table->keys); + + $arrayOfOne = $obj->Find($key.'='.$this->$columnName.' '.$whereOrderBy,false,false,$extras); + if ($arrayOfOne) { + $this->$name = $arrayOfOne[0]; + return $arrayOfOne[0]; + } + } + } + if(!empty($table->_hasMany[$name])) + { + $obj = $table->_hasMany[$name]; + $key = reset($table->keys); + $id = @$this->$key; + if (!is_numeric($id)) { + $db = $this->DB(); + $id = $db->qstr($id); + } + $objs = $obj->Find($obj->foreignKey.'='.$id. ' '.$whereOrderBy,false,false,$extras); + if (!$objs) $objs = array(); + $this->$name = $objs; + return $objs; + } + + return array(); + } ////////////////////////////////// // update metadata function UpdateActiveTable($pkeys=false,$forceUpdate=false) { global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS; - global $ADODB_ACTIVE_DEFVALS; + global $ADODB_ACTIVE_DEFVALS,$ADODB_FETCH_MODE; $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat]; @@ -169,7 +351,6 @@ class ADODB_Active_Record { } return; } - $db = $activedb->db; $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache'; if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) { @@ -191,8 +372,15 @@ class ADODB_Active_Record { $activetab = new ADODB_Active_Table(); $activetab->name = $table; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + if ($db->fetchMode !== false) $savem = $db->SetFetchMode(false); $cols = $db->MetaColumns($table); + + if (isset($savem)) $db->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if (!$cols) { $this->Error("Invalid table name: $table",'UpdateActiveTable'); return false; @@ -270,6 +458,12 @@ class ADODB_Active_Record { if (!function_exists('adodb_write_file')) include(ADODB_DIR.'/adodb-csvlib.inc.php'); adodb_write_file($fname,$s); } + if (isset($activedb->tables[$table])) { + $oldtab = $activedb->tables[$table]; + + if ($oldtab) $activetab->_belongsTo = $oldtab->_belongsTo; + if ($oldtab) $activetab->_hasMany = $oldtab->_hasMany; + } $activedb->tables[$table] = $activetab; } @@ -338,15 +532,26 @@ class ADODB_Active_Record { } // retrieve ADODB_Active_Table - function TableInfo() + function &TableInfo() { global $_ADODB_ACTIVE_DBS; - $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat]; $table = $activedb->tables[$this->_tableat]; return $table; } + + // I have an ON INSERT trigger on a table that sets other columns in the table. + // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook + function Reload() + { + $db =& $this->DB(); if (!$db) return false; + $table =& $this->TableInfo(); + $where = $this->GenWhere($db, $table); + return($this->Load($where)); + } + + // set a numeric array (using natural table field ordering) as object properties function Set(&$row) { @@ -378,7 +583,8 @@ class ADODB_Active_Record { # } else - $keys = array_keys($row); + $keys = array_keys($row); + # reset($keys); $this->_original = array(); @@ -388,6 +594,7 @@ class ADODB_Active_Record { $this->_original[] = $value; next($keys); } + # return true; } @@ -414,12 +621,15 @@ class ADODB_Active_Record { case 'D': case 'T': if (empty($val)) return 'null'; - + + case 'B': + case 'N': case 'C': case 'X': if (is_null($val)) return 'null'; - if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") { + if (strlen($val)>1 && + (strncmp($val,"'",1) != 0 || substr($val,strlen($val)-1,1) != "'")) { return $db->qstr($val); break; } @@ -447,18 +657,48 @@ class ADODB_Active_Record { //------------------------------------------------------------ Public functions below - function Load($where,$bindarr=false) + function Load($where=null,$bindarr=false) { + global $ADODB_FETCH_MODE; + $db = $this->DB(); if (!$db) return false; $this->_where = $where; - $save = $db->SetFetchMode(ADODB_FETCH_NUM); - $row = $db->GetRow("select * from ".$this->_table.' WHERE '.$where,$bindarr); - $db->SetFetchMode($save); + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($db->fetchMode !== false) $savem = $db->SetFetchMode(false); + + $qry = "select * from ".$this->_table; + + if($where) { + $qry .= ' WHERE '.$where; + } + $row = $db->GetRow($qry,$bindarr); + + if (isset($savem)) $db->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; return $this->Set($row); } + # useful for multiple record inserts + # see http://phplens.com/lens/lensforum/msgs.php?id=17795 + function Reset() + { + $this->_where=null; + $this->_saved = false; + $this->_lasterr = false; + $this->_original = false; + $vars=get_object_vars($this); + foreach($vars as $k=>$v){ + if(substr($k,0,1)!=='_'){ + $this->{$k}=null; + } + } + $this->foreignName=strtolower(get_class($this)); + return true; + } + // false on error function Save() { @@ -481,7 +721,7 @@ class ADODB_Active_Record { foreach($table->flds as $name=>$fld) { $val = $this->$name; - if(!is_null($val) || !array_key_exists($name, $table->keys)) { + if(!is_array($val) || !is_null($val) || !array_key_exists($name, $table->keys)) { $valarr[] = $val; $names[] = $name; $valstr[] = $db->Param($cnt); @@ -532,10 +772,10 @@ class ADODB_Active_Record { } // returns an array of active record objects - function Find($whereOrderBy,$bindarr=false,$pkeysArr=false) + function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array()) { $db = $this->DB(); if (!$db || empty($this->_table)) return false; - $arr = $db->GetActiveRecordsClass(get_class($this),$this->_table, $whereOrderBy,$bindarr,$pkeysArr); + $arr = $db->GetActiveRecordsClass(get_class($this),$this->_table, $whereOrderBy,$bindarr,$pkeysArr,$extra); return $arr; } @@ -564,6 +804,9 @@ class ADODB_Active_Record { if (is_null($val) && !empty($fld->auto_increment)) { continue; } + + if (is_array($val)) continue; + $t = $db->MetaType($fld->type); $arr[$name] = $this->doquote($db,$val,$t); $valarr[] = $val; @@ -623,9 +866,8 @@ class ADODB_Active_Record { $val = $this->$name; $neworig[] = $val; - if (isset($table->keys[$name])) { + if (isset($table->keys[$name]) || is_array($val)) continue; - } if (is_null($val)) { if (isset($fld->not_null) && $fld->not_null) { @@ -665,4 +907,65 @@ class ADODB_Active_Record { }; +function adodb_GetActiveRecordsClass(&$db, $class, $table,$whereOrderBy,$bindarr, $primkeyArr, + $extra) +{ +global $_ADODB_ACTIVE_DBS; + + + $save = $db->SetFetchMode(ADODB_FETCH_NUM); + $qry = "select * from ".$table; + + if (!empty($whereOrderBy)) + $qry .= ' WHERE '.$whereOrderBy; + if(isset($extra['limit'])) + { + $rows = false; + if(isset($extra['offset'])) { + $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset']); + } else { + $rs = $db->SelectLimit($qry, $extra['limit']); + } + if ($rs) { + while (!$rs->EOF) { + $rows[] = $rs->fields; + $rs->MoveNext(); + } + } + } else + $rows = $db->GetAll($qry,$bindarr); + + $db->SetFetchMode($save); + + $false = false; + + if ($rows === false) { + return $false; + } + + + if (!class_exists($class)) { + $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass'); + return $false; + } + $arr = array(); + // arrRef will be the structure that knows about our objects. + // It is an associative array. + // We will, however, return arr, preserving regular 0.. order so that + // obj[0] can be used by app developpers. + $arrRef = array(); + $bTos = array(); // Will store belongTo's indices if any + foreach($rows as $row) { + + $obj = new $class($table,$primkeyArr,$db); + if ($obj->ErrorNo()){ + $db->_errorMsg = $obj->ErrorMsg(); + return $false; + } + $obj->Set($row); + $arr[] = $obj; + } // foreach($rows as $row) + + return $arr; +} ?> \ No newline at end of file diff --git a/lib/adodb/adodb-active-recordx.inc.php b/lib/adodb/adodb-active-recordx.inc.php new file mode 100644 index 0000000000..6d270b6545 --- /dev/null +++ b/lib/adodb/adodb-active-recordx.inc.php @@ -0,0 +1,1421 @@ +_dbat +$_ADODB_ACTIVE_DBS = array(); +$ACTIVE_RECORD_SAFETY = true; // CFR: disabled while playing with relations +$ADODB_ACTIVE_DEFVALS = false; + +class ADODB_Active_DB { + var $db; // ADOConnection + var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename +} + +class ADODB_Active_Table { + var $name; // table name + var $flds; // assoc array of adofieldobjs, indexed by fieldname + var $keys; // assoc array of primary keys, indexed by fieldname + var $_created; // only used when stored as a cached file + var $_belongsTo = array(); + var $_hasMany = array(); + var $_colsCount; // total columns count, including relations + + function updateColsCount() + { + $this->_colsCount = sizeof($this->flds); + foreach($this->_belongsTo as $foreignTable) + $this->_colsCount += sizeof($foreignTable->TableInfo()->flds); + foreach($this->_hasMany as $foreignTable) + $this->_colsCount += sizeof($foreignTable->TableInfo()->flds); + } +} + +// returns index into $_ADODB_ACTIVE_DBS +function ADODB_SetDatabaseAdapter(&$db) +{ + global $_ADODB_ACTIVE_DBS; + + foreach($_ADODB_ACTIVE_DBS as $k => $d) { + if (PHP_VERSION >= 5) { + if ($d->db === $db) return $k; + } else { + if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database) + return $k; + } + } + + $obj = new ADODB_Active_DB(); + $obj->db = $db; + $obj->tables = array(); + + $_ADODB_ACTIVE_DBS[] = $obj; + + return sizeof($_ADODB_ACTIVE_DBS)-1; +} + + +class ADODB_Active_Record { + static $_changeNames = true; // dynamically pluralize table names + static $_foreignSuffix = '_id'; // + var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat] + var $_table; // tablename, if set in class definition then use it as table name + var $_sTable; // singularized table name + var $_pTable; // pluralized table name + var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat] + var $_where; // where clause set in Load() + var $_saved = false; // indicates whether data is already inserted. + var $_lasterr = false; // last error message + var $_original = false; // the original values loaded or inserted, refreshed on update + + var $foreignName; // CFR: class name when in a relationship + + static function UseDefaultValues($bool=null) + { + global $ADODB_ACTIVE_DEFVALS; + if (isset($bool)) $ADODB_ACTIVE_DEFVALS = $bool; + return $ADODB_ACTIVE_DEFVALS; + } + + // should be static + static function SetDatabaseAdapter(&$db) + { + return ADODB_SetDatabaseAdapter($db); + } + + + public function __set($name, $value) + { + $name = str_replace(' ', '_', $name); + $this->$name = $value; + } + + // php5 constructor + // Note: if $table is defined, then we will use it as our table name + // Otherwise we will use our classname... + // In our database, table names are pluralized (because there can be + // more than one row!) + // Similarly, if $table is defined here, it has to be plural form. + // + // $options is an array that allows us to tweak the constructor's behaviour + // if $options['refresh'] is true, we re-scan our metadata information + // if $options['new'] is true, we forget all relations + function __construct($table = false, $pkeyarr=false, $db=false, $options=array()) + { + global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS; + + if ($db == false && is_object($pkeyarr)) { + $db = $pkeyarr; + $pkeyarr = false; + } + + if($table) + { + // table argument exists. It is expected to be + // already plural form. + $this->_pTable = $table; + $this->_sTable = $this->_singularize($this->_pTable); + } + else + { + // We will use current classname as table name. + // We need to pluralize it for the real table name. + $this->_sTable = strtolower(get_class($this)); + $this->_pTable = $this->_pluralize($this->_sTable); + } + $this->_table = &$this->_pTable; + + $this->foreignName = $this->_sTable; // CFR: default foreign name (singular) + + if ($db) { + $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db); + } else + $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1; + + + if ($this->_dbat < 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor'); + + $this->_tableat = $this->_table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future + + // CFR: Just added this option because UpdateActiveTable() can refresh its information + // but there was no way to ask it to do that. + $forceUpdate = (isset($options['refresh']) && true === $options['refresh']); + $this->UpdateActiveTable($pkeyarr, $forceUpdate); + if(isset($options['new']) && true === $options['new']) + { + $table =& $this->TableInfo(); + unset($table->_hasMany); + unset($table->_belongsTo); + $table->_hasMany = array(); + $table->_belongsTo = array(); + } + } + + function __wakeup() + { + $class = get_class($this); + new $class; + } + + // CFR: Constants found in Rails + static $IrregularP = array( + 'PERSON' => 'people', + 'MAN' => 'men', + 'WOMAN' => 'women', + 'CHILD' => 'children', + 'COW' => 'kine', + ); + + static $IrregularS = array( + 'PEOPLE' => 'PERSON', + 'MEN' => 'man', + 'WOMEN' => 'woman', + 'CHILDREN' => 'child', + 'KINE' => 'cow', + ); + + static $WeIsI = array( + 'EQUIPMENT' => true, + 'INFORMATION' => true, + 'RICE' => true, + 'MONEY' => true, + 'SPECIES' => true, + 'SERIES' => true, + 'FISH' => true, + 'SHEEP' => true, + ); + + function _pluralize($table) + { + if (!ADODB_Active_Record::$_changeNames) return $table; + + $ut = strtoupper($table); + if(isset(self::$WeIsI[$ut])) + { + return $table; + } + if(isset(self::$IrregularP[$ut])) + { + return self::$IrregularP[$ut]; + } + $len = strlen($table); + $lastc = $ut[$len-1]; + $lastc2 = substr($ut,$len-2); + switch ($lastc) { + case 'S': + return $table.'es'; + case 'Y': + return substr($table,0,$len-1).'ies'; + case 'X': + return $table.'es'; + case 'H': + if ($lastc2 == 'CH' || $lastc2 == 'SH') + return $table.'es'; + default: + return $table.'s'; + } + } + + // CFR Lamest singular inflector ever - @todo Make it real! + // Note: There is an assumption here...and it is that the argument's length >= 4 + function _singularize($table) + { + + if (!ADODB_Active_Record::$_changeNames) return $table; + + $ut = strtoupper($table); + if(isset(self::$WeIsI[$ut])) + { + return $table; + } + if(isset(self::$IrregularS[$ut])) + { + return self::$IrregularS[$ut]; + } + $len = strlen($table); + if($ut[$len-1] != 'S') + return $table; // I know...forget oxen + if($ut[$len-2] != 'E') + return substr($table, 0, $len-1); + switch($ut[$len-3]) + { + case 'S': + case 'X': + return substr($table, 0, $len-2); + case 'I': + return substr($table, 0, $len-3) . 'y'; + case 'H'; + if($ut[$len-4] == 'C' || $ut[$len-4] == 'S') + return substr($table, 0, $len-2); + default: + return substr($table, 0, $len-1); // ? + } + } + + /* + * ar->foreignName will contain the name of the tables associated with this table because + * these other tables' rows may also be referenced by this table using theirname_id or the provided + * foreign keys (this index name is stored in ar->foreignKey) + * + * this-table.id = other-table-#1.this-table_id + * = other-table-#2.this-table_id + */ + function hasMany($foreignRef,$foreignKey=false) + { + $ar = new ADODB_Active_Record($foreignRef); + $ar->foreignName = $foreignRef; + $ar->UpdateActiveTable(); + $ar->foreignKey = ($foreignKey) ? $foreignKey : strtolower(get_class($this)) . self::$_foreignSuffix; + + $table =& $this->TableInfo(); + if(!isset($table->_hasMany[$foreignRef])) + { + $table->_hasMany[$foreignRef] = $ar; + $table->updateColsCount(); + } +# @todo Can I make this guy be lazy? + $this->$foreignRef = $table->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get() + } + + /** + * ar->foreignName will contain the name of the tables associated with this table because + * this table's rows may also be referenced by those tables using thistable_id or the provided + * foreign keys (this index name is stored in ar->foreignKey) + * + * this-table.other-table_id = other-table.id + */ + function belongsTo($foreignRef,$foreignKey=false) + { + global $inflector; + + $ar = new ADODB_Active_Record($this->_pluralize($foreignRef)); + $ar->foreignName = $foreignRef; + $ar->UpdateActiveTable(); + $ar->foreignKey = ($foreignKey) ? $foreignKey : $ar->foreignName . self::$_foreignSuffix; + + $table =& $this->TableInfo(); + if(!isset($table->_belongsTo[$foreignRef])) + { + $table->_belongsTo[$foreignRef] = $ar; + $table->updateColsCount(); + } + $this->$foreignRef = $table->_belongsTo[$foreignRef]; + } + + /** + * __get Access properties - used for lazy loading + * + * @param mixed $name + * @access protected + * @return void + */ + function __get($name) + { + return $this->LoadRelations($name, '', -1. -1); + } + + function LoadRelations($name, $whereOrderBy, $offset=-1, $limit=-1) + { + $extras = array(); + if($offset >= 0) $extras['offset'] = $offset; + if($limit >= 0) $extras['limit'] = $limit; + $table =& $this->TableInfo(); + + if (strlen($whereOrderBy)) + if (!preg_match('/^[ \n\r]*AND/i',$whereOrderBy)) + if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i',$whereOrderBy)) + $whereOrderBy = 'AND '.$whereOrderBy; + + if(!empty($table->_belongsTo[$name])) + { + $obj = $table->_belongsTo[$name]; + $columnName = $obj->foreignKey; + if(empty($this->$columnName)) + $this->$name = null; + else + { + if(($k = reset($obj->TableInfo()->keys))) + $belongsToId = $k; + else + $belongsToId = 'id'; + + $arrayOfOne = + $obj->Find( + $belongsToId.'='.$this->$columnName.' '.$whereOrderBy, false, false, $extras); + $this->$name = $arrayOfOne[0]; + } + return $this->$name; + } + if(!empty($table->_hasMany[$name])) + { + $obj = $table->_hasMany[$name]; + if(($k = reset($table->keys))) + $hasManyId = $k; + else + $hasManyId = 'id'; + + $this->$name = + $obj->Find( + $obj->foreignKey.'='.$this->$hasManyId.' '.$whereOrderBy, false, false, $extras); + return $this->$name; + } + } + ////////////////////////////////// + + // update metadata + function UpdateActiveTable($pkeys=false,$forceUpdate=false) + { + global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS; + global $ADODB_ACTIVE_DEFVALS, $ADODB_FETCH_MODE; + + $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat]; + + $table = $this->_table; + $tables = $activedb->tables; + $tableat = $this->_tableat; + if (!$forceUpdate && !empty($tables[$tableat])) { + + $tobj = $tables[$tableat]; + foreach($tobj->flds as $name => $fld) { + if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) + $this->$name = $fld->default_value; + else + $this->$name = null; + } + return; + } + + $db = $activedb->db; + $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache'; + if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) { + $fp = fopen($fname,'r'); + @flock($fp, LOCK_SH); + $acttab = unserialize(fread($fp,100000)); + fclose($fp); + if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) { + // abs(rand()) randomizes deletion, reducing contention to delete/refresh file + // ideally, you should cache at least 32 secs + $activedb->tables[$table] = $acttab; + + //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname"); + return; + } else if ($db->debug) { + ADOConnection::outp("Refreshing cached active record file: $fname"); + } + } + $activetab = new ADODB_Active_Table(); + $activetab->name = $table; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + if ($db->fetchMode !== false) $savem = $db->SetFetchMode(false); + + $cols = $db->MetaColumns($table); + + if (isset($savem)) $db->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if (!$cols) { + $this->Error("Invalid table name: $table",'UpdateActiveTable'); + return false; + } + $fld = reset($cols); + if (!$pkeys) { + if (isset($fld->primary_key)) { + $pkeys = array(); + foreach($cols as $name => $fld) { + if (!empty($fld->primary_key)) $pkeys[] = $name; + } + } else + $pkeys = $this->GetPrimaryKeys($db, $table); + } + if (empty($pkeys)) { + $this->Error("No primary key found for table $table",'UpdateActiveTable'); + return false; + } + + $attr = array(); + $keys = array(); + + switch($ADODB_ASSOC_CASE) { + case 0: + foreach($cols as $name => $fldobj) { + $name = strtolower($name); + if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) + $this->$name = $fldobj->default_value; + else + $this->$name = null; + $attr[$name] = $fldobj; + } + foreach($pkeys as $k => $name) { + $keys[strtolower($name)] = strtolower($name); + } + break; + + case 1: + foreach($cols as $name => $fldobj) { + $name = strtoupper($name); + + if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) + $this->$name = $fldobj->default_value; + else + $this->$name = null; + $attr[$name] = $fldobj; + } + + foreach($pkeys as $k => $name) { + $keys[strtoupper($name)] = strtoupper($name); + } + break; + default: + foreach($cols as $name => $fldobj) { + $name = ($fldobj->name); + + if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) + $this->$name = $fldobj->default_value; + else + $this->$name = null; + $attr[$name] = $fldobj; + } + foreach($pkeys as $k => $name) { + $keys[$name] = $cols[$name]->name; + } + break; + } + + $activetab->keys = $keys; + $activetab->flds = $attr; + $activetab->updateColsCount(); + + if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) { + $activetab->_created = time(); + $s = serialize($activetab); + if (!function_exists('adodb_write_file')) include(ADODB_DIR.'/adodb-csvlib.inc.php'); + adodb_write_file($fname,$s); + } + if (isset($activedb->tables[$table])) { + $oldtab = $activedb->tables[$table]; + + if ($oldtab) $activetab->_belongsTo = $oldtab->_belongsTo; + if ($oldtab) $activetab->_hasMany = $oldtab->_hasMany; + } + $activedb->tables[$table] = $activetab; + } + + function GetPrimaryKeys(&$db, $table) + { + return $db->MetaPrimaryKeys($table); + } + + // error handler for both PHP4+5. + function Error($err,$fn) + { + global $_ADODB_ACTIVE_DBS; + + $fn = get_class($this).'::'.$fn; + $this->_lasterr = $fn.': '.$err; + + if ($this->_dbat < 0) $db = false; + else { + $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat]; + $db = $activedb->db; + } + + if (function_exists('adodb_throw')) { + if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false); + else adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db); + } else + if (!$db || $db->debug) ADOConnection::outp($this->_lasterr); + + } + + // return last error message + function ErrorMsg() + { + if (!function_exists('adodb_throw')) { + if ($this->_dbat < 0) $db = false; + else $db = $this->DB(); + + // last error could be database error too + if ($db && $db->ErrorMsg()) return $db->ErrorMsg(); + } + return $this->_lasterr; + } + + function ErrorNo() + { + if ($this->_dbat < 0) return -9999; // no database connection... + $db = $this->DB(); + + return (int) $db->ErrorNo(); + } + + + // retrieve ADOConnection from _ADODB_Active_DBs + function DB() + { + global $_ADODB_ACTIVE_DBS; + + if ($this->_dbat < 0) { + $false = false; + $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB"); + return $false; + } + $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat]; + $db = $activedb->db; + return $db; + } + + // retrieve ADODB_Active_Table + function &TableInfo() + { + global $_ADODB_ACTIVE_DBS; + + $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat]; + $table = $activedb->tables[$this->_tableat]; + return $table; + } + + + // I have an ON INSERT trigger on a table that sets other columns in the table. + // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook + function Reload() + { + $db =& $this->DB(); if (!$db) return false; + $table =& $this->TableInfo(); + $where = $this->GenWhere($db, $table); + return($this->Load($where)); + } + + + // set a numeric array (using natural table field ordering) as object properties + function Set(&$row) + { + global $ACTIVE_RECORD_SAFETY; + + $db = $this->DB(); + + if (!$row) { + $this->_saved = false; + return false; + } + + $this->_saved = true; + + $table = $this->TableInfo(); + $sizeofFlds = sizeof($table->flds); + $sizeofRow = sizeof($row); + if ($ACTIVE_RECORD_SAFETY && $table->_colsCount != $sizeofRow && $sizeofFlds != $sizeofRow) { + # + $bad_size = TRUE; + if($sizeofRow == 2 * $table->_colsCount || $sizeofRow == 2 * $sizeofFlds) { + // Only keep string keys + $keys = array_filter(array_keys($row), 'is_string'); + if (sizeof($keys) == sizeof($table->flds)) + $bad_size = FALSE; + } + if ($bad_size) { + $this->Error("Table structure of $this->_table has changed","Load"); + return false; + } + # + } + else + $keys = array_keys($row); + # + reset($keys); + $this->_original = array(); + foreach($table->flds as $name=>$fld) + { + $value = $row[current($keys)]; + $this->$name = $value; + $this->_original[] = $value; + if(!next($keys)) break; + } + $table =& $this->TableInfo(); + foreach($table->_belongsTo as $foreignTable) + { + $ft = $foreignTable->TableInfo(); + $propertyName = $ft->name; + foreach($ft->flds as $name=>$fld) + { + $value = $row[current($keys)]; + $foreignTable->$name = $value; + $foreignTable->_original[] = $value; + if(!next($keys)) break; + } + } + foreach($table->_hasMany as $foreignTable) + { + $ft = $foreignTable->TableInfo(); + foreach($ft->flds as $name=>$fld) + { + $value = $row[current($keys)]; + $foreignTable->$name = $value; + $foreignTable->_original[] = $value; + if(!next($keys)) break; + } + } + # + return true; + } + + // get last inserted id for INSERT + function LastInsertID(&$db,$fieldname) + { + if ($db->hasInsertID) + $val = $db->Insert_ID($this->_table,$fieldname); + else + $val = false; + + if (is_null($val) || $val === false) { + // this might not work reliably in multi-user environment + return $db->GetOne("select max(".$fieldname.") from ".$this->_table); + } + return $val; + } + + // quote data in where clause + function doquote(&$db, $val,$t) + { + switch($t) { + case 'D': + case 'T': + if (empty($val)) return 'null'; + + case 'C': + case 'X': + if (is_null($val)) return 'null'; + + if (strlen($val)>1 && + (strncmp($val,"'",1) != 0 || substr($val,strlen($val)-1,1) != "'")) { + return $db->qstr($val); + break; + } + default: + return $val; + break; + } + } + + // generate where clause for an UPDATE/SELECT + function GenWhere(&$db, &$table) + { + $keys = $table->keys; + $parr = array(); + + foreach($keys as $k) { + $f = $table->flds[$k]; + if ($f) { + $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type)); + } + } + return implode(' and ', $parr); + } + + + //------------------------------------------------------------ Public functions below + + function Load($where=null,$bindarr=false) + { + $db = $this->DB(); if (!$db) return false; + $this->_where = $where; + + $save = $db->SetFetchMode(ADODB_FETCH_NUM); + $qry = "select * from ".$this->_table; + $table =& $this->TableInfo(); + + if(($k = reset($table->keys))) + $hasManyId = $k; + else + $hasManyId = 'id'; + + foreach($table->_belongsTo as $foreignTable) + { + if(($k = reset($foreignTable->TableInfo()->keys))) + { + $belongsToId = $k; + } + else + { + $belongsToId = 'id'; + } + $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '. + $this->_table.'.'.$foreignTable->foreignKey.'='. + $foreignTable->_table.'.'.$belongsToId; + } + foreach($table->_hasMany as $foreignTable) + { + $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '. + $this->_table.'.'.$hasManyId.'='. + $foreignTable->_table.'.'.$foreignTable->foreignKey; + } + if($where) + $qry .= ' WHERE '.$where; + + // Simple case: no relations. Load row and return. + if((count($table->_hasMany) + count($table->_belongsTo)) < 1) + { + $row = $db->GetRow($qry,$bindarr); + if(!$row) + return false; + $db->SetFetchMode($save); + return $this->Set($row); + } + + // More complex case when relations have to be collated + $rows = $db->GetAll($qry,$bindarr); + if(!$rows) + return false; + $db->SetFetchMode($save); + if(count($rows) < 1) + return false; + $class = get_class($this); + $isFirstRow = true; + + if(($k = reset($this->TableInfo()->keys))) + $myId = $k; + else + $myId = 'id'; + $index = 0; $found = false; + /** @todo Improve by storing once and for all in table metadata */ + /** @todo Also re-use info for hasManyId */ + foreach($this->TableInfo()->flds as $fld) + { + if($fld->name == $myId) + { + $found = true; + break; + } + $index++; + } + if(!$found) + $this->outp_throw("Unable to locate key $myId for $class in Load()",'Load'); + + foreach($rows as $row) + { + $rowId = intval($row[$index]); + if($rowId > 0) + { + if($isFirstRow) + { + $isFirstRow = false; + if(!$this->Set($row)) + return false; + } + $obj = new $class($table,false,$db); + $obj->Set($row); + // TODO Copy/paste code below: bad! + if(count($table->_hasMany) > 0) + { + foreach($table->_hasMany as $foreignTable) + { + $foreignName = $foreignTable->foreignName; + if(!empty($obj->$foreignName)) + { + if(!is_array($this->$foreignName)) + { + $foreignObj = $this->$foreignName; + $this->$foreignName = array(clone($foreignObj)); + } + else + { + $foreignObj = $obj->$foreignName; + array_push($this->$foreignName, clone($foreignObj)); + } + } + } + } + if(count($table->_belongsTo) > 0) + { + foreach($table->_belongsTo as $foreignTable) + { + $foreignName = $foreignTable->foreignName; + if(!empty($obj->$foreignName)) + { + if(!is_array($this->$foreignName)) + { + $foreignObj = $this->$foreignName; + $this->$foreignName = array(clone($foreignObj)); + } + else + { + $foreignObj = $obj->$foreignName; + array_push($this->$foreignName, clone($foreignObj)); + } + } + } + } + } + } + return true; + } + + // false on error + function Save() + { + if ($this->_saved) $ok = $this->Update(); + else $ok = $this->Insert(); + + return $ok; + } + + // CFR: Sometimes we may wish to consider that an object is not to be replaced but inserted. + // Sample use case: an 'undo' command object (after a delete()) + function Dirty() + { + $this->_saved = false; + } + + // false on error + function Insert() + { + $db = $this->DB(); if (!$db) return false; + $cnt = 0; + $table = $this->TableInfo(); + + $valarr = array(); + $names = array(); + $valstr = array(); + + foreach($table->flds as $name=>$fld) { + $val = $this->$name; + if(!is_null($val) || !array_key_exists($name, $table->keys)) { + $valarr[] = $val; + $names[] = $name; + $valstr[] = $db->Param($cnt); + $cnt += 1; + } + } + + if (empty($names)){ + foreach($table->flds as $name=>$fld) { + $valarr[] = null; + $names[] = $name; + $valstr[] = $db->Param($cnt); + $cnt += 1; + } + } + $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')'; + $ok = $db->Execute($sql,$valarr); + + if ($ok) { + $this->_saved = true; + $autoinc = false; + foreach($table->keys as $k) { + if (is_null($this->$k)) { + $autoinc = true; + break; + } + } + if ($autoinc && sizeof($table->keys) == 1) { + $k = reset($table->keys); + $this->$k = $this->LastInsertID($db,$k); + } + } + + $this->_original = $valarr; + return !empty($ok); + } + + function Delete() + { + $db = $this->DB(); if (!$db) return false; + $table = $this->TableInfo(); + + $where = $this->GenWhere($db,$table); + $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where; + $ok = $db->Execute($sql); + + return $ok ? true : false; + } + + // returns an array of active record objects + function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array()) + { + $db = $this->DB(); if (!$db || empty($this->_table)) return false; + $table =& $this->TableInfo(); + $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra, + array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany)); + return $arr; + } + + // CFR: In introduced this method to ensure that inner workings are not disturbed by + // subclasses...for instance when GetActiveRecordsClass invokes Find() + // Why am I not invoking parent::Find? + // Shockingly because I want to preserve PHP4 compatibility. + function packageFind($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array()) + { + $db = $this->DB(); if (!$db || empty($this->_table)) return false; + $table =& $this->TableInfo(); + $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra, + array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany)); + return $arr; + } + + // returns 0 on error, 1 on update, 2 on insert + function Replace() + { + global $ADODB_ASSOC_CASE; + + $db = $this->DB(); if (!$db) return false; + $table = $this->TableInfo(); + + $pkey = $table->keys; + + foreach($table->flds as $name=>$fld) { + $val = $this->$name; + /* + if (is_null($val)) { + if (isset($fld->not_null) && $fld->not_null) { + if (isset($fld->default_value) && strlen($fld->default_value)) continue; + else { + $this->Error("Cannot update null into $name","Replace"); + return false; + } + } + }*/ + if (is_null($val) && !empty($fld->auto_increment)) { + continue; + } + $t = $db->MetaType($fld->type); + $arr[$name] = $this->doquote($db,$val,$t); + $valarr[] = $val; + } + + if (!is_array($pkey)) $pkey = array($pkey); + + + if ($ADODB_ASSOC_CASE == 0) + foreach($pkey as $k => $v) + $pkey[$k] = strtolower($v); + elseif ($ADODB_ASSOC_CASE == 1) + foreach($pkey as $k => $v) + $pkey[$k] = strtoupper($v); + + $ok = $db->Replace($this->_table,$arr,$pkey); + if ($ok) { + $this->_saved = true; // 1= update 2=insert + if ($ok == 2) { + $autoinc = false; + foreach($table->keys as $k) { + if (is_null($this->$k)) { + $autoinc = true; + break; + } + } + if ($autoinc && sizeof($table->keys) == 1) { + $k = reset($table->keys); + $this->$k = $this->LastInsertID($db,$k); + } + } + + $this->_original = $valarr; + } + return $ok; + } + + // returns 0 on error, 1 on update, -1 if no change in data (no update) + function Update() + { + $db = $this->DB(); if (!$db) return false; + $table = $this->TableInfo(); + + $where = $this->GenWhere($db, $table); + + if (!$where) { + $this->error("Where missing for table $table", "Update"); + return false; + } + $valarr = array(); + $neworig = array(); + $pairs = array(); + $i = -1; + $cnt = 0; + foreach($table->flds as $name=>$fld) { + $i += 1; + $val = $this->$name; + $neworig[] = $val; + + if (isset($table->keys[$name])) { + continue; + } + + if (is_null($val)) { + if (isset($fld->not_null) && $fld->not_null) { + if (isset($fld->default_value) && strlen($fld->default_value)) continue; + else { + $this->Error("Cannot set field $name to NULL","Update"); + return false; + } + } + } + + if (isset($this->_original[$i]) && $val == $this->_original[$i]) { + continue; + } + $valarr[] = $val; + $pairs[] = $name.'='.$db->Param($cnt); + $cnt += 1; + } + + + if (!$cnt) return -1; + $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where; + $ok = $db->Execute($sql,$valarr); + if ($ok) { + $this->_original = $neworig; + return 1; + } + return 0; + } + + function GetAttributeNames() + { + $table = $this->TableInfo(); + if (!$table) return false; + return array_keys($table->flds); + } + +}; + +function adodb_GetActiveRecordsClass(&$db, $class, $tableObj,$whereOrderBy,$bindarr, $primkeyArr, + $extra, $relations) +{ + global $_ADODB_ACTIVE_DBS; + + if (empty($extra['loading'])) $extra['loading'] = ADODB_LAZY_AR; + + $save = $db->SetFetchMode(ADODB_FETCH_NUM); + $table = &$tableObj->_table; + $tableInfo =& $tableObj->TableInfo(); + if(($k = reset($tableInfo->keys))) + $myId = $k; + else + $myId = 'id'; + $index = 0; $found = false; + /** @todo Improve by storing once and for all in table metadata */ + /** @todo Also re-use info for hasManyId */ + foreach($tableInfo->flds as $fld) + { + if($fld->name == $myId) + { + $found = true; + break; + } + $index++; + } + if(!$found) + $db->outp_throw("Unable to locate key $myId for $class in GetActiveRecordsClass()",'GetActiveRecordsClass'); + + $qry = "select * from ".$table; + if(ADODB_JOIN_AR == $extra['loading']) + { + if(!empty($relations['belongsTo'])) + { + foreach($relations['belongsTo'] as $foreignTable) + { + if(($k = reset($foreignTable->TableInfo()->keys))) + { + $belongsToId = $k; + } + else + { + $belongsToId = 'id'; + } + + $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '. + $table.'.'.$foreignTable->foreignKey.'='. + $foreignTable->_table.'.'.$belongsToId; + } + } + if(!empty($relations['hasMany'])) + { + if(empty($relations['foreignName'])) + $db->outp_throw("Missing foreignName is relation specification in GetActiveRecordsClass()",'GetActiveRecordsClass'); + if(($k = reset($tableInfo->keys))) + $hasManyId = $k; + else + $hasManyId = 'id'; + + foreach($relations['hasMany'] as $foreignTable) + { + $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '. + $table.'.'.$hasManyId.'='. + $foreignTable->_table.'.'.$foreignTable->foreignKey; + } + } + } + if (!empty($whereOrderBy)) + $qry .= ' WHERE '.$whereOrderBy; + if(isset($extra['limit'])) + { + $rows = false; + if(isset($extra['offset'])) { + $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset']); + } else { + $rs = $db->SelectLimit($qry, $extra['limit']); + } + if ($rs) { + while (!$rs->EOF) { + $rows[] = $rs->fields; + $rs->MoveNext(); + } + } + } else + $rows = $db->GetAll($qry,$bindarr); + + $db->SetFetchMode($save); + + $false = false; + + if ($rows === false) { + return $false; + } + + + if (!isset($_ADODB_ACTIVE_DBS)) { + include(ADODB_DIR.'/adodb-active-record.inc.php'); + } + if (!class_exists($class)) { + $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass'); + return $false; + } + $uniqArr = array(); // CFR Keep track of records for relations + $arr = array(); + // arrRef will be the structure that knows about our objects. + // It is an associative array. + // We will, however, return arr, preserving regular 0.. order so that + // obj[0] can be used by app developpers. + $arrRef = array(); + $bTos = array(); // Will store belongTo's indices if any + foreach($rows as $row) { + + $obj = new $class($table,$primkeyArr,$db); + if ($obj->ErrorNo()){ + $db->_errorMsg = $obj->ErrorMsg(); + return $false; + } + $obj->Set($row); + // CFR: FIXME: Insane assumption here: + // If the first column returned is an integer, then it's a 'id' field + // And to make things a bit worse, I use intval() rather than is_int() because, in fact, + // $row[0] is not an integer. + // + // So, what does this whole block do? + // When relationships are found, we perform JOINs. This is fast. But not accurate: + // instead of returning n objects with their n' associated cousins, + // we get n*n' objects. This code fixes this. + // Note: to-many relationships mess around with the 'limit' parameter + $rowId = intval($row[$index]); + + if(ADODB_WORK_AR == $extra['loading']) + { + $arrRef[$rowId] = $obj; + $arr[] = &$arrRef[$rowId]; + if(!isset($indices)) + $indices = $rowId; + else + $indices .= ','.$rowId; + if(!empty($relations['belongsTo'])) + { + foreach($relations['belongsTo'] as $foreignTable) + { + $foreignTableRef = $foreignTable->foreignKey; + // First array: list of foreign ids we are looking for + if(empty($bTos[$foreignTableRef])) + $bTos[$foreignTableRef] = array(); + // Second array: list of ids found + if(empty($obj->$foreignTableRef)) + continue; + if(empty($bTos[$foreignTableRef][$obj->$foreignTableRef])) + $bTos[$foreignTableRef][$obj->$foreignTableRef] = array(); + $bTos[$foreignTableRef][$obj->$foreignTableRef][] = $obj; + } + } + continue; + } + + if($rowId>0) + { + if(ADODB_JOIN_AR == $extra['loading']) + { + $isNewObj = !isset($uniqArr['_'.$row[0]]); + if($isNewObj) + $uniqArr['_'.$row[0]] = $obj; + + // TODO Copy/paste code below: bad! + if(!empty($relations['hasMany'])) + { + foreach($relations['hasMany'] as $foreignTable) + { + $foreignName = $foreignTable->foreignName; + if(!empty($obj->$foreignName)) + { + $masterObj = &$uniqArr['_'.$row[0]]; + // Assumption: this property exists in every object since they are instances of the same class + if(!is_array($masterObj->$foreignName)) + { + // Pluck! + $foreignObj = $masterObj->$foreignName; + $masterObj->$foreignName = array(clone($foreignObj)); + } + else + { + // Pluck pluck! + $foreignObj = $obj->$foreignName; + array_push($masterObj->$foreignName, clone($foreignObj)); + } + } + } + } + if(!empty($relations['belongsTo'])) + { + foreach($relations['belongsTo'] as $foreignTable) + { + $foreignName = $foreignTable->foreignName; + if(!empty($obj->$foreignName)) + { + $masterObj = &$uniqArr['_'.$row[0]]; + // Assumption: this property exists in every object since they are instances of the same class + if(!is_array($masterObj->$foreignName)) + { + // Pluck! + $foreignObj = $masterObj->$foreignName; + $masterObj->$foreignName = array(clone($foreignObj)); + } + else + { + // Pluck pluck! + $foreignObj = $obj->$foreignName; + array_push($masterObj->$foreignName, clone($foreignObj)); + } + } + } + } + if(!$isNewObj) + unset($obj); // We do not need this object itself anymore and do not want it re-added to the main array + } + else if(ADODB_LAZY_AR == $extra['loading']) + { + // Lazy loading: we need to give AdoDb a hint that we have not really loaded + // anything, all the while keeping enough information on what we wish to load. + // Let's do this by keeping the relevant info in our relationship arrays + // but get rid of the actual properties. + // We will then use PHP's __get to load these properties on-demand. + if(!empty($relations['hasMany'])) + { + foreach($relations['hasMany'] as $foreignTable) + { + $foreignName = $foreignTable->foreignName; + if(!empty($obj->$foreignName)) + { + unset($obj->$foreignName); + } + } + } + if(!empty($relations['belongsTo'])) + { + foreach($relations['belongsTo'] as $foreignTable) + { + $foreignName = $foreignTable->foreignName; + if(!empty($obj->$foreignName)) + { + unset($obj->$foreignName); + } + } + } + } + } + + if(isset($obj)) + $arr[] = $obj; + } + + if(ADODB_WORK_AR == $extra['loading']) + { + // The best of both worlds? + // Here, the number of queries is constant: 1 + n*relationship. + // The second query will allow us to perform a good join + // while preserving LIMIT etc. + if(!empty($relations['hasMany'])) + { + foreach($relations['hasMany'] as $foreignTable) + { + $foreignName = $foreignTable->foreignName; + $className = ucfirst($foreignTable->_singularize($foreignName)); + $obj = new $className(); + $dbClassRef = $foreignTable->foreignKey; + $objs = $obj->packageFind($dbClassRef.' IN ('.$indices.')'); + foreach($objs as $obj) + { + if(!is_array($arrRef[$obj->$dbClassRef]->$foreignName)) + $arrRef[$obj->$dbClassRef]->$foreignName = array(); + array_push($arrRef[$obj->$dbClassRef]->$foreignName, $obj); + } + } + + } + if(!empty($relations['belongsTo'])) + { + foreach($relations['belongsTo'] as $foreignTable) + { + $foreignTableRef = $foreignTable->foreignKey; + if(empty($bTos[$foreignTableRef])) + continue; + if(($k = reset($foreignTable->TableInfo()->keys))) + { + $belongsToId = $k; + } + else + { + $belongsToId = 'id'; + } + $origObjsArr = $bTos[$foreignTableRef]; + $bTosString = implode(',', array_keys($bTos[$foreignTableRef])); + $foreignName = $foreignTable->foreignName; + $className = ucfirst($foreignTable->_singularize($foreignName)); + $obj = new $className(); + $objs = $obj->packageFind($belongsToId.' IN ('.$bTosString.')'); + foreach($objs as $obj) + { + foreach($origObjsArr[$obj->$belongsToId] as $idx=>$origObj) + { + $origObj->$foreignName = $obj; + } + } + } + } + } + + return $arr; +} +?> diff --git a/lib/adodb/adodb-csvlib.inc.php b/lib/adodb/adodb-csvlib.inc.php index 2872f2042d..9c5385a747 100644 --- a/lib/adodb/adodb-csvlib.inc.php +++ b/lib/adodb/adodb-csvlib.inc.php @@ -8,7 +8,7 @@ $ADODB_INCLUDED_CSV = 1; /* - V5.04a 25 Mar 2008 (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved. + V5.08 6 Apr 2009 (c) 2000-2009 John Lim (jlim#natsoft.com). All rights reserved. Released under both BSD license and Lesser GPL library license. Whenever there is any discrepancy between the two licenses, the BSD license will take precedence. See License.txt. @@ -287,7 +287,7 @@ $ADODB_INCLUDED_CSV = 1; fclose($fd); if ($ok) { - chmod($tmpname,0644); + @chmod($tmpname,0644); // the tricky moment @unlink($filename); if (!@rename($tmpname,$filename)) { @@ -305,7 +305,7 @@ $ADODB_INCLUDED_CSV = 1; if (fwrite( $fd, $contents )) $ok = true; else $ok = false; fclose($fd); - chmod($filename,0644); + @chmod($filename,0644); }else { fclose($fd); if ($debug)ADOConnection::outp( " Failed acquiring lock for $filename
\n"); diff --git a/lib/adodb/adodb-datadict.inc.php b/lib/adodb/adodb-datadict.inc.php index d98d551154..c9ef4e7b64 100644 --- a/lib/adodb/adodb-datadict.inc.php +++ b/lib/adodb/adodb-datadict.inc.php @@ -1,7 +1,7 @@ 'C', 'INTERVAL' => 'C', # Postgres 'MACADDR' => 'C', # postgres + 'VAR_STRING' => 'C', # mysql ## 'LONGCHAR' => 'X', 'TEXT' => 'X', @@ -257,6 +258,7 @@ class ADODB_DataDict { 'TIMESTAMP' => 'T', 'DATETIME' => 'T', 'TIMESTAMPTZ' => 'T', + 'SMALLDATETIME' => 'T', 'T' => 'T', 'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql ## @@ -812,7 +814,7 @@ class ADODB_DataDict { // return string must begin with space - function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint) + function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) { $suffix = ''; if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; @@ -917,7 +919,7 @@ class ADODB_DataDict { 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. */ - function ChangeTableSQL($tablename, $flds, $tableoptions = false) + function ChangeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFlds=false) { global $ADODB_FETCH_MODE; @@ -950,13 +952,15 @@ class ADODB_DataDict { $obj = $cols[$k]; if (isset($obj->not_null) && $obj->not_null) $v = str_replace('NOT NULL','',$v); - + if (isset($obj->auto_increment) && $obj->auto_increment && empty($v['AUTOINCREMENT'])) + $v = str_replace('AUTOINCREMENT','',$v); + $c = $cols[$k]; $ml = $c->max_length; $mt = $this->MetaType($c->type,$ml); if ($ml == -1) $ml = ''; if ($mt == 'X') $ml = $v['SIZE']; - if (($mt != $v['TYPE']) || $ml != $v['SIZE']) { + if (($mt != $v['TYPE']) || $ml != $v['SIZE'] || (isset($v['AUTOINCREMENT']) && $v['AUTOINCREMENT'] != $obj->auto_increment)) { $holdflds[$k] = $v; } } else { @@ -993,6 +997,11 @@ class ADODB_DataDict { } } + if ($dropOldFlds) { + foreach ( $cols as $id => $v ) + if ( !isset($lines[$id]) ) + $sql[] = $alter . $this->dropCol . ' ' . $v->name; + } return $sql; } } // class diff --git a/lib/adodb/adodb-error.inc.php b/lib/adodb/adodb-error.inc.php index 72779ab2cb..ae0b9e3543 100644 --- a/lib/adodb/adodb-error.inc.php +++ b/lib/adodb/adodb-error.inc.php @@ -1,6 +1,6 @@ null2null)!=0) { + } else if ($autoQuote && /*!is_numeric($v) /*and strncmp($v,"'",1) !== 0 -- sql injection risk*/ strcasecmp($v,$zthis->null2null)!=0) { $v = $zthis->qstr($v); $fieldArray[$k] = $v; } @@ -159,7 +159,7 @@ function _adodb_replace(&$zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_ } else $uSet .= ",$k=$v"; } - + $where = false; foreach ($keyCol as $v) { if (isset($fieldArray[$v])) { @@ -542,7 +542,7 @@ function _adodb_pageexecute_all_rows(&$zthis, $sql, $nrows, $page, return $rsreturn; } -// Iv�n Oliva version +// Iván Oliva version function _adodb_pageexecute_no_last_page(&$zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0) { @@ -999,15 +999,15 @@ function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields, case "T": $val = $zthis->DBTimeStamp($arrFields[$fname]); - break; - - case "F": //Floating point number // Moodle added + break; + + case "F": //Floating point number // Moodle added case "N": $val = $arrFields[$fname]; if (!is_numeric($val)) $val = str_replace(',', '.', (float)$val); break; - case "L": //Integer field suitable for storing booleans (0 or 1) // Moodle added + case "L": //Integer field suitable for storing booleans (0 or 1) // Moodle added case "I": case "R": $val = $arrFields[$fname]; @@ -1055,11 +1055,13 @@ function _adodb_debug_execute(&$zthis, $sql, $inputarr) $ss = ''.htmlspecialchars($ss).''; } if ($zthis->debug === -1) - ADOConnection::outp( "
\n($dbt): ".htmlspecialchars($sqlTxt)."   $ss\n
\n",false); - else - ADOConnection::outp( "
\n($dbt): ".htmlspecialchars($sqlTxt)."   $ss\n
\n",false); + ADOConnection::outp( "
\n($dbt): ".htmlspecialchars($sqlTxt)."   $ss\n
\n",false); /// Moodle XHTML + else if ($zthis->debug !== -99) + ADOConnection::outp( "
\n($dbt): ".htmlspecialchars($sqlTxt)."   $ss\n
\n",false); /// Moodle XHTML } else { - ADOConnection::outp("-----\n($dbt): ".$sqlTxt."\n-----\n",false); + $ss = "\n ".$ss; + if ($zthis->debug !== -99) + ADOConnection::outp("-----
\n($dbt): ".$sqlTxt." $ss\n-----
\n",false); /// Moodle XHTML } $qID = $zthis->_query($sql,$inputarr); @@ -1070,10 +1072,21 @@ function _adodb_debug_execute(&$zthis, $sql, $inputarr) */ if ($zthis->databaseType == 'mssql') { // ErrorNo is a slow function call in mssql, and not reliable in PHP 4.0.6 + if($emsg = $zthis->ErrorMsg()) { - if ($err = $zthis->ErrorNo()) ADOConnection::outp($err.': '.$emsg); + if ($err = $zthis->ErrorNo()) { + if ($zthis->debug === -99) + ADOConnection::outp( "
\n($dbt): ".htmlspecialchars($sqlTxt)."   $ss\n
\n",false); /// Moodle XHTML + + ADOConnection::outp($err.': '.$emsg); + } } } else if (!$qID) { + + if ($zthis->debug === -99) + if ($inBrowser) ADOConnection::outp( "
\n($dbt): ".htmlspecialchars($sqlTxt)."   $ss\n
\n",false); /// Moodle XHTML + else ADOConnection::outp("-----
\n($dbt): ".$sqlTxt."$ss\n-----
\n",false); + ADOConnection::outp($zthis->ErrorNo() .': '. $zthis->ErrorMsg()); } @@ -1082,20 +1095,18 @@ function _adodb_debug_execute(&$zthis, $sql, $inputarr) } # pretty print the debug_backtrace function -function _adodb_backtrace($printOrArr=true,$levels=9999,$skippy=0) +function _adodb_backtrace($printOrArr=true,$levels=9999,$skippy=0,$ishtml=null) { if (!function_exists('debug_backtrace')) return ''; - $html = (isset($_SERVER['HTTP_USER_AGENT'])); -// moodle change start - see readme_moodle.txt - $fmt = ($html) ? " %% line %4d, file: %s" : "%% line %4d, file: %s"; -// moodle change end + if ($ishtml === null) $html = (isset($_SERVER['HTTP_USER_AGENT'])); + else $html = $ishtml; + + $fmt = ($html) ? " %% line %4d, file: %s" : "%% line %4d, file: %s"; $MAXSTRLEN = 128; -// moodle change start - see readme_moodle.txt - $s = ($html) ? '
' : '';
-// moodle change end
+	$s = ($html) ? '
' : '';
 	
 	if (is_array($printOrArr)) $traceArr = $printOrArr;
 	else $traceArr = debug_backtrace();
@@ -1121,7 +1132,7 @@ function _adodb_backtrace($printOrArr=true,$levels=9999,$skippy=0)
 			else if (is_bool($v)) $args[] = $v ? 'true' : 'false';
 			else {
 				$v = (string) @$v;
-				$str = htmlspecialchars(substr($v,0,$MAXSTRLEN));
+				$str = htmlspecialchars(str_replace(array("\r","\n"),' ',substr($v,0,$MAXSTRLEN)));
 				if (strlen($v) > $MAXSTRLEN) $str .= '...';
 				$args[] = $str;
 			}
@@ -1181,4 +1192,4 @@ function _adodb_find_from($sql)
 }
 */
 
-?>
\ No newline at end of file
+?>
diff --git a/lib/adodb/adodb-memcache.lib.inc.php b/lib/adodb/adodb-memcache.lib.inc.php
index a4b7e7531c..f50dbdb942 100644
--- a/lib/adodb/adodb-memcache.lib.inc.php
+++ b/lib/adodb/adodb-memcache.lib.inc.php
@@ -6,113 +6,185 @@ if (!defined('ADODB_DIR')) die();
 global $ADODB_INCLUDED_MEMCACHE;
 $ADODB_INCLUDED_MEMCACHE = 1;
 
+global $ADODB_INCLUDED_CSV;
+if (empty($ADODB_INCLUDED_CSV)) include(ADODB_DIR.'/adodb-csvlib.inc.php');
+
 /* 
 
-  V4.90 8 June 2006  (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved.
+  V5.06 16 Oct 2008  (c) 2000-2009 John Lim (jlim#natsoft.com). All rights reserved.
   Released under both BSD license and Lesser GPL library license. 
   Whenever there is any discrepancy between the two licenses, 
   the BSD license will take precedence. See License.txt. 
   Set tabs to 4 for best viewing.
   
   Latest version is available at http://adodb.sourceforge.net
+
+Usage:
+  
+$db = NewADOConnection($driver);
+$db->memCache = true; /// should we use memCache instead of caching in files
+$db->memCacheHost = array($ip1, $ip2, $ip3);
+$db->memCachePort = 11211; /// this is default memCache port
+$db->memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib)
+
+$db->Connect(...);
+$db->CacheExecute($sql);
+  
+  Note the memcache class is shared by all connections, is created during the first call to Connect/PConnect.
   
+  Class instance is stored in $ADODB_CACHE
 */
 
-	function getmemcache($key,&$err, $timeout=0, $host, $port)
-	{
-		$false = false;
-		$err = false;
-
-		if (!function_exists('memcache_pconnect')) {
-			$err = 'Memcache module PECL extension not found!';
-			return $false;
+	class ADODB_Cache_MemCache {
+		var $createdir = false; // create caching directory structure?
+		
+		//-----------------------------
+		// 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 ADODB_Cache_MemCache(&$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)
+		{
+			if (!function_exists('memcache_pconnect')) {
+				$err = 'Memcache module PECL extension not found!';
+				return false;
+			}
 
-		$memcache = new Memcache;
-		if (!@$memcache->pconnect($host, $port)) {
-			$err = 'Can\'t connect to memcache server on: '.$host.':'.$port;
-			return $false;
+			$memcache = new MemCache;
+			
+			if (!is_array($this->hosts)) $this->hosts = array($hosts);
+		
+			$failcnt = 0;
+			foreach($this->hosts as $host) {
+				if (!@$memcache->addServer($host,$this->port,true)) {
+					$failcnt += 1;
+				}
+			}
+			if ($failcnt == sizeof($this->hosts)) {
+				$err = 'Can\'t connect to any memcache server';
+				return false;
+			}
+			$this->_connected = true;
+			$this->_memcache = $memcache;
+			return true;
 		}
-
-		$rs = $memcache->get($key);
-		if (!$rs) {
-			$err = 'Item with such key doesn\'t exists on the memcached server.';
-			return $false;
+		
+		// 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;
+			
+			if (!$this->_memcache->set($filename, $contents, $this->compress, $secs2cache)) {
+				if ($debug) ADOConnection::outp(" Failed to save data at the memcached server!
\n"); + return false; + } + + return true; } - - $tdiff = intval($rs->timeCreated+$timeout - 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"; + + // 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 exists on the memcached 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 + + $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; - } - break; - default: - $err = "Timeout 0"; - return $false; + } } + return $rs; } - return $rs; - } - - function putmemcache($key, $rs, $host, $port, $compress, $debug=false) - { - $false = false; - $true = true; - - if (!function_exists('memcache_pconnect')) { - if ($debug) ADOConnection::outp(" Memcache module PECL extension not found!
\n"); - return $false; + + function flushall($debug=false) + { + if (!$this->_connected) { + $err = ''; + if (!$this->connect($err) && $debug) ADOConnection::outp($err); + } + if (!$this->_memcache) return false; + + $del = $this->_memcache->flush(); + + if ($debug) + if (!$del) ADOConnection::outp("flushall: failed!
\n"); + else ADOConnection::outp("flushall: succeeded!
\n"); + + return $del; } - - $memcache = new Memcache; - if (!@$memcache->pconnect($host, $port)) { - if ($debug) ADOConnection::outp(" Can't connect to memcache server on: $host:$port
\n"); - return $false; + + function flushcache($filename, $debug=false) + { + if (!$this->_connected) { + $err = ''; + if (!$this->connect($err) && $debug) ADOConnection::outp($err); + } + if (!$this->_memcache) return false; + + $del = $this->_memcache->delete($filename); + + if ($debug) + if (!$del) ADOConnection::outp("flushcache: $key entry doesn't exist on memcached server!
\n"); + else ADOConnection::outp("flushcache: $key entry flushed from memcached server!
\n"); + + return $del; } - - $rs->timeCreated = time(); - if (!$memcache->set($key, $rs, $compress, 0)) { - if ($debug) ADOConnection::outp(" Failed to save data at the memcached server!
\n"); - return $false; + + // not used for memcache + function createdir($dir, $hash) + { + return true; } - return $true; } - function flushmemcache($key=false, $host, $port, $debug=false) - { - if (!function_exists('memcache_pconnect')) { - if ($debug) ADOConnection::outp(" Memcache module PECL extension not found!
\n"); - return; - } - - $memcache = new Memcache; - if (!@$memcache->pconnect($host, $port)) { - if ($debug) ADOConnection::outp(" Can't connect to memcache server on: $host:$port
\n"); - return; - } - - if ($key) { - if (!$memcache->delete($key)) { - if ($debug) ADOConnection::outp("CacheFlush: $key entery doesn't exist on memcached server!
\n"); - } else { - if ($debug) ADOConnection::outp("CacheFlush: $key entery flushed from memcached server!
\n"); - } - } else { - if (!$memcache->flush()) { - if ($debug) ADOConnection::outp("CacheFlush: Failure flushing all enteries from memcached server!
\n"); - } else { - if ($debug) ADOConnection::outp("CacheFlush: All enteries flushed from memcached server!
\n"); - } - } - return; - } -?> +?> \ No newline at end of file diff --git a/lib/adodb/adodb-pager.inc.php b/lib/adodb/adodb-pager.inc.php index 16acee365f..f542c3ccb6 100644 --- a/lib/adodb/adodb-pager.inc.php +++ b/lib/adodb/adodb-pager.inc.php @@ -1,7 +1,7 @@ sql = $sql; $this->id = $id; diff --git a/lib/adodb/adodb-pear.inc.php b/lib/adodb/adodb-pear.inc.php index 60696c5cca..6563704558 100644 --- a/lib/adodb/adodb-pear.inc.php +++ b/lib/adodb/adodb-pear.inc.php @@ -1,6 +1,6 @@ fnExecute = false; - $t0 = microtime(); + $a0 = microtime(true); $rs = $connx->Execute($sql,$inputarr); - $t1 = microtime(); + $a1 = microtime(true); if (!empty($connx->_logsql) && (empty($connx->_logsqlErrors) || !$rs)) { global $ADODB_LOG_CONN; @@ -91,12 +91,6 @@ function adodb_log_sql(&$connx,$sql,$inputarr) $conn->_logsql = false; // disable logsql error simulation $dbT = $conn->databaseType; - $a0 = split(' ',$t0); - $a0 = (float)$a0[1]+(float)$a0[0]; - - $a1 = split(' ',$t1); - $a1 = (float)$a1[1]+(float)$a1[0]; - $time = $a1 - $a0; if (!$rs) { @@ -117,9 +111,9 @@ function adodb_log_sql(&$connx,$sql,$inputarr) } if (isset($_SERVER['HTTP_HOST'])) { $tracer .= '
'.$_SERVER['HTTP_HOST']; - if (isset($_SERVER['PHP_SELF'])) $tracer .= $_SERVER['PHP_SELF']; + if (isset($_SERVER['PHP_SELF'])) $tracer .= htmlspecialchars($_SERVER['PHP_SELF']); } else - if (isset($_SERVER['PHP_SELF'])) $tracer .= '
'.$_SERVER['PHP_SELF']; + if (isset($_SERVER['PHP_SELF'])) $tracer .= '
'.htmlspecialchars($_SERVER['PHP_SELF']); //$tracer .= (string) adodb_backtrace(false); $tracer = (string) substr($tracer,0,500); @@ -264,7 +258,7 @@ processes 69293 */ // Algorithm is taken from - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/example__obtaining_raw_performance_data.asp + // http://social.technet.microsoft.com/Forums/en-US/winservergen/thread/414b0e1b-499c-411e-8a02-6a12e339c0f1/ if (strncmp(PHP_OS,'WIN',3)==0) { if (PHP_VERSION == '5.0.0') return false; if (PHP_VERSION == '5.0.1') return false; @@ -272,14 +266,33 @@ processes 69293 if (PHP_VERSION == '5.0.3') return false; if (PHP_VERSION == '4.3.10') return false; # see http://bugs.php.net/bug.php?id=31737 - @$c = new COM("WinMgmts:{impersonationLevel=impersonate}!Win32_PerfRawData_PerfOS_Processor.Name='_Total'"); - if (!$c) return false; + static $FAIL = false; + if ($FAIL) return false; + + $objName = "winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\CIMV2"; + $myQuery = "SELECT * FROM Win32_PerfFormattedData_PerfOS_Processor WHERE Name = '_Total'"; + + try { + @$objWMIService = new COM($objName); + if (!$objWMIService) { + $FAIL = true; + return false; + } + + $info[0] = -1; + $info[1] = 0; + $info[2] = 0; + $info[3] = 0; + foreach($objWMIService->ExecQuery($myQuery) as $objItem) { + $info[0] = $objItem->PercentProcessorTime(); + } + + } catch(Exception $e) { + $FAIL = true; + echo $e->getMessage(); + return false; + } - $info[0] = $c->PercentProcessorTime; - $info[1] = 0; - $info[2] = 0; - $info[3] = $c->TimeStamp_Sys100NS; - //print_r($info); return $info; } @@ -342,27 +355,26 @@ Committed_AS: 348732 kB { $info = $this->_CPULoad(); if (!$info) return false; - - if (empty($this->_lastLoad)) { - sleep(1); - $this->_lastLoad = $info; - $info = $this->_CPULoad(); - } - - $last = $this->_lastLoad; - $this->_lastLoad = $info; - - $d_user = $info[0] - $last[0]; - $d_nice = $info[1] - $last[1]; - $d_system = $info[2] - $last[2]; - $d_idle = $info[3] - $last[3]; - //printf("Delta - User: %f Nice: %f System: %f Idle: %f
",$d_user,$d_nice,$d_system,$d_idle); - if (strncmp(PHP_OS,'WIN',3)==0) { - if ($d_idle < 1) $d_idle = 1; - return 100*(1-$d_user/$d_idle); + return (integer) $info[0]; }else { + if (empty($this->_lastLoad)) { + sleep(1); + $this->_lastLoad = $info; + $info = $this->_CPULoad(); + } + + $last = $this->_lastLoad; + $this->_lastLoad = $info; + + $d_user = $info[0] - $last[0]; + $d_nice = $info[1] - $last[1]; + $d_system = $info[2] - $last[2]; + $d_idle = $info[3] - $last[3]; + + //printf("Delta - User: %f Nice: %f System: %f Idle: %f
",$d_user,$d_nice,$d_system,$d_idle); + $total=$d_user+$d_nice+$d_system+$d_idle; if ($total<1) $total=1; return 100*($d_user+$d_nice+$d_system)/$total; @@ -727,8 +739,9 @@ Committed_AS: 348732 kB echo $this->CheckMemory(); break; case 'poll': + $self = htmlspecialchars($_SERVER['PHP_SELF']); echo ""; + src=\"{$self}?do=poll2&hidem=1\">"; break; case 'poll2': echo "
";
@@ -751,7 +764,7 @@ Committed_AS:   348732 kB
 			echo $this->Tables(); break;
 		}
 		global $ADODB_vers;
-		echo "

$ADODB_vers Sponsored by phpLens
"; + echo "

$ADODB_vers Sponsored by phpLens
"; } /* @@ -905,7 +918,7 @@ Committed_AS: 348732 kB { - $PHP_SELF = $_SERVER['PHP_SELF']; + $PHP_SELF = htmlspecialchars($_SERVER['PHP_SELF']); $sql = isset($_REQUEST['sql']) ? $_REQUEST['sql'] : ''; if (isset($_SESSION['phplens_sqlrows'])) $rows = $_SESSION['phplens_sqlrows']; @@ -1085,4 +1098,4 @@ Committed_AS: 348732 kB // end hack } -?> +?> \ No newline at end of file diff --git a/lib/adodb/adodb-php4.inc.php b/lib/adodb/adodb-php4.inc.php index dca76105cb..9d84ded8a2 100644 --- a/lib/adodb/adodb-php4.inc.php +++ b/lib/adodb/adodb-php4.inc.php @@ -1,7 +1,7 @@ parent = $parent; - } - - /** - * XML Callback to process start elements - * - * @access private - */ - function _tag_open( &$parser, $tag, $attributes ) { - - } - - /** - * XML Callback to process CDATA elements - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - - } - - function create() { - return array(); - } - - /** - * Destroys the object - */ - function destroy() { - unset( $this ); - } - - /** - * 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. - */ - 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. - */ - 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 -*/ -class dbTable extends dbObject { - - /** - * @var string Table name - */ - var $name; - - /** - * @var array Field specifier: Meta-information about each field - */ - var $fields = array(); - - /** - * @var array List of table indexes. - */ - var $indexes = array(); - - /** - * @var array Table options: Table-level options - */ - var $opts = array(); - - /** - * @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 $drop_table; - - /** - * @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 dbTable( &$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 - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'INDEX': - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - xml_set_object( $parser, $this->addIndex( $attributes ) ); - } - break; - case 'DATA': - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - xml_set_object( $parser, $this->addData( $attributes ) ); - } - break; - case 'DROP': - $this->drop(); - break; - case 'FIELD': - // Add a field - $fieldName = $attributes['NAME']; - $fieldType = $attributes['TYPE']; - $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL; - $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL; - - $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts ); - break; - case 'KEY': - case 'NOTNULL': - case 'AUTOINCREMENT': - // Add a field option - $this->addFieldOpt( $this->current_field, $this->currentElement ); - break; - case 'DEFAULT': - // Add a field option to the table object - - // Work around ADOdb datadict issue that misinterprets empty strings. - if( $attributes['VALUE'] == '' ) { - $attributes['VALUE'] = " '' "; - } - - $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] ); - break; - case 'DEFDATE': - case 'DEFTIMESTAMP': - // Add a field option to the table object - $this->addFieldOpt( $this->current_field, $this->currentElement ); - break; - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Table constraint - case 'CONSTRAINT': - if( isset( $this->current_field ) ) { - $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata ); - } else { - $this->addTableOpt( $cdata ); - } - break; - // Table option - case 'OPT': - $this->addTableOpt( $cdata ); - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'TABLE': - $this->parent->addSQL( $this->create( $this->parent ) ); - xml_set_object( $parser, $this->parent ); - $this->destroy(); - break; - case 'FIELD': - unset($this->current_field); - break; - - } - } - - /** - * 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 ); - return $this->indexes[$name]; - } - - /** - * 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 ); - } - return $this->data; - } - - /** - * 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 - */ - function addField( $name, $type, $size = NULL, $opts = NULL ) { - $field_id = $this->FieldID( $name ); - - // Set the field index so we know where we are - $this->current_field = $field_id; - - // Set the field name (required) - $this->fields[$field_id]['NAME'] = $name; - - // Set the field type (required) - $this->fields[$field_id]['TYPE'] = $type; - - // Set the field size (optional) - if( isset( $size ) ) { - $this->fields[$field_id]['SIZE'] = $size; - } - - // Set the field options - if( isset( $opts ) ) { - $this->fields[$field_id]['OPTS'][] = $opts; - } - } - - /** - * 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 - */ - function addFieldOpt( $field, $opt, $value = NULL ) { - if( !isset( $value ) ) { - $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt; - // Add the option and value - } else { - $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value ); - } - } - - /** - * 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 ) { - $this->opts[] = $opt; - - return $this->opts; - } - - /** - * 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(); - - // drop any existing indexes - if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) { - foreach( $legacy_indexes as $index => $index_details ) { - $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name ); - } - } - - // remove fields to be dropped from table object - foreach( $this->drop_field as $field ) { - unset( $this->fields[$field] ); - } - - // if table exists - if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) { - // drop table - if( $this->drop_table ) { - $sql[] = $xmls->dict->DropTableSQL( $this->name ); - - return $sql; - } - - // drop any existing fields not in schema - foreach( $legacy_fields as $field_id => $field ) { - if( !isset( $this->fields[$field_id] ) ) { - $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' ); - } - } - // if table doesn't exist - } else { - if( $this->drop_table ) { - return $sql; - } - - $legacy_fields = array(); - } - - // Loop through the field specifier array, building the associative array for the field options - $fldarray = array(); - - foreach( $this->fields as $field_id => $finfo ) { - // Set an empty size if it isn't supplied - if( !isset( $finfo['SIZE'] ) ) { - $finfo['SIZE'] = ''; - } - - // Initialize the field array with the type and size - $fldarray[$field_id] = array( - 'NAME' => $finfo['NAME'], - 'TYPE' => $finfo['TYPE'], - 'SIZE' => $finfo['SIZE'] - ); - - // Loop through the options array and add the field options. - if( isset( $finfo['OPTS'] ) ) { - foreach( $finfo['OPTS'] as $opt ) { - // Option has an argument. - if( is_array( $opt ) ) { - $key = key( $opt ); - $value = $opt[key( $opt )]; - @$fldarray[$field_id][$key] .= $value; - // Option doesn't have arguments - } else { - $fldarray[$field_id][$opt] = $opt; - } - } - } - } - - if( empty( $legacy_fields ) ) { - // Create the new table - $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); - logMsg( end( $sql ), 'Generated CreateTableSQL' ); - } else { - // Upgrade an existing table - logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" ); - switch( $xmls->upgrade ) { - // Use ChangeTableSQL - case 'ALTER': - logMsg( 'Generated ChangeTableSQL (ALTERing table)' ); - $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts ); - break; - case 'REPLACE': - logMsg( 'Doing upgrade REPLACE (testing)' ); - $sql[] = $xmls->dict->DropTableSQL( $this->name ); - $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); - break; - // ignore table - default: - return array(); - } - } - - foreach( $this->indexes as $index ) { - $sql[] = $index->create( $xmls ); - } - - if( isset( $this->data ) ) { - $sql[] = $this->data->create( $xmls ); - } - - return $sql; - } - - /** - * Marks a field or table for destruction - */ - function drop() { - if( isset( $this->current_field ) ) { - // Drop the current field - logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" ); - // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field ); - $this->drop_field[$this->current_field] = $this->current_field; - } else { - // Drop the current table - logMsg( "Dropping table '{$this->name}'" ); - // $this->drop_table = $xmls->dict->DropTableSQL( $this->name ); - $this->drop_table = TRUE; - } - } -} - -/** -* 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 $name; - - /** - * @var array Index options: Index-level options - */ - var $opts = array(); - - /** - * @var array Indexed fields: Table columns included in this index - */ - var $columns = array(); - - /** - * @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 dbIndex( &$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 - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'DROP': - $this->drop(); - break; - case 'CLUSTERED': - case 'BITMAP': - case 'UNIQUE': - case 'FULLTEXT': - case 'HASH': - // Add index Option - $this->addIndexOpt( $this->currentElement ); - break; - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - * - * Processes XML cdata. - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Index field name - case 'COL': - $this->addField( $cdata ); - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'INDEX': - xml_set_object( $parser, $this->parent ); - break; - } - } - - /** - * Adds a field to the index - * - * @param string $name Field name - * @return string Field list - */ - function addField( $name ) { - $this->columns[$this->FieldID( $name )] = $name; - - // Return the field list - return $this->columns; - } - - /** - * Adds options to the index - * - * @param string $opt Comma-separated list of index options. - * @return string Option list - */ - function addIndexOpt( $opt ) { - $this->opts[] = $opt; - - // Return the options list - return $this->opts; - } - - /** - * 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; - } - - // eliminate any columns that aren't in the table - foreach( $this->columns as $id => $col ) { - if( !isset( $this->parent->fields[$id] ) ) { - unset( $this->columns[$id] ); - } - } - - return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts ); - } - - /** - * 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 -*/ -class dbData extends dbObject { - - var $data = array(); - - var $row; - - /** - * Initializes the new dbIndex object. - * - * @param object $parent Parent object - * @param array $attributes Attributes - * - * @internal - */ - function dbData( &$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 - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'ROW': - $this->row = count( $this->data ); - $this->data[$this->row] = array(); - break; - case 'F': - $this->addField($attributes); - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - * - * Processes XML cdata. - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Index field name - case 'F': - $this->addData( $cdata ); - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'DATA': - xml_set_object( $parser, $this->parent ); - break; - } - } - - /** - * 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']; - } else { - $name = count($this->data[$this->row]); - } - - // Set the field index so we know where we are - $this->current_field = $this->FieldID( $name ); - } - - /** - * 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(); - } - - if( !isset( $this->data[$this->row][$this->current_field] ) ) { - $this->data[$this->row][$this->current_field] = ''; - } - - $this->data[$this->row][$this->current_field] .= $cdata; - } - - /** - * 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); - $sql = array(); - - // eliminate any columns that aren't in the table - foreach( $this->data as $row ) { - $table_fields = $this->parent->fields; - $fields = array(); - - 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 ) ); - } else { - continue; - } - } - - $name = $table_fields[$field_id]['NAME']; - - switch( $table_fields[$field_id]['TYPE'] ) { - case 'C': - case 'C2': - case 'X': - case 'X2': - $fields[$name] = $xmls->db->qstr( $field_data ); - break; - case 'I': - case 'I1': - case 'I2': - case 'I4': - case 'I8': - $fields[$name] = intval($field_data); - break; - default: - $fields[$name] = $field_data; - } - - unset($table_fields[$field_id]); - } - - // check that at least 1 column is specified - if( empty( $fields ) ) { - continue; - } - - // check that no required columns are missing - if( count( $fields ) < $table_field_count ) { - foreach( $table_fields as $field ) { - if (isset( $field['OPTS'] )) - if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) { - continue(2); - } - } - } - - $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')'; - } - - return $sql; - } -} - -/** -* 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 $queries = array(); - - /** - * @var string String used to build of a query line by line - */ - var $query; - - /** - * @var string Query prefix key - */ - var $prefixKey = ''; - - /** - * @var boolean Auto prefix enable (TRUE) - */ - var $prefixMethod = 'AUTO'; - - /** - * Initializes the query set. - * - * @param object $parent Parent object - * @param array $attributes Attributes - */ - function dbQuerySet( &$parent, $attributes = NULL ) { - $this->parent = $parent; - - // Overrides the manual prefix key - if( isset( $attributes['KEY'] ) ) { - $this->prefixKey = $attributes['KEY']; - } - - $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : ''; - - // Enables or disables automatic prefix prepending - switch( $prefixMethod ) { - case 'AUTO': - $this->prefixMethod = 'AUTO'; - break; - case 'MANUAL': - $this->prefixMethod = 'MANUAL'; - break; - case 'NONE': - $this->prefixMethod = 'NONE'; - break; - } - } - - /** - * XML Callback to process start elements. Elements currently - * processed are: QUERY. - * - * @access private - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'QUERY': - // Create a new query in a SQL queryset. - // Ignore this query set if a platform is specified and it's different than the - // current connection platform. - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - $this->newQuery(); - } else { - $this->discardQuery(); - } - break; - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Line of queryset SQL data - case 'QUERY': - $this->buildQuery( $cdata ); - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'QUERY': - // Add the finished query to the open query set. - $this->addQuery(); - break; - case 'SQL': - $this->parent->addSQL( $this->create( $this->parent ) ); - xml_set_object( $parser, $this->parent ); - $this->destroy(); - break; - default: - - } - } - - /** - * Re-initializes the query. - * - * @return boolean TRUE - */ - function newQuery() { - $this->query = ''; - - return TRUE; - } - - /** - * Discards the existing query. - * - * @return boolean TRUE - */ - function discardQuery() { - unset( $this->query ); - - return TRUE; - } - - /** - * 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; - } - - $this->query .= $sql; - - return $this->query; - } - - /** - * Adds a completed query to the query list - * - * @return string SQL of added query - */ - function addQuery() { - if( !isset( $this->query ) ) { - return FALSE; - } - - $this->queries[] = $return = trim($this->query); - - unset( $this->query ); - - return $return; - } - - /** - * 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 ) { - case 'AUTO': - // Enable auto prefix replacement - - // Process object prefix. - // Evaluate SQL statements to prepend prefix to objects - $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); - $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); - $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); - - // SELECT statements aren't working yet - #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data ); - - case 'MANUAL': - // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX. - // If prefixKey is not set, we use the default constant XMLS_PREFIX - if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) { - // Enable prefix override - $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query ); - } else { - // Use default replacement - $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query ); - } - } - - $this->queries[$id] = trim( $query ); - } - - // Return the query set array - return $this->queries; - } - - /** - * 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; - } - - if( preg_match( $regex, $query, $match ) ) { - $preamble = $match[1]; - $postamble = $match[5]; - $objectList = explode( ',', $match[3] ); - // $prefix = $prefix . '_'; - - $prefixedList = ''; - - foreach( $objectList as $object ) { - if( $prefixedList !== '' ) { - $prefixedList .= ', '; - } - - $prefixedList .= $prefix . trim( $object ); - } - - $query = $preamble . ' ' . $prefixedList . ' ' . $postamble; - } - - return $query; - } -} - -/** -* 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$ -* -* @package axmls -*/ -class adoSchema { - - /** - * @var array Array containing SQL queries to generate all objects - * @access private - */ - var $sqlArray; - - /** - * @var object ADOdb connection object - * @access private - */ - var $db; - - /** - * @var object ADOdb Data Dictionary - * @access private - */ - var $dict; - - /** - * @var string Current XML element - * @access private - */ - var $currentElement = ''; - - /** - * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database - * @access private - */ - var $upgrade = ''; - - /** - * @var string Optional object prefix - * @access private - */ - var $objectPrefix = ''; - - /** - * @var long Original Magic Quotes Runtime value - * @access private - */ - var $mgq; - - /** - * @var long System debug - * @access private - */ - var $debug; - - /** - * @var string Regular expression to find schema version - * @access private - */ - var $versionRegex = '//'; - - /** - * @var string Current schema version - * @access private - */ - var $schemaVersion; - - /** - * @var int Success of last Schema execution - */ - var $success; - - /** - * @var bool Execute SQL inline as it is generated - */ - var $executeInline; - - /** - * @var bool Continue SQL execution if errors occur - */ - var $continueOnError; - - /** - * 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 adoSchema( &$db ) { - // Initialize the environment - $this->mgq = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); - - $this->db = $db; - $this->debug = $this->db->debug; - $this->dict = NewDataDictionary( $this->db ); - $this->sqlArray = array(); - $this->schemaVersion = XMLS_SCHEMA_VERSION; - $this->executeInline( XMLS_EXECUTE_INLINE ); - $this->continueOnError( XMLS_CONTINUE_ON_ERROR ); - $this->setUpgradeMethod(); - } - - /** - * 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; - } - - $method = strtoupper( $method ); - - // Handle the upgrade methods - switch( $method ) { - case 'ALTER': - $this->upgrade = $method; - break; - case 'REPLACE': - $this->upgrade = $method; - break; - case 'BEST': - $this->upgrade = 'ALTER'; - break; - case 'NONE': - $this->upgrade = 'NONE'; - break; - default: - // Use default if no legitimate method is passed. - $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD; - } - - return $this->upgrade; - } - - /** - * 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() - */ - function ExecuteInline( $mode = NULL ) { - if( is_bool( $mode ) ) { - $this->executeInline = $mode; - } - - return $this->executeInline; - } - - /** - * 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() - */ - function ContinueOnError( $mode = NULL ) { - if( is_bool( $mode ) ) { - $this->continueOnError = $mode; - } - - return $this->continueOnError; - } - - /** - * 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() - */ - function ParseSchemaFile( $filename, $returnSchema = FALSE ) { - // Open the file - if( !($fp = fopen( $filename, 'r' )) ) { - // die( 'Unable to open file' ); - return FALSE; - } - - // do version detection here - if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) { - return FALSE; - } - - if ( $returnSchema ) - { - $xmlstring = ''; - while( $data = fread( $fp, 100000 ) ) { - $xmlstring .= $data; - } - return $xmlstring; - } - - $this->success = 2; - - $xmlParser = $this->create_parser(); - - // Process the file - while( $data = fread( $fp, 4096 ) ) { - if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) { - die( sprintf( - "XML error: %s at line %d", - xml_error_string( xml_get_error_code( $xmlParser) ), - xml_get_current_line_number( $xmlParser) - ) ); - } - } - - xml_parser_free( $xmlParser ); - - return $this->sqlArray; - } - - /** - * 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; - } - - // do version detection here - if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) { - return FALSE; - } - - if ( $returnSchema ) - { - return $xmlstring; - } - - $this->success = 2; - - $xmlParser = $this->create_parser(); - - if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) { - die( sprintf( - "XML error: %s at line %d", - xml_error_string( xml_get_error_code( $xmlParser) ), - xml_get_current_line_number( $xmlParser) - ) ); - } - - xml_parser_free( $xmlParser ); - - return $this->sqlArray; - } - - /** - * 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. - */ - function RemoveSchemaString( $schema, $returnSchema = FALSE ) { - - // grab current version - if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { - return FALSE; - } - - return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema ); - } - - /** - * 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. - */ - function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) { - if( !is_bool( $continueOnErr ) ) { - $continueOnErr = $this->ContinueOnError(); - } - - if( !isset( $sqlArray ) ) { - $sqlArray = $this->sqlArray; - } - - if( !is_array( $sqlArray ) ) { - $this->success = 0; - } else { - $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr ); - } - - return $this->success; - } - - /** - * 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. - */ - function SaveSQL( $filename = './schema.sql' ) { - - if( !isset( $sqlArray ) ) { - $sqlArray = $this->sqlArray; - } - if( !isset( $sqlArray ) ) { - return FALSE; - } - - $fp = fopen( $filename, "w" ); - - foreach( $sqlArray as $key => $query ) { - fwrite( $fp, $query . ";\n" ); - } - fclose( $fp ); - } - - /** - * Create an xml parser - * - * @return object PHP XML parser object - * - * @access private - */ - function create_parser() { - // Create the parser - $xmlParser = xml_parser_create(); - xml_set_object( $xmlParser, $this ); - - // Initialize the XML callback functions - xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' ); - xml_set_character_data_handler( $xmlParser, '_tag_cdata' ); - - return $xmlParser; - } - - /** - * XML Callback to process start elements - * - * @access private - */ - function _tag_open( &$parser, $tag, $attributes ) { - switch( strtoupper( $tag ) ) { - case 'TABLE': - $this->obj = new dbTable( $this, $attributes ); - xml_set_object( $parser, $this->obj ); - break; - case 'SQL': - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - $this->obj = new dbQuerySet( $this, $attributes ); - xml_set_object( $parser, $this->obj ); - } - break; - default: - // print_r( array( $tag, $attributes ) ); - } - - } - - /** - * XML Callback to process CDATA elements - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - } - - /** - * 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. - */ - function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) { - - // grab current version - if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { - return FALSE; - } - - if( !isset ($newVersion) ) { - $newVersion = $this->schemaVersion; - } - - if( $version == $newVersion ) { - $result = $schema; - } else { - $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion); - } - - if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { - fwrite( $fp, $result ); - fclose( $fp ); - } - - 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. - */ - function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) { - - // grab current version - if( !( $version = $this->SchemaFileVersion( $filename ) ) ) { - return FALSE; - } - - if( !isset ($newVersion) ) { - $newVersion = $this->schemaVersion; - } - - if( $version == $newVersion ) { - $result = _file_get_contents( $filename ); - - // remove unicode BOM if present - if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) { - $result = substr( $result, 3 ); - } - } else { - $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' ); - } - - if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { - fwrite( $fp, $result ); - fclose( $fp ); - } - - return $result; - } - - function TransformSchema( $schema, $xsl, $schematype='string' ) - { - // Fail if XSLT extension is not available - if( ! function_exists( 'xslt_create' ) ) { - return FALSE; - } - - $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl'; - - // look for xsl - if( !is_readable( $xsl_file ) ) { - return FALSE; - } - - switch( $schematype ) - { - case 'file': - if( !is_readable( $schema ) ) { - return FALSE; - } - - $schema = _file_get_contents( $schema ); - break; - case 'string': - default: - if( !is_string( $schema ) ) { - return FALSE; - } - } - - $arguments = array ( - '/_xml' => $schema, - '/_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')); - - // process the schema - $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); - - xslt_free ($xh); - - return $result; - } - - /** - * 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( - 'Message Type' => ucfirst( $fields['msgtype'] ), - 'Message Code' => $fields['code'], - 'Message' => $fields['msg'], - 'Error Number' => $errno, - 'Level' => $level - ); - - switch( $fields['URI'] ) { - case 'arg:/_xml': - $msg['Input'] = 'XML'; - break; - case 'arg:/_xsl': - $msg['Input'] = 'XSL'; - break; - default: - $msg['Input'] = $fields['URI']; - } - - $msg['Line'] = $fields['line']; - } else { - $msg = array( - 'Message Type' => 'Error', - 'Error Number' => $errno, - 'Level' => $level, - 'Fields' => var_export( $fields, TRUE ) - ); - } - - $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n" - . '' . "\n"; - - foreach( $msg as $label => $details ) { - $error_details .= '' . "\n"; - } - - $error_details .= '
' . $label . ': ' . htmlentities( $details ) . '
'; - - trigger_error( $error_details, E_USER_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' )) ) { - // die( 'Unable to open file' ); - return FALSE; - } - - // Process the file - while( $data = fread( $fp, 4096 ) ) { - if( preg_match( $this->versionRegex, $data, $matches ) ) { - return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; - } - } - - return FALSE; - } - - /** - * 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; - } - - if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) { - return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; - } - - return FALSE; - } - - /** - * 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 ); - - $schema = '' . "\n" - . '' . "\n"; - - if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) { - foreach( $tables as $table ) { - $schema .= ' ' . "\n"; - - // grab details from database - $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' ); - $fields = $this->db->MetaColumns( $table ); - $indexes = $this->db->MetaIndexes( $table ); - - if( is_array( $fields ) ) { - foreach( $fields as $details ) { - $extra = ''; - $content = array(); - - if( $details->max_length > 0 ) { - $extra .= ' size="' . $details->max_length . '"'; - } - - if( $details->primary_key ) { - $content[] = ''; - } elseif( $details->not_null ) { - $content[] = ''; - } - - if( $details->has_default ) { - $content[] = ''; - } - - if( $details->auto_increment ) { - $content[] = ''; - } - - // this stops the creation of 'R' columns, - // AUTOINCREMENT is used to create auto columns - $details->primary_key = 0; - $type = $rs->MetaType( $details ); - - $schema .= ' '; - - if( !empty( $content ) ) { - $schema .= "\n " . implode( "\n ", $content ) . "\n "; - } - - $schema .= '' . "\n"; - } - } - - if( is_array( $indexes ) ) { - foreach( $indexes as $index => $details ) { - $schema .= ' ' . "\n"; - - if( $details['unique'] ) { - $schema .= ' ' . "\n"; - } - - foreach( $details['columns'] as $column ) { - $schema .= ' ' . $column . '' . "\n"; - } - - $schema .= ' ' . "\n"; - } - } - - if( $data ) { - $rs = $this->db->Execute( 'SELECT * FROM ' . $table ); - - if( is_object( $rs ) ) { - $schema .= ' ' . "\n"; - - while( $row = $rs->FetchRow() ) { - foreach( $row as $key => $val ) { - $row[$key] = htmlentities($val); - } - - $schema .= ' ' . implode( '', $row ) . '' . "\n"; - } - - $schema .= ' ' . "\n"; - } - } - - $schema .= '
' . "\n"; - } - } - - $this->db->SetFetchMode( $old_mode ); - - $schema .= '
'; - return $schema; - } - - /** - * 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 - case empty( $prefix ): - logMsg( 'Cleared prefix' ); - $this->objectPrefix = ''; - return TRUE; - // prefix too long - case strlen( $prefix ) > XMLS_PREFIX_MAXLEN: - // prefix contains invalid characters - case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ): - logMsg( 'Invalid prefix: ' . $prefix ); - return FALSE; - } - - if( $underscore AND substr( $prefix, -1 ) != '_' ) { - $prefix .= '_'; - } - - // prefix valid - logMsg( 'Set prefix: ' . $prefix ); - $this->objectPrefix = $prefix; - return TRUE; - } - - /** - * 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 ) ) { - // Prepend the object prefix to the table name - // prepend after quote if used - return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name ); - } - - // No prefix set. Use name provided. - return $name; - } - - /** - * 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*)*$/'; - - if( !isset( $platform ) OR preg_match( $regex, $platform ) ) { - logMsg( "Platform $platform is supported" ); - return TRUE; - } else { - logMsg( "Platform $platform is NOT supported" ); - return FALSE; - } - } - - /** - * 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 - */ - function addSQL( $sql = NULL ) { - if( is_array( $sql ) ) { - foreach( $sql as $line ) { - $this->addSQL( $line ); - } - - return TRUE; - } - - if( is_string( $sql ) ) { - $this->sqlArray[] = $sql; - - // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL. - if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) { - $saved = $this->db->debug; - $this->db->debug = $this->debug; - $ok = $this->db->Execute( $sql ); - $this->db->debug = $saved; - - if( !$ok ) { - if( $this->debug ) { - ADOConnection::outp( $this->db->ErrorMsg() ); - } - - $this->success = 1; - } - } - - return TRUE; - } - - return FALSE; - } - - /** - * 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; - } - - if( !is_array( $sqlArray ) ) { - return FALSE; - } - - switch( strtolower( $format ) ) { - case 'string': - case 'text': - return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : ''; - case'html': - return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : ''; - } - - return $this->sqlArray; - } - - /** - * 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() { - set_magic_quotes_runtime( $this->mgq ); - unset( $this ); - } -} - -/** -* Message logging function -* -* @access private -*/ -function logMsg( $msg, $title = NULL, $force = FALSE ) { - if( XMLS_DEBUG or $force ) { - echo '
';
-		
-		if( isset( $title ) ) {
-			echo '

' . htmlentities( $title ) . '

'; - } - - if( is_object( $this ) ) { - echo '[' . get_class( $this ) . '] '; - } - - print_r( $msg ); - - echo '
'; - } -} +parent = $parent; + } + + /** + * XML Callback to process start elements + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + + } + + function create() { + return array(); + } + + /** + * Destroys the object + */ + function destroy() { + unset( $this ); + } + + /** + * 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. + */ + 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. + */ + 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 +*/ +class dbTable extends dbObject { + + /** + * @var string Table name + */ + var $name; + + /** + * @var array Field specifier: Meta-information about each field + */ + var $fields = array(); + + /** + * @var array List of table indexes. + */ + var $indexes = array(); + + /** + * @var array Table options: Table-level options + */ + var $opts = array(); + + /** + * @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 $drop_table; + + /** + * @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 dbTable( &$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 + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'INDEX': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + xml_set_object( $parser, $this->addIndex( $attributes ) ); + } + break; + case 'DATA': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + xml_set_object( $parser, $this->addData( $attributes ) ); + } + break; + case 'DROP': + $this->drop(); + break; + case 'FIELD': + // Add a field + $fieldName = $attributes['NAME']; + $fieldType = $attributes['TYPE']; + $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL; + $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL; + + $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts ); + break; + case 'KEY': + case 'NOTNULL': + case 'AUTOINCREMENT': + // Add a field option + $this->addFieldOpt( $this->current_field, $this->currentElement ); + break; + case 'DEFAULT': + // Add a field option to the table object + + // Work around ADOdb datadict issue that misinterprets empty strings. + if( $attributes['VALUE'] == '' ) { + $attributes['VALUE'] = " '' "; + } + + $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] ); + break; + case 'DEFDATE': + case 'DEFTIMESTAMP': + // Add a field option to the table object + $this->addFieldOpt( $this->current_field, $this->currentElement ); + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Table constraint + case 'CONSTRAINT': + if( isset( $this->current_field ) ) { + $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata ); + } else { + $this->addTableOpt( $cdata ); + } + break; + // Table option + case 'OPT': + $this->addTableOpt( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'TABLE': + $this->parent->addSQL( $this->create( $this->parent ) ); + xml_set_object( $parser, $this->parent ); + $this->destroy(); + break; + case 'FIELD': + unset($this->current_field); + break; + + } + } + + /** + * 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 ); + return $this->indexes[$name]; + } + + /** + * 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 ); + } + return $this->data; + } + + /** + * 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 + */ + function addField( $name, $type, $size = NULL, $opts = NULL ) { + $field_id = $this->FieldID( $name ); + + // Set the field index so we know where we are + $this->current_field = $field_id; + + // Set the field name (required) + $this->fields[$field_id]['NAME'] = $name; + + // Set the field type (required) + $this->fields[$field_id]['TYPE'] = $type; + + // Set the field size (optional) + if( isset( $size ) ) { + $this->fields[$field_id]['SIZE'] = $size; + } + + // Set the field options + if( isset( $opts ) ) { + $this->fields[$field_id]['OPTS'][] = $opts; + } + } + + /** + * 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 + */ + function addFieldOpt( $field, $opt, $value = NULL ) { + if( !isset( $value ) ) { + $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt; + // Add the option and value + } else { + $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value ); + } + } + + /** + * 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; + } + return $this->opts; + } + + + /** + * 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(); + + // drop any existing indexes + if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) { + foreach( $legacy_indexes as $index => $index_details ) { + $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name ); + } + } + + // remove fields to be dropped from table object + foreach( $this->drop_field as $field ) { + unset( $this->fields[$field] ); + } + + // if table exists + if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) { + // drop table + if( $this->drop_table ) { + $sql[] = $xmls->dict->DropTableSQL( $this->name ); + + return $sql; + } + + // drop any existing fields not in schema + foreach( $legacy_fields as $field_id => $field ) { + if( !isset( $this->fields[$field_id] ) ) { + $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' ); + } + } + // if table doesn't exist + } else { + if( $this->drop_table ) { + return $sql; + } + + $legacy_fields = array(); + } + + // Loop through the field specifier array, building the associative array for the field options + $fldarray = array(); + + foreach( $this->fields as $field_id => $finfo ) { + // Set an empty size if it isn't supplied + if( !isset( $finfo['SIZE'] ) ) { + $finfo['SIZE'] = ''; + } + + // Initialize the field array with the type and size + $fldarray[$field_id] = array( + 'NAME' => $finfo['NAME'], + 'TYPE' => $finfo['TYPE'], + 'SIZE' => $finfo['SIZE'] + ); + + // Loop through the options array and add the field options. + if( isset( $finfo['OPTS'] ) ) { + foreach( $finfo['OPTS'] as $opt ) { + // Option has an argument. + if( is_array( $opt ) ) { + $key = key( $opt ); + $value = $opt[key( $opt )]; + @$fldarray[$field_id][$key] .= $value; + // Option doesn't have arguments + } else { + $fldarray[$field_id][$opt] = $opt; + } + } + } + } + + if( empty( $legacy_fields ) ) { + // Create the new table + $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); + logMsg( end( $sql ), 'Generated CreateTableSQL' ); + } else { + // Upgrade an existing table + logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" ); + switch( $xmls->upgrade ) { + // Use ChangeTableSQL + case 'ALTER': + logMsg( 'Generated ChangeTableSQL (ALTERing table)' ); + $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts ); + break; + case 'REPLACE': + logMsg( 'Doing upgrade REPLACE (testing)' ); + $sql[] = $xmls->dict->DropTableSQL( $this->name ); + $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); + break; + // ignore table + default: + return array(); + } + } + + foreach( $this->indexes as $index ) { + $sql[] = $index->create( $xmls ); + } + + if( isset( $this->data ) ) { + $sql[] = $this->data->create( $xmls ); + } + + return $sql; + } + + /** + * Marks a field or table for destruction + */ + function drop() { + if( isset( $this->current_field ) ) { + // Drop the current field + logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" ); + // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field ); + $this->drop_field[$this->current_field] = $this->current_field; + } else { + // Drop the current table + logMsg( "Dropping table '{$this->name}'" ); + // $this->drop_table = $xmls->dict->DropTableSQL( $this->name ); + $this->drop_table = TRUE; + } + } +} + +/** +* 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 $name; + + /** + * @var array Index options: Index-level options + */ + var $opts = array(); + + /** + * @var array Indexed fields: Table columns included in this index + */ + var $columns = array(); + + /** + * @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 dbIndex( &$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 + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'DROP': + $this->drop(); + break; + case 'CLUSTERED': + case 'BITMAP': + case 'UNIQUE': + case 'FULLTEXT': + case 'HASH': + // Add index Option + $this->addIndexOpt( $this->currentElement ); + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * Processes XML cdata. + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Index field name + case 'COL': + $this->addField( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'INDEX': + xml_set_object( $parser, $this->parent ); + break; + } + } + + /** + * Adds a field to the index + * + * @param string $name Field name + * @return string Field list + */ + function addField( $name ) { + $this->columns[$this->FieldID( $name )] = $name; + + // Return the field list + return $this->columns; + } + + /** + * Adds options to the index + * + * @param string $opt Comma-separated list of index options. + * @return string Option list + */ + function addIndexOpt( $opt ) { + $this->opts[] = $opt; + + // Return the options list + return $this->opts; + } + + /** + * 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; + } + + // eliminate any columns that aren't in the table + foreach( $this->columns as $id => $col ) { + if( !isset( $this->parent->fields[$id] ) ) { + unset( $this->columns[$id] ); + } + } + + return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts ); + } + + /** + * 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 +*/ +class dbData extends dbObject { + + var $data = array(); + + var $row; + + /** + * Initializes the new dbIndex object. + * + * @param object $parent Parent object + * @param array $attributes Attributes + * + * @internal + */ + function dbData( &$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 + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'ROW': + $this->row = count( $this->data ); + $this->data[$this->row] = array(); + break; + case 'F': + $this->addField($attributes); + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * Processes XML cdata. + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Index field name + case 'F': + $this->addData( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'DATA': + xml_set_object( $parser, $this->parent ); + break; + } + } + + /** + * 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']; + } else { + $name = count($this->data[$this->row]); + } + + // Set the field index so we know where we are + $this->current_field = $this->FieldID( $name ); + } + + /** + * 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(); + } + + if( !isset( $this->data[$this->row][$this->current_field] ) ) { + $this->data[$this->row][$this->current_field] = ''; + } + + $this->data[$this->row][$this->current_field] .= $cdata; + } + + /** + * 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); + $sql = array(); + + // eliminate any columns that aren't in the table + foreach( $this->data as $row ) { + $table_fields = $this->parent->fields; + $fields = array(); + + 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 ) ); + } else { + continue; + } + } + + $name = $table_fields[$field_id]['NAME']; + + switch( $table_fields[$field_id]['TYPE'] ) { + case 'C': + case 'C2': + case 'X': + case 'X2': + $fields[$name] = $xmls->db->qstr( $field_data ); + break; + case 'I': + case 'I1': + case 'I2': + case 'I4': + case 'I8': + $fields[$name] = intval($field_data); + break; + default: + $fields[$name] = $field_data; + } + + unset($table_fields[$field_id]); + } + + // check that at least 1 column is specified + if( empty( $fields ) ) { + continue; + } + + // check that no required columns are missing + if( count( $fields ) < $table_field_count ) { + foreach( $table_fields as $field ) { + if (isset( $field['OPTS'] )) + if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) { + continue(2); + } + } + } + + $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')'; + } + + return $sql; + } +} + +/** +* 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 $queries = array(); + + /** + * @var string String used to build of a query line by line + */ + var $query; + + /** + * @var string Query prefix key + */ + var $prefixKey = ''; + + /** + * @var boolean Auto prefix enable (TRUE) + */ + var $prefixMethod = 'AUTO'; + + /** + * Initializes the query set. + * + * @param object $parent Parent object + * @param array $attributes Attributes + */ + function dbQuerySet( &$parent, $attributes = NULL ) { + $this->parent = $parent; + + // Overrides the manual prefix key + if( isset( $attributes['KEY'] ) ) { + $this->prefixKey = $attributes['KEY']; + } + + $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : ''; + + // Enables or disables automatic prefix prepending + switch( $prefixMethod ) { + case 'AUTO': + $this->prefixMethod = 'AUTO'; + break; + case 'MANUAL': + $this->prefixMethod = 'MANUAL'; + break; + case 'NONE': + $this->prefixMethod = 'NONE'; + break; + } + } + + /** + * XML Callback to process start elements. Elements currently + * processed are: QUERY. + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'QUERY': + // Create a new query in a SQL queryset. + // Ignore this query set if a platform is specified and it's different than the + // current connection platform. + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + $this->newQuery(); + } else { + $this->discardQuery(); + } + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Line of queryset SQL data + case 'QUERY': + $this->buildQuery( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'QUERY': + // Add the finished query to the open query set. + $this->addQuery(); + break; + case 'SQL': + $this->parent->addSQL( $this->create( $this->parent ) ); + xml_set_object( $parser, $this->parent ); + $this->destroy(); + break; + default: + + } + } + + /** + * Re-initializes the query. + * + * @return boolean TRUE + */ + function newQuery() { + $this->query = ''; + + return TRUE; + } + + /** + * Discards the existing query. + * + * @return boolean TRUE + */ + function discardQuery() { + unset( $this->query ); + + return TRUE; + } + + /** + * 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; + } + + $this->query .= $sql; + + return $this->query; + } + + /** + * Adds a completed query to the query list + * + * @return string SQL of added query + */ + function addQuery() { + if( !isset( $this->query ) ) { + return FALSE; + } + + $this->queries[] = $return = trim($this->query); + + unset( $this->query ); + + return $return; + } + + /** + * 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 ) { + case 'AUTO': + // Enable auto prefix replacement + + // Process object prefix. + // Evaluate SQL statements to prepend prefix to objects + $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + + // SELECT statements aren't working yet + #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data ); + + case 'MANUAL': + // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX. + // If prefixKey is not set, we use the default constant XMLS_PREFIX + if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) { + // Enable prefix override + $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query ); + } else { + // Use default replacement + $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query ); + } + } + + $this->queries[$id] = trim( $query ); + } + + // Return the query set array + return $this->queries; + } + + /** + * 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; + } + + if( preg_match( $regex, $query, $match ) ) { + $preamble = $match[1]; + $postamble = $match[5]; + $objectList = explode( ',', $match[3] ); + // $prefix = $prefix . '_'; + + $prefixedList = ''; + + foreach( $objectList as $object ) { + if( $prefixedList !== '' ) { + $prefixedList .= ', '; + } + + $prefixedList .= $prefix . trim( $object ); + } + + $query = $preamble . ' ' . $prefixedList . ' ' . $postamble; + } + + return $query; + } +} + +/** +* 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$ +* +* @package axmls +*/ +class adoSchema { + + /** + * @var array Array containing SQL queries to generate all objects + * @access private + */ + var $sqlArray; + + /** + * @var object ADOdb connection object + * @access private + */ + var $db; + + /** + * @var object ADOdb Data Dictionary + * @access private + */ + var $dict; + + /** + * @var string Current XML element + * @access private + */ + var $currentElement = ''; + + /** + * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database + * @access private + */ + var $upgrade = ''; + + /** + * @var string Optional object prefix + * @access private + */ + var $objectPrefix = ''; + + /** + * @var long Original Magic Quotes Runtime value + * @access private + */ + var $mgq; + + /** + * @var long System debug + * @access private + */ + var $debug; + + /** + * @var string Regular expression to find schema version + * @access private + */ + var $versionRegex = '//'; + + /** + * @var string Current schema version + * @access private + */ + var $schemaVersion; + + /** + * @var int Success of last Schema execution + */ + var $success; + + /** + * @var bool Execute SQL inline as it is generated + */ + var $executeInline; + + /** + * @var bool Continue SQL execution if errors occur + */ + var $continueOnError; + + /** + * 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 adoSchema( &$db ) { + // Initialize the environment + $this->mgq = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + + $this->db = $db; + $this->debug = $this->db->debug; + $this->dict = NewDataDictionary( $this->db ); + $this->sqlArray = array(); + $this->schemaVersion = XMLS_SCHEMA_VERSION; + $this->executeInline( XMLS_EXECUTE_INLINE ); + $this->continueOnError( XMLS_CONTINUE_ON_ERROR ); + $this->setUpgradeMethod(); + } + + /** + * 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; + } + + $method = strtoupper( $method ); + + // Handle the upgrade methods + switch( $method ) { + case 'ALTER': + $this->upgrade = $method; + break; + case 'REPLACE': + $this->upgrade = $method; + break; + case 'BEST': + $this->upgrade = 'ALTER'; + break; + case 'NONE': + $this->upgrade = 'NONE'; + break; + default: + // Use default if no legitimate method is passed. + $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD; + } + + return $this->upgrade; + } + + /** + * 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() + */ + function ExecuteInline( $mode = NULL ) { + if( is_bool( $mode ) ) { + $this->executeInline = $mode; + } + + return $this->executeInline; + } + + /** + * 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() + */ + function ContinueOnError( $mode = NULL ) { + if( is_bool( $mode ) ) { + $this->continueOnError = $mode; + } + + return $this->continueOnError; + } + + /** + * 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() + */ + function ParseSchemaFile( $filename, $returnSchema = FALSE ) { + // Open the file + if( !($fp = fopen( $filename, 'r' )) ) { + // die( 'Unable to open file' ); + return FALSE; + } + + // do version detection here + if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) { + return FALSE; + } + + if ( $returnSchema ) + { + $xmlstring = ''; + while( $data = fread( $fp, 100000 ) ) { + $xmlstring .= $data; + } + return $xmlstring; + } + + $this->success = 2; + + $xmlParser = $this->create_parser(); + + // Process the file + while( $data = fread( $fp, 4096 ) ) { + if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) { + die( sprintf( + "XML error: %s at line %d", + xml_error_string( xml_get_error_code( $xmlParser) ), + xml_get_current_line_number( $xmlParser) + ) ); + } + } + + xml_parser_free( $xmlParser ); + + return $this->sqlArray; + } + + /** + * 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; + } + + // do version detection here + if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) { + return FALSE; + } + + if ( $returnSchema ) + { + return $xmlstring; + } + + $this->success = 2; + + $xmlParser = $this->create_parser(); + + if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) { + die( sprintf( + "XML error: %s at line %d", + xml_error_string( xml_get_error_code( $xmlParser) ), + xml_get_current_line_number( $xmlParser) + ) ); + } + + xml_parser_free( $xmlParser ); + + return $this->sqlArray; + } + + /** + * 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. + */ + function RemoveSchemaString( $schema, $returnSchema = FALSE ) { + + // grab current version + if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { + return FALSE; + } + + return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema ); + } + + /** + * 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. + */ + function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) { + if( !is_bool( $continueOnErr ) ) { + $continueOnErr = $this->ContinueOnError(); + } + + if( !isset( $sqlArray ) ) { + $sqlArray = $this->sqlArray; + } + + if( !is_array( $sqlArray ) ) { + $this->success = 0; + } else { + $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr ); + } + + return $this->success; + } + + /** + * 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. + */ + function SaveSQL( $filename = './schema.sql' ) { + + if( !isset( $sqlArray ) ) { + $sqlArray = $this->sqlArray; + } + if( !isset( $sqlArray ) ) { + return FALSE; + } + + $fp = fopen( $filename, "w" ); + + foreach( $sqlArray as $key => $query ) { + fwrite( $fp, $query . ";\n" ); + } + fclose( $fp ); + } + + /** + * Create an xml parser + * + * @return object PHP XML parser object + * + * @access private + */ + function create_parser() { + // Create the parser + $xmlParser = xml_parser_create(); + xml_set_object( $xmlParser, $this ); + + // Initialize the XML callback functions + xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' ); + xml_set_character_data_handler( $xmlParser, '_tag_cdata' ); + + return $xmlParser; + } + + /** + * XML Callback to process start elements + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + switch( strtoupper( $tag ) ) { + case 'TABLE': + $this->obj = new dbTable( $this, $attributes ); + xml_set_object( $parser, $this->obj ); + break; + case 'SQL': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + $this->obj = new dbQuerySet( $this, $attributes ); + xml_set_object( $parser, $this->obj ); + } + break; + default: + // print_r( array( $tag, $attributes ) ); + } + + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + } + + /** + * 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. + */ + function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) { + + // grab current version + if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { + return FALSE; + } + + if( !isset ($newVersion) ) { + $newVersion = $this->schemaVersion; + } + + if( $version == $newVersion ) { + $result = $schema; + } else { + $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion); + } + + if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { + fwrite( $fp, $result ); + fclose( $fp ); + } + + 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. + */ + function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) { + + // grab current version + if( !( $version = $this->SchemaFileVersion( $filename ) ) ) { + return FALSE; + } + + if( !isset ($newVersion) ) { + $newVersion = $this->schemaVersion; + } + + if( $version == $newVersion ) { + $result = _file_get_contents( $filename ); + + // remove unicode BOM if present + if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) { + $result = substr( $result, 3 ); + } + } else { + $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' ); + } + + if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { + fwrite( $fp, $result ); + fclose( $fp ); + } + + return $result; + } + + function TransformSchema( $schema, $xsl, $schematype='string' ) + { + // Fail if XSLT extension is not available + if( ! function_exists( 'xslt_create' ) ) { + return FALSE; + } + + $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl'; + + // look for xsl + if( !is_readable( $xsl_file ) ) { + return FALSE; + } + + switch( $schematype ) + { + case 'file': + if( !is_readable( $schema ) ) { + return FALSE; + } + + $schema = _file_get_contents( $schema ); + break; + case 'string': + default: + if( !is_string( $schema ) ) { + return FALSE; + } + } + + $arguments = array ( + '/_xml' => $schema, + '/_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')); + + // process the schema + $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); + + xslt_free ($xh); + + return $result; + } + + /** + * 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( + 'Message Type' => ucfirst( $fields['msgtype'] ), + 'Message Code' => $fields['code'], + 'Message' => $fields['msg'], + 'Error Number' => $errno, + 'Level' => $level + ); + + switch( $fields['URI'] ) { + case 'arg:/_xml': + $msg['Input'] = 'XML'; + break; + case 'arg:/_xsl': + $msg['Input'] = 'XSL'; + break; + default: + $msg['Input'] = $fields['URI']; + } + + $msg['Line'] = $fields['line']; + } else { + $msg = array( + 'Message Type' => 'Error', + 'Error Number' => $errno, + 'Level' => $level, + 'Fields' => var_export( $fields, TRUE ) + ); + } + + $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n" + . '' . "\n"; + + foreach( $msg as $label => $details ) { + $error_details .= '' . "\n"; + } + + $error_details .= '
' . $label . ': ' . htmlentities( $details ) . '
'; + + trigger_error( $error_details, E_USER_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' )) ) { + // die( 'Unable to open file' ); + return FALSE; + } + + // Process the file + while( $data = fread( $fp, 4096 ) ) { + if( preg_match( $this->versionRegex, $data, $matches ) ) { + return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; + } + } + + return FALSE; + } + + /** + * 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; + } + + if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) { + return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; + } + + return FALSE; + } + + /** + * 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 ); + + $schema = '' . "\n" + . '' . "\n"; + + if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) { + foreach( $tables as $table ) { + $schema .= ' ' . "\n"; + + // grab details from database + $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' ); + $fields = $this->db->MetaColumns( $table ); + $indexes = $this->db->MetaIndexes( $table ); + + if( is_array( $fields ) ) { + foreach( $fields as $details ) { + $extra = ''; + $content = array(); + + if( $details->max_length > 0 ) { + $extra .= ' size="' . $details->max_length . '"'; + } + + if( $details->primary_key ) { + $content[] = ''; + } elseif( $details->not_null ) { + $content[] = ''; + } + + if( $details->has_default ) { + $content[] = ''; + } + + if( $details->auto_increment ) { + $content[] = ''; + } + + // this stops the creation of 'R' columns, + // AUTOINCREMENT is used to create auto columns + $details->primary_key = 0; + $type = $rs->MetaType( $details ); + + $schema .= ' '; + + if( !empty( $content ) ) { + $schema .= "\n " . implode( "\n ", $content ) . "\n "; + } + + $schema .= '' . "\n"; + } + } + + if( is_array( $indexes ) ) { + foreach( $indexes as $index => $details ) { + $schema .= ' ' . "\n"; + + if( $details['unique'] ) { + $schema .= ' ' . "\n"; + } + + foreach( $details['columns'] as $column ) { + $schema .= ' ' . $column . '' . "\n"; + } + + $schema .= ' ' . "\n"; + } + } + + if( $data ) { + $rs = $this->db->Execute( 'SELECT * FROM ' . $table ); + + if( is_object( $rs ) ) { + $schema .= ' ' . "\n"; + + while( $row = $rs->FetchRow() ) { + foreach( $row as $key => $val ) { + $row[$key] = htmlentities($val); + } + + $schema .= ' ' . implode( '', $row ) . '' . "\n"; + } + + $schema .= ' ' . "\n"; + } + } + + $schema .= '
' . "\n"; + } + } + + $this->db->SetFetchMode( $old_mode ); + + $schema .= '
'; + return $schema; + } + + /** + * 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 + case empty( $prefix ): + logMsg( 'Cleared prefix' ); + $this->objectPrefix = ''; + return TRUE; + // prefix too long + case strlen( $prefix ) > XMLS_PREFIX_MAXLEN: + // prefix contains invalid characters + case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ): + logMsg( 'Invalid prefix: ' . $prefix ); + return FALSE; + } + + if( $underscore AND substr( $prefix, -1 ) != '_' ) { + $prefix .= '_'; + } + + // prefix valid + logMsg( 'Set prefix: ' . $prefix ); + $this->objectPrefix = $prefix; + return TRUE; + } + + /** + * 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 ) ) { + // Prepend the object prefix to the table name + // prepend after quote if used + return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name ); + } + + // No prefix set. Use name provided. + return $name; + } + + /** + * 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*)*$/'; + + if( !isset( $platform ) OR preg_match( $regex, $platform ) ) { + logMsg( "Platform $platform is supported" ); + return TRUE; + } else { + logMsg( "Platform $platform is NOT supported" ); + return FALSE; + } + } + + /** + * 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 + */ + function addSQL( $sql = NULL ) { + if( is_array( $sql ) ) { + foreach( $sql as $line ) { + $this->addSQL( $line ); + } + + return TRUE; + } + + if( is_string( $sql ) ) { + $this->sqlArray[] = $sql; + + // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL. + if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) { + $saved = $this->db->debug; + $this->db->debug = $this->debug; + $ok = $this->db->Execute( $sql ); + $this->db->debug = $saved; + + if( !$ok ) { + if( $this->debug ) { + ADOConnection::outp( $this->db->ErrorMsg() ); + } + + $this->success = 1; + } + } + + return TRUE; + } + + return FALSE; + } + + /** + * 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; + } + + if( !is_array( $sqlArray ) ) { + return FALSE; + } + + switch( strtolower( $format ) ) { + case 'string': + case 'text': + return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : ''; + case'html': + return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : ''; + } + + return $this->sqlArray; + } + + /** + * 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() { + set_magic_quotes_runtime( $this->mgq ); + unset( $this ); + } +} + +/** +* Message logging function +* +* @access private +*/ +function logMsg( $msg, $title = NULL, $force = FALSE ) { + if( XMLS_DEBUG or $force ) { + echo '
';
+		
+		if( isset( $title ) ) {
+			echo '

' . htmlentities( $title ) . '

'; + } + + if( is_object( $this ) ) { + echo '[' . get_class( $this ) . '] '; + } + + print_r( $msg ); + + echo '
'; + } +} ?> \ No newline at end of file diff --git a/lib/adodb/adodb-xmlschema03.inc.php b/lib/adodb/adodb-xmlschema03.inc.php index 8c6a538780..f112f7b104 100644 --- a/lib/adodb/adodb-xmlschema03.inc.php +++ b/lib/adodb/adodb-xmlschema03.inc.php @@ -1,2403 +1,2404 @@ -parent = $parent; - } - - /** - * XML Callback to process start elements - * - * @access private - */ - function _tag_open( &$parser, $tag, $attributes ) { - - } - - /** - * XML Callback to process CDATA elements - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - - } - - function create() { - return array(); - } - - /** - * Destroys the object - */ - function destroy() { - unset( $this ); - } - - /** - * 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. - */ - 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. - */ - 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 -*/ -class dbTable extends dbObject { - - /** - * @var string Table name - */ - var $name; - - /** - * @var array Field specifier: Meta-information about each field - */ - var $fields = array(); - - /** - * @var array List of table indexes. - */ - var $indexes = array(); - - /** - * @var array Table options: Table-level options - */ - var $opts = array(); - - /** - * @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 $drop_table; - - /** - * @var boolean Mark field for destruction (not yet implemented) - * @access private - */ - var $drop_field = array(); - - /** - * @var array Platform-specific options - * @access private - */ - var $currentPlatform = true; - - - /** - * Iniitializes a new table object. - * - * @param string $prefix DB Object prefix - * @param array $attributes Array of table attributes. - */ - function dbTable( &$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 - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'INDEX': - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - xml_set_object( $parser, $this->addIndex( $attributes ) ); - } - break; - case 'DATA': - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - xml_set_object( $parser, $this->addData( $attributes ) ); - } - break; - case 'DROP': - $this->drop(); - break; - case 'FIELD': - // Add a field - $fieldName = $attributes['NAME']; - $fieldType = $attributes['TYPE']; - $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL; - $fieldOpts = !empty( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL; - - $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts ); - break; - case 'KEY': - case 'NOTNULL': - case 'AUTOINCREMENT': - case 'DEFDATE': - case 'DEFTIMESTAMP': - case 'UNSIGNED': - // Add a field option - $this->addFieldOpt( $this->current_field, $this->currentElement ); - break; - case 'DEFAULT': - // Add a field option to the table object - - // Work around ADOdb datadict issue that misinterprets empty strings. - if( $attributes['VALUE'] == '' ) { - $attributes['VALUE'] = " '' "; - } - - $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] ); - break; - case 'OPT': - case 'CONSTRAINT': - // Accept platform-specific options - $this->currentPlatform = ( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ); - break; - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Table/field constraint - case 'CONSTRAINT': - if( isset( $this->current_field ) ) { - $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata ); - } else { - $this->addTableOpt( $cdata ); - } - break; - // Table/field option - case 'OPT': - if( isset( $this->current_field ) ) { - $this->addFieldOpt( $this->current_field, $cdata ); - } else { - $this->addTableOpt( $cdata ); - } - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'TABLE': - $this->parent->addSQL( $this->create( $this->parent ) ); - xml_set_object( $parser, $this->parent ); - $this->destroy(); - break; - case 'FIELD': - unset($this->current_field); - break; - case 'OPT': - case 'CONSTRAINT': - $this->currentPlatform = true; - break; - default: - - } - } - - /** - * 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 ); - return $this->indexes[$name]; - } - - /** - * 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 ); - } - return $this->data; - } - - /** - * 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 - */ - function addField( $name, $type, $size = NULL, $opts = NULL ) { - $field_id = $this->FieldID( $name ); - - // Set the field index so we know where we are - $this->current_field = $field_id; - - // Set the field name (required) - $this->fields[$field_id]['NAME'] = $name; - - // Set the field type (required) - $this->fields[$field_id]['TYPE'] = $type; - - // Set the field size (optional) - if( isset( $size ) ) { - $this->fields[$field_id]['SIZE'] = $size; - } - - // Set the field options - if( isset( $opts ) ) { - $this->fields[$field_id]['OPTS'] = array($opts); - } else { - $this->fields[$field_id]['OPTS'] = 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 array Field specifier array - */ - function addFieldOpt( $field, $opt, $value = NULL ) { - if( $this->currentPlatform ) { - if( !isset( $value ) ) { - $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt; - // Add the option and value - } else { - $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value ); - } - } - } - - /** - * 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( $this->currentPlatform ) { - $this->opts[] = $opt; - } - return $this->opts; - } - - /** - * 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(); - - // drop any existing indexes - if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) { - foreach( $legacy_indexes as $index => $index_details ) { - $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name ); - } - } - - // remove fields to be dropped from table object - foreach( $this->drop_field as $field ) { - unset( $this->fields[$field] ); - } - - // if table exists - if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) { - // drop table - if( $this->drop_table ) { - $sql[] = $xmls->dict->DropTableSQL( $this->name ); - - return $sql; - } - - // drop any existing fields not in schema - foreach( $legacy_fields as $field_id => $field ) { - if( !isset( $this->fields[$field_id] ) ) { - $sql[] = $xmls->dict->DropColumnSQL( $this->name, $field->name ); - } - } - // if table doesn't exist - } else { - if( $this->drop_table ) { - return $sql; - } - - $legacy_fields = array(); - } - - // Loop through the field specifier array, building the associative array for the field options - $fldarray = array(); - - foreach( $this->fields as $field_id => $finfo ) { - // Set an empty size if it isn't supplied - if( !isset( $finfo['SIZE'] ) ) { - $finfo['SIZE'] = ''; - } - - // Initialize the field array with the type and size - $fldarray[$field_id] = array( - 'NAME' => $finfo['NAME'], - 'TYPE' => $finfo['TYPE'], - 'SIZE' => $finfo['SIZE'] - ); - - // Loop through the options array and add the field options. - if( isset( $finfo['OPTS'] ) ) { - foreach( $finfo['OPTS'] as $opt ) { - // Option has an argument. - if( is_array( $opt ) ) { - $key = key( $opt ); - $value = $opt[key( $opt )]; - @$fldarray[$field_id][$key] .= $value; - // Option doesn't have arguments - } else { - $fldarray[$field_id][$opt] = $opt; - } - } - } - } - - if( empty( $legacy_fields ) ) { - // Create the new table - $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); - logMsg( end( $sql ), 'Generated CreateTableSQL' ); - } else { - // Upgrade an existing table - logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" ); - switch( $xmls->upgrade ) { - // Use ChangeTableSQL - case 'ALTER': - logMsg( 'Generated ChangeTableSQL (ALTERing table)' ); - $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts ); - break; - case 'REPLACE': - logMsg( 'Doing upgrade REPLACE (testing)' ); - $sql[] = $xmls->dict->DropTableSQL( $this->name ); - $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); - break; - // ignore table - default: - return array(); - } - } - - foreach( $this->indexes as $index ) { - $sql[] = $index->create( $xmls ); - } - - if( isset( $this->data ) ) { - $sql[] = $this->data->create( $xmls ); - } - - return $sql; - } - - /** - * Marks a field or table for destruction - */ - function drop() { - if( isset( $this->current_field ) ) { - // Drop the current field - logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" ); - // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field ); - $this->drop_field[$this->current_field] = $this->current_field; - } else { - // Drop the current table - logMsg( "Dropping table '{$this->name}'" ); - // $this->drop_table = $xmls->dict->DropTableSQL( $this->name ); - $this->drop_table = TRUE; - } - } -} - -/** -* 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 $name; - - /** - * @var array Index options: Index-level options - */ - var $opts = array(); - - /** - * @var array Indexed fields: Table columns included in this index - */ - var $columns = array(); - - /** - * @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 dbIndex( &$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 - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'DROP': - $this->drop(); - break; - case 'CLUSTERED': - case 'BITMAP': - case 'UNIQUE': - case 'FULLTEXT': - case 'HASH': - // Add index Option - $this->addIndexOpt( $this->currentElement ); - break; - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - * - * Processes XML cdata. - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Index field name - case 'COL': - $this->addField( $cdata ); - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'INDEX': - xml_set_object( $parser, $this->parent ); - break; - } - } - - /** - * Adds a field to the index - * - * @param string $name Field name - * @return string Field list - */ - function addField( $name ) { - $this->columns[$this->FieldID( $name )] = $name; - - // Return the field list - return $this->columns; - } - - /** - * Adds options to the index - * - * @param string $opt Comma-separated list of index options. - * @return string Option list - */ - function addIndexOpt( $opt ) { - $this->opts[] = $opt; - - // Return the options list - return $this->opts; - } - - /** - * 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; - } - - // eliminate any columns that aren't in the table - foreach( $this->columns as $id => $col ) { - if( !isset( $this->parent->fields[$id] ) ) { - unset( $this->columns[$id] ); - } - } - - return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts ); - } - - /** - * 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 -*/ -class dbData extends dbObject { - - var $data = array(); - - var $row; - - /** - * Initializes the new dbData object. - * - * @param object $parent Parent object - * @param array $attributes Attributes - * - * @internal - */ - function dbData( &$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 - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'ROW': - $this->row = count( $this->data ); - $this->data[$this->row] = array(); - break; - case 'F': - $this->addField($attributes); - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - * - * Processes XML cdata. - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Index field name - case 'F': - $this->addData( $cdata ); - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'DATA': - xml_set_object( $parser, $this->parent ); - break; - } - } - - /** - * 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] ) ) { - return; - } - - // Set the field index so we know where we are - if( isset( $attributes['NAME'] ) ) { - $this->current_field = $this->FieldID( $attributes['NAME'] ); - } else { - $this->current_field = count( $this->data[$this->row] ); - } - - // initialise data - if( !isset( $this->data[$this->row][$this->current_field] ) ) { - $this->data[$this->row][$this->current_field] = ''; - } - } - - /** - * Adds options to the index - * - * @param string $opt Comma-separated list of index options. - * @return string Option list - */ - function addData( $cdata ) { - // check we're in a valid field - if ( isset( $this->data[$this->row][$this->current_field] ) ) { - // add data to field - $this->data[$this->row][$this->current_field] .= $cdata; - } - } - - /** - * 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); - $tables = $xmls->db->MetaTables(); - $sql = array(); - - $ukeys = $xmls->db->MetaPrimaryKeys( $table ); - if( !empty( $this->parent->indexes ) and !empty( $ukeys ) ) { - foreach( $this->parent->indexes as $indexObj ) { - if( !in_array( $indexObj->name, $ukeys ) ) $ukeys[] = $indexObj->name; - } - } - - // eliminate any columns that aren't in the table - foreach( $this->data as $row ) { - $table_fields = $this->parent->fields; - $fields = array(); - $rawfields = array(); // Need to keep some of the unprocessed data on hand. - - 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 ) ); - } else { - continue; - } - } - - $name = $table_fields[$field_id]['NAME']; - - switch( $table_fields[$field_id]['TYPE'] ) { - case 'I': - case 'I1': - case 'I2': - case 'I4': - case 'I8': - $fields[$name] = intval($field_data); - break; - case 'C': - case 'C2': - case 'X': - case 'X2': - default: - $fields[$name] = $xmls->db->qstr( $field_data ); - $rawfields[$name] = $field_data; - } - - unset($table_fields[$field_id]); - - } - - // check that at least 1 column is specified - if( empty( $fields ) ) { - continue; - } - - // check that no required columns are missing - if( count( $fields ) < $table_field_count ) { - foreach( $table_fields as $field ) { - if( isset( $field['OPTS'] ) and ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) { - continue(2); - } - } - } - - // The rest of this method deals with updating existing data records. - - if( !in_array( $table, $tables ) or ( $mode = $xmls->existingData() ) == XMLS_MODE_INSERT ) { - // Table doesn't yet exist, so it's safe to insert. - logMsg( "$table doesn't exist, inserting or mode is INSERT" ); - $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')'; - continue; - } - - // Prepare to test for potential violations. Get primary keys and unique indexes - $mfields = array_merge( $fields, $rawfields ); - $keyFields = array_intersect( $ukeys, array_keys( $mfields ) ); - - if( empty( $ukeys ) or count( $keyFields ) == 0 ) { - // No unique keys in schema, so safe to insert - logMsg( "Either schema or data has no unique keys, so safe to insert" ); - $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')'; - continue; - } - - // Select record containing matching unique keys. - $where = ''; - foreach( $ukeys as $key ) { - if( isset( $mfields[$key] ) and $mfields[$key] ) { - if( $where ) $where .= ' AND '; - $where .= $key . ' = ' . $xmls->db->qstr( $mfields[$key] ); - } - } - $records = $xmls->db->Execute( 'SELECT * FROM ' . $table . ' WHERE ' . $where ); - switch( $records->RecordCount() ) { - case 0: - // No matching record, so safe to insert. - logMsg( "No matching records. Inserting new row with unique data" ); - $sql[] = $xmls->db->GetInsertSQL( $records, $mfields ); - break; - case 1: - // Exactly one matching record, so we can update if the mode permits. - logMsg( "One matching record..." ); - if( $mode == XMLS_MODE_UPDATE ) { - logMsg( "...Updating existing row from unique data" ); - $sql[] = $xmls->db->GetUpdateSQL( $records, $mfields ); - } - break; - default: - // More than one matching record; the result is ambiguous, so we must ignore the row. - logMsg( "More than one matching record. Ignoring row." ); - } - } - return $sql; - } -} - -/** -* 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 $queries = array(); - - /** - * @var string String used to build of a query line by line - */ - var $query; - - /** - * @var string Query prefix key - */ - var $prefixKey = ''; - - /** - * @var boolean Auto prefix enable (TRUE) - */ - var $prefixMethod = 'AUTO'; - - /** - * Initializes the query set. - * - * @param object $parent Parent object - * @param array $attributes Attributes - */ - function dbQuerySet( &$parent, $attributes = NULL ) { - $this->parent = $parent; - - // Overrides the manual prefix key - if( isset( $attributes['KEY'] ) ) { - $this->prefixKey = $attributes['KEY']; - } - - $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : ''; - - // Enables or disables automatic prefix prepending - switch( $prefixMethod ) { - case 'AUTO': - $this->prefixMethod = 'AUTO'; - break; - case 'MANUAL': - $this->prefixMethod = 'MANUAL'; - break; - case 'NONE': - $this->prefixMethod = 'NONE'; - break; - } - } - - /** - * XML Callback to process start elements. Elements currently - * processed are: QUERY. - * - * @access private - */ - function _tag_open( &$parser, $tag, $attributes ) { - $this->currentElement = strtoupper( $tag ); - - switch( $this->currentElement ) { - case 'QUERY': - // Create a new query in a SQL queryset. - // Ignore this query set if a platform is specified and it's different than the - // current connection platform. - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - $this->newQuery(); - } else { - $this->discardQuery(); - } - break; - default: - // print_r( array( $tag, $attributes ) ); - } - } - - /** - * XML Callback to process CDATA elements - */ - function _tag_cdata( &$parser, $cdata ) { - switch( $this->currentElement ) { - // Line of queryset SQL data - case 'QUERY': - $this->buildQuery( $cdata ); - break; - default: - - } - } - - /** - * XML Callback to process end elements - * - * @access private - */ - function _tag_close( &$parser, $tag ) { - $this->currentElement = ''; - - switch( strtoupper( $tag ) ) { - case 'QUERY': - // Add the finished query to the open query set. - $this->addQuery(); - break; - case 'SQL': - $this->parent->addSQL( $this->create( $this->parent ) ); - xml_set_object( $parser, $this->parent ); - $this->destroy(); - break; - default: - - } - } - - /** - * Re-initializes the query. - * - * @return boolean TRUE - */ - function newQuery() { - $this->query = ''; - - return TRUE; - } - - /** - * Discards the existing query. - * - * @return boolean TRUE - */ - function discardQuery() { - unset( $this->query ); - - return TRUE; - } - - /** - * 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; - } - - $this->query .= $sql; - - return $this->query; - } - - /** - * Adds a completed query to the query list - * - * @return string SQL of added query - */ - function addQuery() { - if( !isset( $this->query ) ) { - return FALSE; - } - - $this->queries[] = $return = trim($this->query); - - unset( $this->query ); - - return $return; - } - - /** - * 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 ) { - case 'AUTO': - // Enable auto prefix replacement - - // Process object prefix. - // Evaluate SQL statements to prepend prefix to objects - $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); - $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); - $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); - - // SELECT statements aren't working yet - #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data ); - - case 'MANUAL': - // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX. - // If prefixKey is not set, we use the default constant XMLS_PREFIX - if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) { - // Enable prefix override - $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query ); - } else { - // Use default replacement - $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query ); - } - } - - $this->queries[$id] = trim( $query ); - } - - // Return the query set array - return $this->queries; - } - - /** - * 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; - } - - if( preg_match( $regex, $query, $match ) ) { - $preamble = $match[1]; - $postamble = $match[5]; - $objectList = explode( ',', $match[3] ); - // $prefix = $prefix . '_'; - - $prefixedList = ''; - - foreach( $objectList as $object ) { - if( $prefixedList !== '' ) { - $prefixedList .= ', '; - } - - $prefixedList .= $prefix . trim( $object ); - } - - $query = $preamble . ' ' . $prefixedList . ' ' . $postamble; - } - - return $query; - } -} - -/** -* 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$ -* -* @package axmls -*/ -class adoSchema { - - /** - * @var array Array containing SQL queries to generate all objects - * @access private - */ - var $sqlArray; - - /** - * @var object ADOdb connection object - * @access private - */ - var $db; - - /** - * @var object ADOdb Data Dictionary - * @access private - */ - var $dict; - - /** - * @var string Current XML element - * @access private - */ - var $currentElement = ''; - - /** - * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database - * @access private - */ - var $upgrade = ''; - - /** - * @var string Optional object prefix - * @access private - */ - var $objectPrefix = ''; - - /** - * @var long Original Magic Quotes Runtime value - * @access private - */ - var $mgq; - - /** - * @var long System debug - * @access private - */ - var $debug; - - /** - * @var string Regular expression to find schema version - * @access private - */ - var $versionRegex = '//'; - - /** - * @var string Current schema version - * @access private - */ - var $schemaVersion; - - /** - * @var int Success of last Schema execution - */ - var $success; - - /** - * @var bool Execute SQL inline as it is generated - */ - var $executeInline; - - /** - * @var bool Continue SQL execution if errors occur - */ - var $continueOnError; - - /** - * @var int How to handle existing data rows (insert, update, or ignore) - */ - var $existingData; - - /** - * 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 adoSchema( &$db ) { - // Initialize the environment - $this->mgq = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); - - $this->db = $db; - $this->debug = $this->db->debug; - $this->dict = NewDataDictionary( $this->db ); - $this->sqlArray = array(); - $this->schemaVersion = XMLS_SCHEMA_VERSION; - $this->executeInline( XMLS_EXECUTE_INLINE ); - $this->continueOnError( XMLS_CONTINUE_ON_ERROR ); - $this->existingData( XMLS_EXISTING_DATA ); - $this->setUpgradeMethod(); - } - - /** - * 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; - } - - $method = strtoupper( $method ); - - // Handle the upgrade methods - switch( $method ) { - case 'ALTER': - $this->upgrade = $method; - break; - case 'REPLACE': - $this->upgrade = $method; - break; - case 'BEST': - $this->upgrade = 'ALTER'; - break; - case 'NONE': - $this->upgrade = 'NONE'; - break; - default: - // Use default if no legitimate method is passed. - $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD; - } - - return $this->upgrade; - } - - /** - * 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 ) { - case XMLS_MODE_UPDATE: - $mode = XMLS_MODE_UPDATE; - break; - case XMLS_MODE_IGNORE: - $mode = XMLS_MODE_IGNORE; - break; - case XMLS_MODE_INSERT: - $mode = XMLS_MODE_INSERT; - break; - default: - $mode = XMLS_EXISITNG_DATA; - break; - } - $this->existingData = $mode; - } - - return $this->existingData; - } - - /** - * 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() - */ - function ExecuteInline( $mode = NULL ) { - if( is_bool( $mode ) ) { - $this->executeInline = $mode; - } - - return $this->executeInline; - } - - /** - * 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() - */ - function ContinueOnError( $mode = NULL ) { - if( is_bool( $mode ) ) { - $this->continueOnError = $mode; - } - - return $this->continueOnError; - } - - /** - * 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() - */ - function ParseSchemaFile( $filename, $returnSchema = FALSE ) { - // Open the file - if( !($fp = fopen( $filename, 'r' )) ) { - logMsg( 'Unable to open file' ); - return FALSE; - } - - // do version detection here - if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) { - logMsg( 'Invalid Schema Version' ); - return FALSE; - } - - if( $returnSchema ) { - $xmlstring = ''; - while( $data = fread( $fp, 4096 ) ) { - $xmlstring .= $data . "\n"; - } - return $xmlstring; - } - - $this->success = 2; - - $xmlParser = $this->create_parser(); - - // Process the file - while( $data = fread( $fp, 4096 ) ) { - if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) { - die( sprintf( - "XML error: %s at line %d", - xml_error_string( xml_get_error_code( $xmlParser) ), - xml_get_current_line_number( $xmlParser) - ) ); - } - } - - xml_parser_free( $xmlParser ); - - return $this->sqlArray; - } - - /** - * 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' ); - return FALSE; - } - - // do version detection here - if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) { - logMsg( 'Invalid Schema Version' ); - return FALSE; - } - - if( $returnSchema ) { - return $xmlstring; - } - - $this->success = 2; - - $xmlParser = $this->create_parser(); - - if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) { - die( sprintf( - "XML error: %s at line %d", - xml_error_string( xml_get_error_code( $xmlParser) ), - xml_get_current_line_number( $xmlParser) - ) ); - } - - xml_parser_free( $xmlParser ); - - return $this->sqlArray; - } - - /** - * 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. - */ - function RemoveSchemaString( $schema, $returnSchema = FALSE ) { - - // grab current version - if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { - return FALSE; - } - - return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema ); - } - - /** - * 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. - */ - function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) { - if( !is_bool( $continueOnErr ) ) { - $continueOnErr = $this->ContinueOnError(); - } - - if( !isset( $sqlArray ) ) { - $sqlArray = $this->sqlArray; - } - - if( !is_array( $sqlArray ) ) { - $this->success = 0; - } else { - $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr ); - } - - return $this->success; - } - - /** - * 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. - */ - function SaveSQL( $filename = './schema.sql' ) { - - if( !isset( $sqlArray ) ) { - $sqlArray = $this->sqlArray; - } - if( !isset( $sqlArray ) ) { - return FALSE; - } - - $fp = fopen( $filename, "w" ); - - foreach( $sqlArray as $key => $query ) { - fwrite( $fp, $query . ";\n" ); - } - fclose( $fp ); - } - - /** - * Create an xml parser - * - * @return object PHP XML parser object - * - * @access private - */ - function create_parser() { - // Create the parser - $xmlParser = xml_parser_create(); - xml_set_object( $xmlParser, $this ); - - // Initialize the XML callback functions - xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' ); - xml_set_character_data_handler( $xmlParser, '_tag_cdata' ); - - return $xmlParser; - } - - /** - * XML Callback to process start elements - * - * @access private - */ - function _tag_open( &$parser, $tag, $attributes ) { - switch( strtoupper( $tag ) ) { - case 'TABLE': - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - $this->obj = new dbTable( $this, $attributes ); - xml_set_object( $parser, $this->obj ); - } - break; - case 'SQL': - if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { - $this->obj = new dbQuerySet( $this, $attributes ); - xml_set_object( $parser, $this->obj ); - } - break; - default: - // print_r( array( $tag, $attributes ) ); - } - - } - - /** - * XML Callback to process CDATA elements - * - * @access private - */ - function _tag_cdata( &$parser, $cdata ) { - } - - /** - * 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. - */ - function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) { - - // grab current version - if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { - return FALSE; - } - - if( !isset ($newVersion) ) { - $newVersion = $this->schemaVersion; - } - - if( $version == $newVersion ) { - $result = $schema; - } else { - $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion); - } - - if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { - fwrite( $fp, $result ); - fclose( $fp ); - } - - 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. - */ - function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) { - - // grab current version - if( !( $version = $this->SchemaFileVersion( $filename ) ) ) { - return FALSE; - } - - if( !isset ($newVersion) ) { - $newVersion = $this->schemaVersion; - } - - if( $version == $newVersion ) { - $result = _file_get_contents( $filename ); - - // remove unicode BOM if present - if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) { - $result = substr( $result, 3 ); - } - } else { - $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' ); - } - - if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { - fwrite( $fp, $result ); - fclose( $fp ); - } - - return $result; - } - - function TransformSchema( $schema, $xsl, $schematype='string' ) - { - // Fail if XSLT extension is not available - if( ! function_exists( 'xslt_create' ) ) { - return FALSE; - } - - $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl'; - - // look for xsl - if( !is_readable( $xsl_file ) ) { - return FALSE; - } - - switch( $schematype ) - { - case 'file': - if( !is_readable( $schema ) ) { - return FALSE; - } - - $schema = _file_get_contents( $schema ); - break; - case 'string': - default: - if( !is_string( $schema ) ) { - return FALSE; - } - } - - $arguments = array ( - '/_xml' => $schema, - '/_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')); - - // process the schema - $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); - - xslt_free ($xh); - - return $result; - } - - /** - * 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( - 'Message Type' => ucfirst( $fields['msgtype'] ), - 'Message Code' => $fields['code'], - 'Message' => $fields['msg'], - 'Error Number' => $errno, - 'Level' => $level - ); - - switch( $fields['URI'] ) { - case 'arg:/_xml': - $msg['Input'] = 'XML'; - break; - case 'arg:/_xsl': - $msg['Input'] = 'XSL'; - break; - default: - $msg['Input'] = $fields['URI']; - } - - $msg['Line'] = $fields['line']; - } else { - $msg = array( - 'Message Type' => 'Error', - 'Error Number' => $errno, - 'Level' => $level, - 'Fields' => var_export( $fields, TRUE ) - ); - } - - $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n" - . '' . "\n"; - - foreach( $msg as $label => $details ) { - $error_details .= '' . "\n"; - } - - $error_details .= '
' . $label . ': ' . htmlentities( $details ) . '
'; - - trigger_error( $error_details, E_USER_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' )) ) { - // die( 'Unable to open file' ); - return FALSE; - } - - // Process the file - while( $data = fread( $fp, 4096 ) ) { - if( preg_match( $this->versionRegex, $data, $matches ) ) { - return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; - } - } - - return FALSE; - } - - /** - * 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; - } - - if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) { - return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; - } - - return FALSE; - } - - /** - * 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 - * @indent string indentation to use - * @prefix string extract only tables with given prefix - * @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 ); - - $schema = '' . "\n" - . '' . "\n"; - - if( is_array( $tables = $this->db->MetaTables( 'TABLES' , ($prefix) ? $prefix.'%' : '') ) ) { - foreach( $tables as $table ) { - if ($stripprefix) $table = str_replace(str_replace('\\_', '_', $pfx ), '', $table); - $schema .= $indent . '' . "\n"; - - // grab details from database - $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE -1' ); - $fields = $this->db->MetaColumns( $table ); - $indexes = $this->db->MetaIndexes( $table ); - - if( is_array( $fields ) ) { - foreach( $fields as $details ) { - $extra = ''; - $content = array(); - - if( isset($details->max_length) && $details->max_length > 0 ) { - $extra .= ' size="' . $details->max_length . '"'; - } - - if( isset($details->primary_key) && $details->primary_key ) { - $content[] = ''; - } elseif( isset($details->not_null) && $details->not_null ) { - $content[] = ''; - } - - if( isset($details->has_default) && $details->has_default ) { - $content[] = ''; - } - - if( isset($details->auto_increment) && $details->auto_increment ) { - $content[] = ''; - } - - if( isset($details->unsigned) && $details->unsigned ) { - $content[] = ''; - } - - // this stops the creation of 'R' columns, - // AUTOINCREMENT is used to create auto columns - $details->primary_key = 0; - $type = $rs->MetaType( $details ); - - $schema .= str_repeat( $indent, 2 ) . '' . "\n"; - } else { - $schema .= "/>\n"; - } - } - } - - if( is_array( $indexes ) ) { - foreach( $indexes as $index => $details ) { - $schema .= str_repeat( $indent, 2 ) . '' . "\n"; - - if( $details['unique'] ) { - $schema .= str_repeat( $indent, 3 ) . '' . "\n"; - } - - foreach( $details['columns'] as $column ) { - $schema .= str_repeat( $indent, 3 ) . '' . htmlentities( $column ) . '' . "\n"; - } - - $schema .= str_repeat( $indent, 2 ) . '' . "\n"; - } - } - - if( $data ) { - $rs = $this->db->Execute( 'SELECT * FROM ' . $table ); - - if( is_object( $rs ) && !$rs->EOF ) { - $schema .= str_repeat( $indent, 2 ) . "\n"; - - while( $row = $rs->FetchRow() ) { - foreach( $row as $key => $val ) { - if ( $val != htmlentities( $val ) ) { - $row[$key] = ''; - } - } - - $schema .= str_repeat( $indent, 3 ) . '' . implode( '', $row ) . "\n"; - } - - $schema .= str_repeat( $indent, 2 ) . "\n"; - } - } - - $schema .= $indent . "
\n"; - } - } - - $this->db->SetFetchMode( $old_mode ); - - $schema .= '
'; - return $schema; - } - - /** - * 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 - case empty( $prefix ): - logMsg( 'Cleared prefix' ); - $this->objectPrefix = ''; - return TRUE; - // prefix too long - case strlen( $prefix ) > XMLS_PREFIX_MAXLEN: - // prefix contains invalid characters - case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ): - logMsg( 'Invalid prefix: ' . $prefix ); - return FALSE; - } - - if( $underscore AND substr( $prefix, -1 ) != '_' ) { - $prefix .= '_'; - } - - // prefix valid - logMsg( 'Set prefix: ' . $prefix ); - $this->objectPrefix = $prefix; - return TRUE; - } - - /** - * 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 ) ) { - // Prepend the object prefix to the table name - // prepend after quote if used - return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name ); - } - - // No prefix set. Use name provided. - return $name; - } - - /** - * 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'; - - if( preg_match( '/^- /', $platform ) ) { - if (preg_match ( $regex, substr( $platform, 2 ) ) ) { - logMsg( 'Platform ' . $platform . ' is NOT supported' ); - return FALSE; - } - } else { - if( !preg_match ( $regex, $platform ) ) { - logMsg( 'Platform ' . $platform . ' is NOT supported' ); - return FALSE; - } - } - } - - logMsg( 'Platform ' . $platform . ' is supported' ); - return TRUE; - } - - /** - * 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 - */ - function addSQL( $sql = NULL ) { - if( is_array( $sql ) ) { - foreach( $sql as $line ) { - $this->addSQL( $line ); - } - - return TRUE; - } - - if( is_string( $sql ) ) { - $this->sqlArray[] = $sql; - - // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL. - if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) { - $saved = $this->db->debug; - $this->db->debug = $this->debug; - $ok = $this->db->Execute( $sql ); - $this->db->debug = $saved; - - if( !$ok ) { - if( $this->debug ) { - ADOConnection::outp( $this->db->ErrorMsg() ); - } - - $this->success = 1; - } - } - - return TRUE; - } - - return FALSE; - } - - /** - * 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; - } - - if( !is_array( $sqlArray ) ) { - return FALSE; - } - - switch( strtolower( $format ) ) { - case 'string': - case 'text': - return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : ''; - case'html': - return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : ''; - } - - return $this->sqlArray; - } - - /** - * 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() { - set_magic_quotes_runtime( $this->mgq ); - unset( $this ); - } -} - -/** -* Message logging function -* -* @access private -*/ -function logMsg( $msg, $title = NULL, $force = FALSE ) { - if( XMLS_DEBUG or $force ) { - echo '
';
-		
-		if( isset( $title ) ) {
-			echo '

' . htmlentities( $title ) . '

'; - } - - if( @is_object( $this ) ) { - echo '[' . get_class( $this ) . '] '; - } - - print_r( $msg ); - - echo '
'; - } -} +parent = $parent; + } + + /** + * XML Callback to process start elements + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + + } + + function create() { + return array(); + } + + /** + * Destroys the object + */ + function destroy() { + unset( $this ); + } + + /** + * 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. + */ + 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. + */ + 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 +*/ +class dbTable extends dbObject { + + /** + * @var string Table name + */ + var $name; + + /** + * @var array Field specifier: Meta-information about each field + */ + var $fields = array(); + + /** + * @var array List of table indexes. + */ + var $indexes = array(); + + /** + * @var array Table options: Table-level options + */ + var $opts = array(); + + /** + * @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 $drop_table; + + /** + * @var boolean Mark field for destruction (not yet implemented) + * @access private + */ + var $drop_field = array(); + + /** + * @var array Platform-specific options + * @access private + */ + var $currentPlatform = true; + + + /** + * Iniitializes a new table object. + * + * @param string $prefix DB Object prefix + * @param array $attributes Array of table attributes. + */ + function dbTable( &$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 + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'INDEX': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + xml_set_object( $parser, $this->addIndex( $attributes ) ); + } + break; + case 'DATA': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + xml_set_object( $parser, $this->addData( $attributes ) ); + } + break; + case 'DROP': + $this->drop(); + break; + case 'FIELD': + // Add a field + $fieldName = $attributes['NAME']; + $fieldType = $attributes['TYPE']; + $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL; + $fieldOpts = !empty( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL; + + $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts ); + break; + case 'KEY': + case 'NOTNULL': + case 'AUTOINCREMENT': + case 'DEFDATE': + case 'DEFTIMESTAMP': + case 'UNSIGNED': + // Add a field option + $this->addFieldOpt( $this->current_field, $this->currentElement ); + break; + case 'DEFAULT': + // Add a field option to the table object + + // Work around ADOdb datadict issue that misinterprets empty strings. + if( $attributes['VALUE'] == '' ) { + $attributes['VALUE'] = " '' "; + } + + $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] ); + break; + case 'OPT': + case 'CONSTRAINT': + // Accept platform-specific options + $this->currentPlatform = ( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ); + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Table/field constraint + case 'CONSTRAINT': + if( isset( $this->current_field ) ) { + $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata ); + } else { + $this->addTableOpt( $cdata ); + } + break; + // Table/field option + case 'OPT': + if( isset( $this->current_field ) ) { + $this->addFieldOpt( $this->current_field, $cdata ); + } else { + $this->addTableOpt( $cdata ); + } + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'TABLE': + $this->parent->addSQL( $this->create( $this->parent ) ); + xml_set_object( $parser, $this->parent ); + $this->destroy(); + break; + case 'FIELD': + unset($this->current_field); + break; + case 'OPT': + case 'CONSTRAINT': + $this->currentPlatform = true; + break; + default: + + } + } + + /** + * 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 ); + return $this->indexes[$name]; + } + + /** + * 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 ); + } + return $this->data; + } + + /** + * 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 + */ + function addField( $name, $type, $size = NULL, $opts = NULL ) { + $field_id = $this->FieldID( $name ); + + // Set the field index so we know where we are + $this->current_field = $field_id; + + // Set the field name (required) + $this->fields[$field_id]['NAME'] = $name; + + // Set the field type (required) + $this->fields[$field_id]['TYPE'] = $type; + + // Set the field size (optional) + if( isset( $size ) ) { + $this->fields[$field_id]['SIZE'] = $size; + } + + // Set the field options + if( isset( $opts ) ) { + $this->fields[$field_id]['OPTS'] = array($opts); + } else { + $this->fields[$field_id]['OPTS'] = 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 array Field specifier array + */ + function addFieldOpt( $field, $opt, $value = NULL ) { + if( $this->currentPlatform ) { + if( !isset( $value ) ) { + $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt; + // Add the option and value + } else { + $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value ); + } + } + } + + /** + * 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; + } + return $this->opts; + } + + + /** + * 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(); + + // drop any existing indexes + if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) { + foreach( $legacy_indexes as $index => $index_details ) { + $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name ); + } + } + + // remove fields to be dropped from table object + foreach( $this->drop_field as $field ) { + unset( $this->fields[$field] ); + } + + // if table exists + if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) { + // drop table + if( $this->drop_table ) { + $sql[] = $xmls->dict->DropTableSQL( $this->name ); + + return $sql; + } + + // drop any existing fields not in schema + foreach( $legacy_fields as $field_id => $field ) { + if( !isset( $this->fields[$field_id] ) ) { + $sql[] = $xmls->dict->DropColumnSQL( $this->name, $field->name ); + } + } + // if table doesn't exist + } else { + if( $this->drop_table ) { + return $sql; + } + + $legacy_fields = array(); + } + + // Loop through the field specifier array, building the associative array for the field options + $fldarray = array(); + + foreach( $this->fields as $field_id => $finfo ) { + // Set an empty size if it isn't supplied + if( !isset( $finfo['SIZE'] ) ) { + $finfo['SIZE'] = ''; + } + + // Initialize the field array with the type and size + $fldarray[$field_id] = array( + 'NAME' => $finfo['NAME'], + 'TYPE' => $finfo['TYPE'], + 'SIZE' => $finfo['SIZE'] + ); + + // Loop through the options array and add the field options. + if( isset( $finfo['OPTS'] ) ) { + foreach( $finfo['OPTS'] as $opt ) { + // Option has an argument. + if( is_array( $opt ) ) { + $key = key( $opt ); + $value = $opt[key( $opt )]; + @$fldarray[$field_id][$key] .= $value; + // Option doesn't have arguments + } else { + $fldarray[$field_id][$opt] = $opt; + } + } + } + } + + if( empty( $legacy_fields ) ) { + // Create the new table + $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); + logMsg( end( $sql ), 'Generated CreateTableSQL' ); + } else { + // Upgrade an existing table + logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" ); + switch( $xmls->upgrade ) { + // Use ChangeTableSQL + case 'ALTER': + logMsg( 'Generated ChangeTableSQL (ALTERing table)' ); + $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts ); + break; + case 'REPLACE': + logMsg( 'Doing upgrade REPLACE (testing)' ); + $sql[] = $xmls->dict->DropTableSQL( $this->name ); + $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); + break; + // ignore table + default: + return array(); + } + } + + foreach( $this->indexes as $index ) { + $sql[] = $index->create( $xmls ); + } + + if( isset( $this->data ) ) { + $sql[] = $this->data->create( $xmls ); + } + + return $sql; + } + + /** + * Marks a field or table for destruction + */ + function drop() { + if( isset( $this->current_field ) ) { + // Drop the current field + logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" ); + // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field ); + $this->drop_field[$this->current_field] = $this->current_field; + } else { + // Drop the current table + logMsg( "Dropping table '{$this->name}'" ); + // $this->drop_table = $xmls->dict->DropTableSQL( $this->name ); + $this->drop_table = TRUE; + } + } +} + +/** +* 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 $name; + + /** + * @var array Index options: Index-level options + */ + var $opts = array(); + + /** + * @var array Indexed fields: Table columns included in this index + */ + var $columns = array(); + + /** + * @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 dbIndex( &$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 + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'DROP': + $this->drop(); + break; + case 'CLUSTERED': + case 'BITMAP': + case 'UNIQUE': + case 'FULLTEXT': + case 'HASH': + // Add index Option + $this->addIndexOpt( $this->currentElement ); + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * Processes XML cdata. + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Index field name + case 'COL': + $this->addField( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'INDEX': + xml_set_object( $parser, $this->parent ); + break; + } + } + + /** + * Adds a field to the index + * + * @param string $name Field name + * @return string Field list + */ + function addField( $name ) { + $this->columns[$this->FieldID( $name )] = $name; + + // Return the field list + return $this->columns; + } + + /** + * Adds options to the index + * + * @param string $opt Comma-separated list of index options. + * @return string Option list + */ + function addIndexOpt( $opt ) { + $this->opts[] = $opt; + + // Return the options list + return $this->opts; + } + + /** + * 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; + } + + // eliminate any columns that aren't in the table + foreach( $this->columns as $id => $col ) { + if( !isset( $this->parent->fields[$id] ) ) { + unset( $this->columns[$id] ); + } + } + + return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts ); + } + + /** + * 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 +*/ +class dbData extends dbObject { + + var $data = array(); + + var $row; + + /** + * Initializes the new dbData object. + * + * @param object $parent Parent object + * @param array $attributes Attributes + * + * @internal + */ + function dbData( &$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 + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'ROW': + $this->row = count( $this->data ); + $this->data[$this->row] = array(); + break; + case 'F': + $this->addField($attributes); + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * Processes XML cdata. + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Index field name + case 'F': + $this->addData( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'DATA': + xml_set_object( $parser, $this->parent ); + break; + } + } + + /** + * 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] ) ) { + return; + } + + // Set the field index so we know where we are + if( isset( $attributes['NAME'] ) ) { + $this->current_field = $this->FieldID( $attributes['NAME'] ); + } else { + $this->current_field = count( $this->data[$this->row] ); + } + + // initialise data + if( !isset( $this->data[$this->row][$this->current_field] ) ) { + $this->data[$this->row][$this->current_field] = ''; + } + } + + /** + * Adds options to the index + * + * @param string $opt Comma-separated list of index options. + * @return string Option list + */ + function addData( $cdata ) { + // check we're in a valid field + if ( isset( $this->data[$this->row][$this->current_field] ) ) { + // add data to field + $this->data[$this->row][$this->current_field] .= $cdata; + } + } + + /** + * 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); + $tables = $xmls->db->MetaTables(); + $sql = array(); + + $ukeys = $xmls->db->MetaPrimaryKeys( $table ); + if( !empty( $this->parent->indexes ) and !empty( $ukeys ) ) { + foreach( $this->parent->indexes as $indexObj ) { + if( !in_array( $indexObj->name, $ukeys ) ) $ukeys[] = $indexObj->name; + } + } + + // eliminate any columns that aren't in the table + foreach( $this->data as $row ) { + $table_fields = $this->parent->fields; + $fields = array(); + $rawfields = array(); // Need to keep some of the unprocessed data on hand. + + 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 ) ); + } else { + continue; + } + } + + $name = $table_fields[$field_id]['NAME']; + + switch( $table_fields[$field_id]['TYPE'] ) { + case 'I': + case 'I1': + case 'I2': + case 'I4': + case 'I8': + $fields[$name] = intval($field_data); + break; + case 'C': + case 'C2': + case 'X': + case 'X2': + default: + $fields[$name] = $xmls->db->qstr( $field_data ); + $rawfields[$name] = $field_data; + } + + unset($table_fields[$field_id]); + + } + + // check that at least 1 column is specified + if( empty( $fields ) ) { + continue; + } + + // check that no required columns are missing + if( count( $fields ) < $table_field_count ) { + foreach( $table_fields as $field ) { + if( isset( $field['OPTS'] ) and ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) { + continue(2); + } + } + } + + // The rest of this method deals with updating existing data records. + + if( !in_array( $table, $tables ) or ( $mode = $xmls->existingData() ) == XMLS_MODE_INSERT ) { + // Table doesn't yet exist, so it's safe to insert. + logMsg( "$table doesn't exist, inserting or mode is INSERT" ); + $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')'; + continue; + } + + // Prepare to test for potential violations. Get primary keys and unique indexes + $mfields = array_merge( $fields, $rawfields ); + $keyFields = array_intersect( $ukeys, array_keys( $mfields ) ); + + if( empty( $ukeys ) or count( $keyFields ) == 0 ) { + // No unique keys in schema, so safe to insert + logMsg( "Either schema or data has no unique keys, so safe to insert" ); + $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')'; + continue; + } + + // Select record containing matching unique keys. + $where = ''; + foreach( $ukeys as $key ) { + if( isset( $mfields[$key] ) and $mfields[$key] ) { + if( $where ) $where .= ' AND '; + $where .= $key . ' = ' . $xmls->db->qstr( $mfields[$key] ); + } + } + $records = $xmls->db->Execute( 'SELECT * FROM ' . $table . ' WHERE ' . $where ); + switch( $records->RecordCount() ) { + case 0: + // No matching record, so safe to insert. + logMsg( "No matching records. Inserting new row with unique data" ); + $sql[] = $xmls->db->GetInsertSQL( $records, $mfields ); + break; + case 1: + // Exactly one matching record, so we can update if the mode permits. + logMsg( "One matching record..." ); + if( $mode == XMLS_MODE_UPDATE ) { + logMsg( "...Updating existing row from unique data" ); + $sql[] = $xmls->db->GetUpdateSQL( $records, $mfields ); + } + break; + default: + // More than one matching record; the result is ambiguous, so we must ignore the row. + logMsg( "More than one matching record. Ignoring row." ); + } + } + return $sql; + } +} + +/** +* 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 $queries = array(); + + /** + * @var string String used to build of a query line by line + */ + var $query; + + /** + * @var string Query prefix key + */ + var $prefixKey = ''; + + /** + * @var boolean Auto prefix enable (TRUE) + */ + var $prefixMethod = 'AUTO'; + + /** + * Initializes the query set. + * + * @param object $parent Parent object + * @param array $attributes Attributes + */ + function dbQuerySet( &$parent, $attributes = NULL ) { + $this->parent = $parent; + + // Overrides the manual prefix key + if( isset( $attributes['KEY'] ) ) { + $this->prefixKey = $attributes['KEY']; + } + + $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : ''; + + // Enables or disables automatic prefix prepending + switch( $prefixMethod ) { + case 'AUTO': + $this->prefixMethod = 'AUTO'; + break; + case 'MANUAL': + $this->prefixMethod = 'MANUAL'; + break; + case 'NONE': + $this->prefixMethod = 'NONE'; + break; + } + } + + /** + * XML Callback to process start elements. Elements currently + * processed are: QUERY. + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'QUERY': + // Create a new query in a SQL queryset. + // Ignore this query set if a platform is specified and it's different than the + // current connection platform. + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + $this->newQuery(); + } else { + $this->discardQuery(); + } + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Line of queryset SQL data + case 'QUERY': + $this->buildQuery( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'QUERY': + // Add the finished query to the open query set. + $this->addQuery(); + break; + case 'SQL': + $this->parent->addSQL( $this->create( $this->parent ) ); + xml_set_object( $parser, $this->parent ); + $this->destroy(); + break; + default: + + } + } + + /** + * Re-initializes the query. + * + * @return boolean TRUE + */ + function newQuery() { + $this->query = ''; + + return TRUE; + } + + /** + * Discards the existing query. + * + * @return boolean TRUE + */ + function discardQuery() { + unset( $this->query ); + + return TRUE; + } + + /** + * 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; + } + + $this->query .= $sql; + + return $this->query; + } + + /** + * Adds a completed query to the query list + * + * @return string SQL of added query + */ + function addQuery() { + if( !isset( $this->query ) ) { + return FALSE; + } + + $this->queries[] = $return = trim($this->query); + + unset( $this->query ); + + return $return; + } + + /** + * 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 ) { + case 'AUTO': + // Enable auto prefix replacement + + // Process object prefix. + // Evaluate SQL statements to prepend prefix to objects + $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + + // SELECT statements aren't working yet + #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data ); + + case 'MANUAL': + // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX. + // If prefixKey is not set, we use the default constant XMLS_PREFIX + if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) { + // Enable prefix override + $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query ); + } else { + // Use default replacement + $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query ); + } + } + + $this->queries[$id] = trim( $query ); + } + + // Return the query set array + return $this->queries; + } + + /** + * 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; + } + + if( preg_match( $regex, $query, $match ) ) { + $preamble = $match[1]; + $postamble = $match[5]; + $objectList = explode( ',', $match[3] ); + // $prefix = $prefix . '_'; + + $prefixedList = ''; + + foreach( $objectList as $object ) { + if( $prefixedList !== '' ) { + $prefixedList .= ', '; + } + + $prefixedList .= $prefix . trim( $object ); + } + + $query = $preamble . ' ' . $prefixedList . ' ' . $postamble; + } + + return $query; + } +} + +/** +* 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$ +* +* @package axmls +*/ +class adoSchema { + + /** + * @var array Array containing SQL queries to generate all objects + * @access private + */ + var $sqlArray; + + /** + * @var object ADOdb connection object + * @access private + */ + var $db; + + /** + * @var object ADOdb Data Dictionary + * @access private + */ + var $dict; + + /** + * @var string Current XML element + * @access private + */ + var $currentElement = ''; + + /** + * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database + * @access private + */ + var $upgrade = ''; + + /** + * @var string Optional object prefix + * @access private + */ + var $objectPrefix = ''; + + /** + * @var long Original Magic Quotes Runtime value + * @access private + */ + var $mgq; + + /** + * @var long System debug + * @access private + */ + var $debug; + + /** + * @var string Regular expression to find schema version + * @access private + */ + var $versionRegex = '//'; + + /** + * @var string Current schema version + * @access private + */ + var $schemaVersion; + + /** + * @var int Success of last Schema execution + */ + var $success; + + /** + * @var bool Execute SQL inline as it is generated + */ + var $executeInline; + + /** + * @var bool Continue SQL execution if errors occur + */ + var $continueOnError; + + /** + * @var int How to handle existing data rows (insert, update, or ignore) + */ + var $existingData; + + /** + * 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 adoSchema( &$db ) { + // Initialize the environment + $this->mgq = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + + $this->db = $db; + $this->debug = $this->db->debug; + $this->dict = NewDataDictionary( $this->db ); + $this->sqlArray = array(); + $this->schemaVersion = XMLS_SCHEMA_VERSION; + $this->executeInline( XMLS_EXECUTE_INLINE ); + $this->continueOnError( XMLS_CONTINUE_ON_ERROR ); + $this->existingData( XMLS_EXISTING_DATA ); + $this->setUpgradeMethod(); + } + + /** + * 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; + } + + $method = strtoupper( $method ); + + // Handle the upgrade methods + switch( $method ) { + case 'ALTER': + $this->upgrade = $method; + break; + case 'REPLACE': + $this->upgrade = $method; + break; + case 'BEST': + $this->upgrade = 'ALTER'; + break; + case 'NONE': + $this->upgrade = 'NONE'; + break; + default: + // Use default if no legitimate method is passed. + $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD; + } + + return $this->upgrade; + } + + /** + * 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 ) { + case XMLS_MODE_UPDATE: + $mode = XMLS_MODE_UPDATE; + break; + case XMLS_MODE_IGNORE: + $mode = XMLS_MODE_IGNORE; + break; + case XMLS_MODE_INSERT: + $mode = XMLS_MODE_INSERT; + break; + default: + $mode = XMLS_EXISTING_DATA; + break; + } + $this->existingData = $mode; + } + + return $this->existingData; + } + + /** + * 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() + */ + function ExecuteInline( $mode = NULL ) { + if( is_bool( $mode ) ) { + $this->executeInline = $mode; + } + + return $this->executeInline; + } + + /** + * 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() + */ + function ContinueOnError( $mode = NULL ) { + if( is_bool( $mode ) ) { + $this->continueOnError = $mode; + } + + return $this->continueOnError; + } + + /** + * 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() + */ + function ParseSchemaFile( $filename, $returnSchema = FALSE ) { + // Open the file + if( !($fp = fopen( $filename, 'r' )) ) { + logMsg( 'Unable to open file' ); + return FALSE; + } + + // do version detection here + if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) { + logMsg( 'Invalid Schema Version' ); + return FALSE; + } + + if( $returnSchema ) { + $xmlstring = ''; + while( $data = fread( $fp, 4096 ) ) { + $xmlstring .= $data . "\n"; + } + return $xmlstring; + } + + $this->success = 2; + + $xmlParser = $this->create_parser(); + + // Process the file + while( $data = fread( $fp, 4096 ) ) { + if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) { + die( sprintf( + "XML error: %s at line %d", + xml_error_string( xml_get_error_code( $xmlParser) ), + xml_get_current_line_number( $xmlParser) + ) ); + } + } + + xml_parser_free( $xmlParser ); + + return $this->sqlArray; + } + + /** + * 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' ); + return FALSE; + } + + // do version detection here + if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) { + logMsg( 'Invalid Schema Version' ); + return FALSE; + } + + if( $returnSchema ) { + return $xmlstring; + } + + $this->success = 2; + + $xmlParser = $this->create_parser(); + + if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) { + die( sprintf( + "XML error: %s at line %d", + xml_error_string( xml_get_error_code( $xmlParser) ), + xml_get_current_line_number( $xmlParser) + ) ); + } + + xml_parser_free( $xmlParser ); + + return $this->sqlArray; + } + + /** + * 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. + */ + function RemoveSchemaString( $schema, $returnSchema = FALSE ) { + + // grab current version + if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { + return FALSE; + } + + return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema ); + } + + /** + * 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. + */ + function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) { + if( !is_bool( $continueOnErr ) ) { + $continueOnErr = $this->ContinueOnError(); + } + + if( !isset( $sqlArray ) ) { + $sqlArray = $this->sqlArray; + } + + if( !is_array( $sqlArray ) ) { + $this->success = 0; + } else { + $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr ); + } + + return $this->success; + } + + /** + * 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. + */ + function SaveSQL( $filename = './schema.sql' ) { + + if( !isset( $sqlArray ) ) { + $sqlArray = $this->sqlArray; + } + if( !isset( $sqlArray ) ) { + return FALSE; + } + + $fp = fopen( $filename, "w" ); + + foreach( $sqlArray as $key => $query ) { + fwrite( $fp, $query . ";\n" ); + } + fclose( $fp ); + } + + /** + * Create an xml parser + * + * @return object PHP XML parser object + * + * @access private + */ + function create_parser() { + // Create the parser + $xmlParser = xml_parser_create(); + xml_set_object( $xmlParser, $this ); + + // Initialize the XML callback functions + xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' ); + xml_set_character_data_handler( $xmlParser, '_tag_cdata' ); + + return $xmlParser; + } + + /** + * XML Callback to process start elements + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + switch( strtoupper( $tag ) ) { + case 'TABLE': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + $this->obj = new dbTable( $this, $attributes ); + xml_set_object( $parser, $this->obj ); + } + break; + case 'SQL': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + $this->obj = new dbQuerySet( $this, $attributes ); + xml_set_object( $parser, $this->obj ); + } + break; + default: + // print_r( array( $tag, $attributes ) ); + } + + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + } + + /** + * 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. + */ + function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) { + + // grab current version + if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { + return FALSE; + } + + if( !isset ($newVersion) ) { + $newVersion = $this->schemaVersion; + } + + if( $version == $newVersion ) { + $result = $schema; + } else { + $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion); + } + + if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { + fwrite( $fp, $result ); + fclose( $fp ); + } + + 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. + */ + function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) { + + // grab current version + if( !( $version = $this->SchemaFileVersion( $filename ) ) ) { + return FALSE; + } + + if( !isset ($newVersion) ) { + $newVersion = $this->schemaVersion; + } + + if( $version == $newVersion ) { + $result = _file_get_contents( $filename ); + + // remove unicode BOM if present + if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) { + $result = substr( $result, 3 ); + } + } else { + $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' ); + } + + if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { + fwrite( $fp, $result ); + fclose( $fp ); + } + + return $result; + } + + function TransformSchema( $schema, $xsl, $schematype='string' ) + { + // Fail if XSLT extension is not available + if( ! function_exists( 'xslt_create' ) ) { + return FALSE; + } + + $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl'; + + // look for xsl + if( !is_readable( $xsl_file ) ) { + return FALSE; + } + + switch( $schematype ) + { + case 'file': + if( !is_readable( $schema ) ) { + return FALSE; + } + + $schema = _file_get_contents( $schema ); + break; + case 'string': + default: + if( !is_string( $schema ) ) { + return FALSE; + } + } + + $arguments = array ( + '/_xml' => $schema, + '/_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')); + + // process the schema + $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); + + xslt_free ($xh); + + return $result; + } + + /** + * 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( + 'Message Type' => ucfirst( $fields['msgtype'] ), + 'Message Code' => $fields['code'], + 'Message' => $fields['msg'], + 'Error Number' => $errno, + 'Level' => $level + ); + + switch( $fields['URI'] ) { + case 'arg:/_xml': + $msg['Input'] = 'XML'; + break; + case 'arg:/_xsl': + $msg['Input'] = 'XSL'; + break; + default: + $msg['Input'] = $fields['URI']; + } + + $msg['Line'] = $fields['line']; + } else { + $msg = array( + 'Message Type' => 'Error', + 'Error Number' => $errno, + 'Level' => $level, + 'Fields' => var_export( $fields, TRUE ) + ); + } + + $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n" + . '' . "\n"; + + foreach( $msg as $label => $details ) { + $error_details .= '' . "\n"; + } + + $error_details .= '
' . $label . ': ' . htmlentities( $details ) . '
'; + + trigger_error( $error_details, E_USER_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' )) ) { + // die( 'Unable to open file' ); + return FALSE; + } + + // Process the file + while( $data = fread( $fp, 4096 ) ) { + if( preg_match( $this->versionRegex, $data, $matches ) ) { + return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; + } + } + + return FALSE; + } + + /** + * 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; + } + + if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) { + return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; + } + + return FALSE; + } + + /** + * 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 + * @indent string indentation to use + * @prefix string extract only tables with given prefix + * @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 ); + + $schema = '' . "\n" + . '' . "\n"; + + if( is_array( $tables = $this->db->MetaTables( 'TABLES' , ($prefix) ? $prefix.'%' : '') ) ) { + foreach( $tables as $table ) { + if ($stripprefix) $table = str_replace(str_replace('\\_', '_', $pfx ), '', $table); + $schema .= $indent . '' . "\n"; + + // grab details from database + $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE -1' ); + $fields = $this->db->MetaColumns( $table ); + $indexes = $this->db->MetaIndexes( $table ); + + if( is_array( $fields ) ) { + foreach( $fields as $details ) { + $extra = ''; + $content = array(); + + if( isset($details->max_length) && $details->max_length > 0 ) { + $extra .= ' size="' . $details->max_length . '"'; + } + + if( isset($details->primary_key) && $details->primary_key ) { + $content[] = ''; + } elseif( isset($details->not_null) && $details->not_null ) { + $content[] = ''; + } + + if( isset($details->has_default) && $details->has_default ) { + $content[] = ''; + } + + if( isset($details->auto_increment) && $details->auto_increment ) { + $content[] = ''; + } + + if( isset($details->unsigned) && $details->unsigned ) { + $content[] = ''; + } + + // this stops the creation of 'R' columns, + // AUTOINCREMENT is used to create auto columns + $details->primary_key = 0; + $type = $rs->MetaType( $details ); + + $schema .= str_repeat( $indent, 2 ) . '' . "\n"; + } else { + $schema .= "/>\n"; + } + } + } + + if( is_array( $indexes ) ) { + foreach( $indexes as $index => $details ) { + $schema .= str_repeat( $indent, 2 ) . '' . "\n"; + + if( $details['unique'] ) { + $schema .= str_repeat( $indent, 3 ) . '' . "\n"; + } + + foreach( $details['columns'] as $column ) { + $schema .= str_repeat( $indent, 3 ) . '' . htmlentities( $column ) . '' . "\n"; + } + + $schema .= str_repeat( $indent, 2 ) . '' . "\n"; + } + } + + if( $data ) { + $rs = $this->db->Execute( 'SELECT * FROM ' . $table ); + + if( is_object( $rs ) && !$rs->EOF ) { + $schema .= str_repeat( $indent, 2 ) . "\n"; + + while( $row = $rs->FetchRow() ) { + foreach( $row as $key => $val ) { + if ( $val != htmlentities( $val ) ) { + $row[$key] = ''; + } + } + + $schema .= str_repeat( $indent, 3 ) . '' . implode( '', $row ) . "\n"; + } + + $schema .= str_repeat( $indent, 2 ) . "\n"; + } + } + + $schema .= $indent . "
\n"; + } + } + + $this->db->SetFetchMode( $old_mode ); + + $schema .= '
'; + return $schema; + } + + /** + * 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 + case empty( $prefix ): + logMsg( 'Cleared prefix' ); + $this->objectPrefix = ''; + return TRUE; + // prefix too long + case strlen( $prefix ) > XMLS_PREFIX_MAXLEN: + // prefix contains invalid characters + case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ): + logMsg( 'Invalid prefix: ' . $prefix ); + return FALSE; + } + + if( $underscore AND substr( $prefix, -1 ) != '_' ) { + $prefix .= '_'; + } + + // prefix valid + logMsg( 'Set prefix: ' . $prefix ); + $this->objectPrefix = $prefix; + return TRUE; + } + + /** + * 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 ) ) { + // Prepend the object prefix to the table name + // prepend after quote if used + return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name ); + } + + // No prefix set. Use name provided. + return $name; + } + + /** + * 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'; + + if( preg_match( '/^- /', $platform ) ) { + if (preg_match ( $regex, substr( $platform, 2 ) ) ) { + logMsg( 'Platform ' . $platform . ' is NOT supported' ); + return FALSE; + } + } else { + if( !preg_match ( $regex, $platform ) ) { + logMsg( 'Platform ' . $platform . ' is NOT supported' ); + return FALSE; + } + } + } + + logMsg( 'Platform ' . $platform . ' is supported' ); + return TRUE; + } + + /** + * 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 + */ + function addSQL( $sql = NULL ) { + if( is_array( $sql ) ) { + foreach( $sql as $line ) { + $this->addSQL( $line ); + } + + return TRUE; + } + + if( is_string( $sql ) ) { + $this->sqlArray[] = $sql; + + // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL. + if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) { + $saved = $this->db->debug; + $this->db->debug = $this->debug; + $ok = $this->db->Execute( $sql ); + $this->db->debug = $saved; + + if( !$ok ) { + if( $this->debug ) { + ADOConnection::outp( $this->db->ErrorMsg() ); + } + + $this->success = 1; + } + } + + return TRUE; + } + + return FALSE; + } + + /** + * 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; + } + + if( !is_array( $sqlArray ) ) { + return FALSE; + } + + switch( strtolower( $format ) ) { + case 'string': + case 'text': + return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : ''; + case'html': + return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : ''; + } + + return $this->sqlArray; + } + + /** + * 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() { + set_magic_quotes_runtime( $this->mgq ); + unset( $this ); + } +} + +/** +* Message logging function +* +* @access private +*/ +function logMsg( $msg, $title = NULL, $force = FALSE ) { + if( XMLS_DEBUG or $force ) { + echo '
';
+		
+		if( isset( $title ) ) {
+			echo '

' . htmlentities( $title ) . '

'; + } + + if( @is_object( $this ) ) { + echo '[' . get_class( $this ) . '] '; + } + + print_r( $msg ); + + echo '
'; + } +} ?> \ No newline at end of file diff --git a/lib/adodb/adodb.inc.php b/lib/adodb/adodb.inc.php index 42fc35a2e9..9a619ffb5b 100644 --- a/lib/adodb/adodb.inc.php +++ b/lib/adodb/adodb.inc.php @@ -12,9 +12,9 @@ */ /** - \mainpage + \mainpage - @version V5.04a 25 Mar 2008 (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved. + @version V5.08 6 Apr 2009 (c) 2000-2009 John Lim (jlim#natsoft.com). All rights reserved. Released under both BSD license and Lesser GPL library license. You can choose which license you prefer. @@ -54,9 +54,12 @@ $ADODB_vers, // database version $ADODB_COUNTRECS, // count number of records returned - slows down query $ADODB_CACHE_DIR, // directory to cache recordsets + $ADODB_CACHE, + $ADODB_CACHE_CLASS, $ADODB_EXTENSION, // ADODB extension installed $ADODB_COMPAT_FETCH, // If $ADODB_COUNTRECS and this is true, $rs->fields is available on EOF $ADODB_FETCH_MODE, // DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default... + $ADODB_GETONE_EOF, $ADODB_QUOTE_FIELDNAMES; // Allows you to force quotes (backticks) around field names in queries generated by getinsertsql and getupdatesql. //============================================================================================== @@ -119,6 +122,7 @@ die("PHP5 or later required. You are running ".PHP_VERSION); } + //if (!defined('ADODB_ASSOC_CASE')) define('ADODB_ASSOC_CASE',2); @@ -146,12 +150,16 @@ $ADODB_COUNTRECS, // count number of records returned - slows down query $ADODB_CACHE_DIR, // directory to cache recordsets $ADODB_FETCH_MODE, + $ADODB_CACHE, + $ADODB_CACHE_CLASS, $ADODB_FORCE_TYPE, + $ADODB_GETONE_EOF, $ADODB_QUOTE_FIELDNAMES; + if (empty($ADODB_CACHE_CLASS)) $ADODB_CACHE_CLASS = 'ADODB_Cache_File' ; $ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT; $ADODB_FORCE_TYPE = ADODB_FORCE_VALUE; - + $ADODB_GETONE_EOF = null; if (!isset($ADODB_CACHE_DIR)) { $ADODB_CACHE_DIR = '/tmp'; //(isset($_ENV['TMP'])) ? $_ENV['TMP'] : '/tmp'; @@ -169,7 +177,7 @@ /** * ADODB version as a string. */ - $ADODB_vers = 'V5.04a 25 Mar 2008 (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved. Released BSD & LGPL.'; + $ADODB_vers = 'V5.08 6 Apr 2009 (c) 2000-2009 John Lim (jlim#natsoft.com). All rights reserved. Released BSD & LGPL.'; /** * Determines whether recordset->RecordCount() is used. @@ -208,7 +216,7 @@ */ } - + // for transaction handling function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) { @@ -220,6 +228,95 @@ } } + //------------------ + // class for caching + class ADODB_Cache_File { + + var $createdir = true; // requires creation of temp dirs + + function ADODB_Cache_File() + { + global $ADODB_INCLUDED_CSV; + if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php'); + } + + // write serialised recordset to cache item/file + function writecache($filename, $contents, $debug, $secs2cache) + { + return adodb_write_file($filename, $contents,$debug); + } + + // load serialised recordset and unserialise it + function &readcache($filename, &$err, $secs2cache, $rsClass) + { + $rs = csv2rs($filename,$err,$secs2cache,$rsClass); + return $rs; + } + + // flush all items in cache + function flushall($debug=false) + { + global $ADODB_CACHE_DIR; + + $rez = false; + + if (strlen($ADODB_CACHE_DIR) > 1) { + $rez = $this->_dirFlush($ADODB_CACHE_DIR); + if ($debug) ADOConnection::outp( "flushall: $dir
\n". $rez."
"); + } + return $rez; + } + + // flush one file in cache + function flushcache($f, $debug=false) + { + if (!@unlink($f)) { + if ($debug) ADOConnection::outp( "flushcache: failed for $f"); + } + } + + 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; + } + + // create temp directories + function createdir($hash, $debug) + { + $dir = $this->getdirname($hash); + if ($this->notSafeMode && !file_exists($dir)) { + $oldu = umask(0); + if (!@mkdir($dir,0771)) if(!is_dir($dir) && $debug) ADOConnection::outp("Cannot create $dir"); + umask($oldu); + } + + return $dir; + } + + /** + * Private function to erase all of the files and subdirectories in a directory. + * + * Just specify the directory, and tell it if you want to delete the directory or just clear it out. + * Note: $kill_top_level is used internally in the function to flush subdirectories. + */ + function _dirFlush($dir, $kill_top_level = false) + { + if(!$dh = @opendir($dir)) return; + + while (($obj = readdir($dh))) { + if($obj=='.' || $obj=='..') continue; + $f = $dir.'/'.$obj; + + if (strpos($obj,'.cache')) @unlink($f); + if (is_dir($f)) $this->_dirFlush($f, true); + } + if ($kill_top_level === true) @rmdir($dir); + return true; + } + } + //============================================================================================== // CLASS ADOConnection //============================================================================================== @@ -417,6 +514,10 @@ if ($argDatabaseName != "") $this->database = $argDatabaseName; $this->_isPersistentConnection = false; + + global $ADODB_CACHE; + if (empty($ADODB_CACHE)) $this->_CreateCache(); + if ($forceNew) { if ($rez=$this->_nconnect($this->host, $this->user, $this->password, $this->database)) return true; } else { @@ -472,6 +573,7 @@ */ function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") { + if (defined('ADODB_NEVER_PERSIST')) return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName); @@ -481,6 +583,10 @@ if ($argDatabaseName != "") $this->database = $argDatabaseName; $this->_isPersistentConnection = true; + + global $ADODB_CACHE; + if (empty($ADODB_CACHE)) $this->_CreateCache(); + if ($rez = $this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true; if (isset($rez)) { $err = $this->ErrorMsg(); @@ -508,6 +614,21 @@ ADOConnection::outp($msg); } + // create cache class. Code is backward compat with old memcache implementation + function _CreateCache() + { + global $ADODB_CACHE, $ADODB_CACHE_CLASS; + + if ($this->memCache) { + global $ADODB_INCLUDED_MEMCACHE; + + if (empty($ADODB_INCLUDED_MEMCACHE)) include(ADODB_DIR.'/adodb-memcache.lib.inc.php'); + $ADODB_CACHE = new ADODB_Cache_MemCache($this); + } else + $ADODB_CACHE = new $ADODB_CACHE_CLASS($this); + + } + // Format date column in sql string given an input format that understands Y M D function SQLDate($fmt, $col=false) { @@ -745,7 +866,7 @@ { if ($this->transOff > 0) { $this->transOff += 1; - return; + return true; } $this->_oldRaiseFn = $this->raiseErrorFn; @@ -753,8 +874,9 @@ $this->_transOK = true; if ($this->debug && $this->transCnt > 0) ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans"); - $this->BeginTrans(); + $ok = $this->BeginTrans(); $this->transOff = 1; + return $ok; } @@ -839,7 +961,7 @@ if (!is_array($sql) && !$this->_bindInputArray) { $sqlarr = explode('?',$sql); - + $nparams = sizeof($sqlarr)-1; if (!$array_2d) $inputarr = array($inputarr); foreach($inputarr as $arr) { $sql = ''; $i = 0; @@ -864,7 +986,9 @@ else $sql .= $v; $i += 1; - } + + if ($i == $nparams) break; + } // while if (isset($sqlarr[$i])) { $sql .= $sqlarr[$i]; if ($i+1 != sizeof($sqlarr)) $this->outp_throw( "Input Array does not match ?: ".htmlspecialchars($sql),'Execute'); @@ -1181,13 +1305,10 @@ $savec = $ADODB_COUNTRECS; $ADODB_COUNTRECS = false; - if ($offset>0){ - if ($secs2cache != 0) $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); - else $rs = $this->Execute($sql,$inputarr); - } else { - if ($secs2cache != 0) $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); - else $rs = $this->Execute($sql,$inputarr); - } + + if ($secs2cache != 0) $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); + else $rs = $this->Execute($sql,$inputarr); + $ADODB_COUNTRECS = $savec; if ($rs && !$rs->EOF) { $rs = $this->_rs2rs($rs,$nrows,$offset); @@ -1300,14 +1421,14 @@ */ function GetOne($sql,$inputarr=false) { - global $ADODB_COUNTRECS; + global $ADODB_COUNTRECS,$ADODB_GETONE_EOF; $crecs = $ADODB_COUNTRECS; $ADODB_COUNTRECS = false; $ret = false; $rs = $this->Execute($sql,$inputarr); if ($rs) { - if ($rs->EOF) $ret = null; + if ($rs->EOF) $ret = $ADODB_GETONE_EOF; else $ret = reset($rs->fields); $rs->Close(); @@ -1316,12 +1437,26 @@ return $ret; } + // $where should include 'WHERE fld=value' + function GetMedian($table, $field,$where = '') + { + $total = $this->GetOne("select count(*) from $table $where"); + if (!$total) return false; + + $midrow = (integer) ($total/2); + $rs = $this->SelectLimit("select $field from $table $where order by 1",1,$midrow); + if ($rs && !$rs->EOF) return reset($rs->fields); + return false; + } + + function CacheGetOne($secs2cache,$sql=false,$inputarr=false) { + global $ADODB_GETONE_EOF; $ret = false; $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); if ($rs) { - if ($rs->EOF) $ret = null; + if ($rs->EOF) $ret = $ADODB_GETONE_EOF; else $ret = reset($rs->fields); $rs->Close(); } @@ -1456,7 +1591,7 @@ function GetRandRow($sql, $arr= false) { $rezarr = $this->GetAll($sql, $arr); - $sz = sizeof($rez); + $sz = sizeof($rezarr); return $rezarr[abs(rand()) % $sz]; } @@ -1573,103 +1708,17 @@ */ function CacheFlush($sql=false,$inputarr=false) { - global $ADODB_CACHE_DIR; - - if ($this->memCache) { - global $ADODB_INCLUDED_MEMCACHE; + global $ADODB_CACHE_DIR, $ADODB_CACHE; - $key = false; - if (empty($ADODB_INCLUDED_MEMCACHE)) include(ADODB_DIR.'/adodb-memcache.lib.inc.php'); - if ($sql) $key = $this->_gencachename($sql.serialize($inputarr),false,true); - FlushMemCache($key, $this->memCacheHost, $this->memCachePort, $this->debug); - return; - } - - if (strlen($ADODB_CACHE_DIR) > 1 && !$sql) { - /*if (strncmp(PHP_OS,'WIN',3) === 0) - $dir = str_replace('/', '\\', $ADODB_CACHE_DIR); - else */ - $dir = $ADODB_CACHE_DIR; - - if ($this->debug) { - ADOConnection::outp( "CacheFlush: $dir
\n". $this->_dirFlush($dir)."
"); - } else { - $this->_dirFlush($dir); - } - return; - } - - global $ADODB_INCLUDED_CSV; - if (empty($ADODB_INCLUDED_CSV)) include(ADODB_DIR.'/adodb-csvlib.inc.php'); - - $f = $this->_gencachename($sql.serialize($inputarr),false); - adodb_write_file($f,''); // is adodb_write_file needed? - if (!@unlink($f)) { - if ($this->debug) ADOConnection::outp( "CacheFlush: failed for $f"); - } - } - - /** - * Private function to erase all of the files and subdirectories in a directory. - * - * Just specify the directory, and tell it if you want to delete the directory or just clear it out. - * Note: $kill_top_level is used internally in the function to flush subdirectories. - * - */ - function _dirFlush($dir, $kill_top_level = false) - { - if(!$dh = @opendir($dir)) return; - - while (($obj = readdir($dh))) { - if($obj=='.' || $obj=='..') continue; - $f = $dir.'/'.$obj; - - if (strpos($obj,'.cache')) @unlink($f); - if (is_dir($f)) $this->_dirFlush($f, true); - } - if ($kill_top_level === true) @rmdir($dir); - return true; - } - - // this only deletes .cache files - function xCacheFlush($sql=false,$inputarr=false) - { - global $ADODB_CACHE_DIR; - - if ($this->memCache) { - global $ADODB_INCLUDED_MEMCACHE; - $key = false; - if (empty($ADODB_INCLUDED_MEMCACHE)) include(ADODB_DIR.'/adodb-memcache.lib.inc.php'); - if ($sql) $key = $this->_gencachename($sql.serialize($inputarr),false,true); - flushmemCache($key, $this->memCacheHost, $this->memCachePort, $this->debug); - return; - } - - if (strlen($ADODB_CACHE_DIR) > 1 && !$sql) { - if (strncmp(PHP_OS,'WIN',3) === 0) { - $cmd = 'del /s '.str_replace('/','\\',$ADODB_CACHE_DIR).'\adodb_*.cache'; - } else { - //$cmd = 'find "'.$ADODB_CACHE_DIR.'" -type f -maxdepth 1 -print0 | xargs -0 rm -f'; - $cmd = 'rm -rf '.$ADODB_CACHE_DIR.'/[0-9a-f][0-9a-f]/'; - // old version 'rm -f `find '.$ADODB_CACHE_DIR.' -name adodb_*.cache`'; - } - if ($this->debug) { - ADOConnection::outp( "CacheFlush: $cmd
\n", system($cmd),"
"); - } else { - exec($cmd); - } - return; - } - - global $ADODB_INCLUDED_CSV; - if (empty($ADODB_INCLUDED_CSV)) include(ADODB_DIR.'/adodb-csvlib.inc.php'); + if (!$sql) { + $ADODB_CACHE->flushall($this->debug); + return; + } $f = $this->_gencachename($sql.serialize($inputarr),false); - adodb_write_file($f,''); // is adodb_write_file needed? - if (!@unlink($f)) { - if ($this->debug) ADOConnection::outp( "CacheFlush: failed for $f"); - } + return $ADODB_CACHE->flushcache($f, $this->debug); } + /** * Private function to generate filename for caching. @@ -1685,10 +1734,9 @@ * 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! */ - function _gencachename($sql,$createdir,$memcache=false) + function _gencachename($sql,$createdir) { - global $ADODB_CACHE_DIR; - static $notSafeMode; + global $ADODB_CACHE, $ADODB_CACHE_DIR; if ($this->fetchMode === false) { global $ADODB_FETCH_MODE; @@ -1697,17 +1745,10 @@ $mode = $this->fetchMode; } $m = md5($sql.$this->databaseType.$this->database.$this->user.$mode); - if ($memcache) return $m; + if (!$ADODB_CACHE->createdir) return $m; + if (!$createdir) $dir = $ADODB_CACHE->getdirname($m); + else $dir = $ADODB_CACHE->createdir($m, $this->debug); - if (!isset($notSafeMode)) $notSafeMode = !ini_get('safe_mode'); - $dir = ($notSafeMode) ? $ADODB_CACHE_DIR.'/'.substr($m,0,2) : $ADODB_CACHE_DIR; - - if ($createdir && $notSafeMode && !file_exists($dir)) { - $oldu = umask(0); - if (!@mkdir($dir,0771)) - if(!is_dir($dir) && $this->debug) ADOConnection::outp( "Unable to mkdir $dir for $sql"); - umask($oldu); - } return $dir.'/adodb_'.$m.'.cache'; } @@ -1723,6 +1764,8 @@ */ function CacheExecute($secs2cache,$sql=false,$inputarr=false) { + global $ADODB_CACHE; + if (!is_numeric($secs2cache)) { $inputarr = $sql; $sql = $secs2cache; @@ -1735,30 +1778,19 @@ } else $sqlparam = $sql; - if ($this->memCache) { - global $ADODB_INCLUDED_MEMCACHE; - if (empty($ADODB_INCLUDED_MEMCACHE)) include(ADODB_DIR.'/adodb-memcache.lib.inc.php'); - $md5file = $this->_gencachename($sql.serialize($inputarr),false,true); - } else { - global $ADODB_INCLUDED_CSV; - if (empty($ADODB_INCLUDED_CSV)) include(ADODB_DIR.'/adodb-csvlib.inc.php'); - $md5file = $this->_gencachename($sql.serialize($inputarr),true); - } - + + $md5file = $this->_gencachename($sql.serialize($inputarr),true); $err = ''; if ($secs2cache > 0){ - if ($this->memCache) - $rs = getmemCache($md5file,$err,$secs2cache, $this->memCacheHost, $this->memCachePort); - else - $rs = csv2rs($md5file,$err,$secs2cache,$this->arrayClass); + $rs = $ADODB_CACHE->readcache($md5file,$err,$secs2cache,$this->arrayClass); $this->numCacheHits += 1; } else { $err='Timeout 1'; $rs = false; $this->numCacheMisses += 1; } - + if (!$rs) { // no cached rs found if ($this->debug) { @@ -1770,19 +1802,14 @@ $rs = $this->Execute($sqlparam,$inputarr); - if ($rs && $this->memCache) { - $rs = $this->_rs2rs($rs); // read entire recordset into memory immediately - if(!putmemCache($md5file, $rs, $this->memCacheHost, $this->memCachePort, $this->memCacheCompress, $this->debug)) { - if ($fn = $this->raiseErrorFn) - $fn($this->databaseType,'CacheExecute',-32000,"Cache write error",$md5file,$sql,$this); - if ($this->debug) ADOConnection::outp( " Cache write error"); - } - } else if ($rs) { + if ($rs) { + $eof = $rs->EOF; $rs = $this->_rs2rs($rs); // read entire recordset into memory immediately + $rs->timeCreated = time(); // used by caching $txt = _rs2serialize($rs,false,$sql); // serialize - $ok = adodb_write_file($md5file,$txt,$this->debug); + $ok = $ADODB_CACHE->writecache($md5file,$txt,$this->debug, $secs2cache); if (!$ok) { if ($ok === false) { $em = 'Cache write error'; @@ -1805,9 +1832,8 @@ $rs->connection = $this; // Pablo suggestion } - } else - if (!$this->memCache) - @unlink($md5file); + } else if (!$this->memCache) + $ADODB_CACHE->flushcache($md5file); } else { $this->_errorMsg = ''; $this->_errorCode = 0; @@ -2068,41 +2094,29 @@ } } - function GetActiveRecordsClass($class, $table,$whereOrderBy=false,$bindarr=false, $primkeyArr=false) + /** + * GetActiveRecordsClass Performs an 'ALL' query + * + * @param mixed $class This string represents the class of the current active record + * @param mixed $table Table used by the active record object + * @param mixed $whereOrderBy Where, order, by clauses + * @param mixed $bindarr + * @param mixed $primkeyArr + * @param array $extra Query extras: limit, offset... + * @param mixed $relations Associative array: table's foreign name, "hasMany", "belongsTo" + * @access public + * @return void + */ + function GetActiveRecordsClass( + $class, $table,$whereOrderBy=false,$bindarr=false, $primkeyArr=false, + $extra=array(), + $relations=array()) { global $_ADODB_ACTIVE_DBS; - - $save = $this->SetFetchMode(ADODB_FETCH_NUM); - if (empty($whereOrderBy)) $whereOrderBy = '1=1'; - $rows = $this->GetAll("select * from ".$table.' WHERE '.$whereOrderBy,$bindarr); - $this->SetFetchMode($save); - - $false = false; - - if ($rows === false) { - return $false; - } - - - if (!isset($_ADODB_ACTIVE_DBS)) { - include(ADODB_DIR.'/adodb-active-record.inc.php'); - } - if (!class_exists($class)) { - $this->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass'); - return $false; - } - $arr = array(); - foreach($rows as $row) { - - $obj = new $class($table,$primkeyArr,$this); - if ($obj->ErrorMsg()){ - $this->_errorMsg = $obj->ErrorMsg(); - return $false; - } - $obj->Set($row); - $arr[] = $obj; - } - return $arr; + ## reduce overhead of adodb.inc.php -- moved to adodb-active-record.inc.php + ## if adodb-active-recordx is loaded -- should be no issue as they will probably use Find() + if (!isset($_ADODB_ACTIVE_DBS))include_once(ADODB_DIR.'/adodb-active-record.inc.php'); + return adodb_GetActiveRecordsClass($this, $class, $table, $whereOrderBy, $bindarr, $primkeyArr, $extra, $relations); } function GetActiveRecords($table,$where=false,$bindarr=false,$primkeyArr=false) @@ -2420,10 +2434,11 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 * * @return date string in database date format */ - function DBDate($d) + function DBDate($d, $isfld=false) { if (empty($d) && $d !== 0) return 'null'; - + if ($isfld) return $d; + if (is_string($d) && !is_numeric($d)) { if ($d === 'null' || strncmp($d,"'",1) === 0) return $d; if ($this->isoDates) return "'$d'"; @@ -2457,10 +2472,11 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 * * @return timestamp string in database timestamp format */ - function DBTimeStamp($ts) + function DBTimeStamp($ts,$isfld=false) { if (empty($ts) && $ts !== 0) return 'null'; - + if ($isfld) return $ts; + # strlen(14) allows YYYYMMDDHHMMSS format if (!is_string($ts) || (is_numeric($ts) && strlen($ts)<14)) return adodb_date($this->fmtTimeStamp,$ts); @@ -2719,12 +2735,12 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 function key() { - return $false; + return false; } function current() { - return $false; + return false; } function next() @@ -3670,6 +3686,7 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 'CHARACTER' => 'C', 'INTERVAL' => 'C', # Postgres 'MACADDR' => 'C', # postgres + 'VAR_STRING' => 'C', # mysql ## 'LONGCHAR' => 'X', 'TEXT' => 'X', @@ -3693,6 +3710,7 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 ## 'UNIQUEIDENTIFIER' => 'C', # MS SQL Server ## + 'SMALLDATETIME' => 'T', 'TIME' => 'T', 'TIMESTAMP' => 'T', 'DATETIME' => 'T', @@ -4139,8 +4157,12 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 if (strncmp($origdsn,'pdo',3) == 0) { $sch = explode('_',$dsna['scheme']); if (sizeof($sch)>1) { + $dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : ''; - $dsna['host'] = rawurlencode($sch[1].':host='.rawurldecode($dsna['host'])); + if ($sch[1] == 'sqlite') + $dsna['host'] = rawurlencode($sch[1].':'.rawurldecode($dsna['host'])); + else + $dsna['host'] = rawurlencode($sch[1].':host='.rawurldecode($dsna['host'])); $dsna['scheme'] = 'pdo'; } } @@ -4172,7 +4194,9 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 if (!empty($ADODB_NEWCONNECTION)) { $obj = $ADODB_NEWCONNECTION($db); - } else { + } + + if(empty($obj)) { if (!isset($ADODB_LASTDB)) $ADODB_LASTDB = ''; if (empty($db)) $db = $ADODB_LASTDB; @@ -4347,13 +4371,13 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1 @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 */ - function adodb_backtrace($printOrArr=true,$levels=9999) + function adodb_backtrace($printOrArr=true,$levels=9999,$ishtml=null) { global $ADODB_INCLUDED_LIB; if (empty($ADODB_INCLUDED_LIB)) include(ADODB_DIR.'/adodb-lib.inc.php'); - return _adodb_backtrace($printOrArr,$levels); + return _adodb_backtrace($printOrArr,$levels,0,$ishtml); } } -?> +?> \ No newline at end of file diff --git a/lib/adodb/datadict/datadict-access.inc.php b/lib/adodb/datadict/datadict-access.inc.php index 468c070026..e4d1c23a89 100644 --- a/lib/adodb/datadict/datadict-access.inc.php +++ b/lib/adodb/datadict/datadict-access.inc.php @@ -1,7 +1,7 @@ type; + $len = $fieldobj->max_length; + } + + $len = -1; // mysql max_length is not accurate + switch (strtoupper($t)) { + case 'R': + case 'INT': + case 'INTEGER': return 'I'; + case 'BIT': + case 'TINYINT': return 'I1'; + case 'SMALLINT': return 'I2'; + case 'BIGINT': return 'I8'; + + case 'REAL': + case 'FLOAT': return 'F'; + default: return parent::MetaType($t,$len,$fieldobj); + } + } + + function ActualType($meta) + { + switch(strtoupper($meta)) { + + case 'C': return 'VARCHAR'; + case 'XL': return (isset($this)) ? $this->typeXL : 'TEXT'; + case 'X': return (isset($this)) ? $this->typeX : 'TEXT'; ## could be varchar(8000), but we want compat with oracle + case 'C2': return 'NVARCHAR'; + case 'X2': return 'NTEXT'; + + case 'B': return 'IMAGE'; + + case 'D': return 'DATETIME'; + case 'T': return 'DATETIME'; + case 'L': return 'BIT'; + + case 'R': + case 'I': return 'INT'; + case 'I1': return 'TINYINT'; + case 'I2': return 'SMALLINT'; + case 'I4': return 'INT'; + case 'I8': return 'BIGINT'; + + case 'F': return 'REAL'; + case 'N': return 'NUMERIC'; + default: + return $meta; + } + } + + + function AddColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $f = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $s = "ALTER TABLE $tabname $this->addCol"; + foreach($lines as $v) { + $f[] = "\n $v"; + } + $s .= implode(', ',$f); + $sql[] = $s; + return $sql; + } + + /* + function AlterColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + foreach($lines as $v) { + $sql[] = "ALTER TABLE $tabname $this->alterCol $v"; + } + + return $sql; + } + */ + + function DropColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + if (!is_array($flds)) + $flds = explode(',',$flds); + $f = array(); + $s = 'ALTER TABLE ' . $tabname; + foreach($flds as $v) { + $f[] = "\n$this->dropCol ".$this->NameQuote($v); + } + $s .= implode(', ',$f); + $sql[] = $s; + return $sql; + } + + // return string must begin with space + function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) + { + $suffix = ''; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fautoinc) $suffix .= ' IDENTITY(1,1)'; + if ($fnotnull) $suffix .= ' NOT NULL'; + else if ($suffix == '') $suffix .= ' NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + /* +CREATE TABLE + [ database_name.[ owner ] . | owner. ] table_name + ( { < column_definition > + | column_name AS computed_column_expression + | < table_constraint > ::= [ CONSTRAINT constraint_name ] } + + | [ { PRIMARY KEY | UNIQUE } [ ,...n ] + ) + +[ ON { filegroup | DEFAULT } ] +[ TEXTIMAGE_ON { filegroup | DEFAULT } ] + +< column_definition > ::= { column_name data_type } + [ COLLATE < collation_name > ] + [ [ DEFAULT constant_expression ] + | [ IDENTITY [ ( seed , increment ) [ NOT FOR REPLICATION ] ] ] + ] + [ ROWGUIDCOL] + [ < column_constraint > ] [ ...n ] + +< column_constraint > ::= [ CONSTRAINT constraint_name ] + { [ NULL | NOT NULL ] + | [ { PRIMARY KEY | UNIQUE } + [ CLUSTERED | NONCLUSTERED ] + [ WITH FILLFACTOR = fillfactor ] + [ON {filegroup | DEFAULT} ] ] + ] + | [ [ FOREIGN KEY ] + REFERENCES ref_table [ ( ref_column ) ] + [ ON DELETE { CASCADE | NO ACTION } ] + [ ON UPDATE { CASCADE | NO ACTION } ] + [ NOT FOR REPLICATION ] + ] + | CHECK [ NOT FOR REPLICATION ] + ( logical_expression ) + } + +< table_constraint > ::= [ CONSTRAINT constraint_name ] + { [ { PRIMARY KEY | UNIQUE } + [ CLUSTERED | NONCLUSTERED ] + { ( column [ ASC | DESC ] [ ,...n ] ) } + [ WITH FILLFACTOR = fillfactor ] + [ ON { filegroup | DEFAULT } ] + ] + | FOREIGN KEY + [ ( column [ ,...n ] ) ] + REFERENCES ref_table [ ( ref_column [ ,...n ] ) ] + [ ON DELETE { CASCADE | NO ACTION } ] + [ ON UPDATE { CASCADE | NO ACTION } ] + [ NOT FOR REPLICATION ] + | CHECK [ NOT FOR REPLICATION ] + ( search_conditions ) + } + + + */ + + /* + CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name + ON { table | view } ( column [ ASC | DESC ] [ ,...n ] ) + [ WITH < index_option > [ ,...n] ] + [ ON filegroup ] + < index_option > :: = + { PAD_INDEX | + FILLFACTOR = fillfactor | + IGNORE_DUP_KEY | + DROP_EXISTING | + STATISTICS_NORECOMPUTE | + SORT_IN_TEMPDB + } +*/ + function _IndexSQL($idxname, $tabname, $flds, $idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + $sql[] = sprintf ($this->dropIndex, $idxname, $tabname); + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : ''; + $clustered = isset($idxoptions['CLUSTERED']) ? ' CLUSTERED' : ''; + + if ( is_array($flds) ) + $flds = implode(', ',$flds); + $s = 'CREATE' . $unique . $clustered . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')'; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + + $sql[] = $s; + + return $sql; + } + + + function _GetSize($ftype, $ty, $fsize, $fprec) + { + switch ($ftype) { + case 'INT': + case 'SMALLINT': + case 'TINYINT': + case 'BIGINT': + return $ftype; + } + if ($ty == 'T') return $ftype; + return parent::_GetSize($ftype, $ty, $fsize, $fprec); + + } +} +?> \ No newline at end of file diff --git a/lib/adodb/datadict/datadict-mysql.inc.php b/lib/adodb/datadict/datadict-mysql.inc.php index 27e0b09e12..2f7426b578 100644 --- a/lib/adodb/datadict/datadict-mysql.inc.php +++ b/lib/adodb/datadict/datadict-mysql.inc.php @@ -1,7 +1,7 @@ CommandText = $sql; $oCmd->CommandType = 1; - foreach($inputarr as $val) { + // Map by http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdmthcreateparam.asp + // Check issue http://bugs.php.net/bug.php?id=40664 !!! + while(list(, $val) = each($inputarr)) { + $type = gettype($val); + $len=strlen($val); + if ($type == 'boolean') + $this->adoParameterType = 11; + else if ($type == 'integer') + $this->adoParameterType = 3; + else if ($type == 'double') + $this->adoParameterType = 5; + elseif ($type == 'string') + $this->adoParameterType = 202; + else if (($val === null) || (!defined($val))) + $len=1; + else + $this->adoParameterType = 130; + // name, type, direction 1 = input, len, - $this->adoParameterType = 130; - $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,strlen($val),$val); - //print $p->Type.' '.$p->value; + $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,$len,$val); + $oCmd->Parameters->Append($p); } $p = false; diff --git a/lib/adodb/drivers/adodb-ado5.inc.php b/lib/adodb/drivers/adodb-ado5.inc.php index d914b93829..54a6f19742 100644 --- a/lib/adodb/drivers/adodb-ado5.inc.php +++ b/lib/adodb/drivers/adodb-ado5.inc.php @@ -1,6 +1,6 @@ CursorLocation = $this->_cursor_location; return $dbc->State > 0; } catch (exception $e) { + if ($this->debug);echo "
",$argHostname,"\n",$e,"
\n"; } return false; @@ -244,13 +246,28 @@ class ADODB_ado extends ADOConnection { $oCmd->CommandText = $sql; $oCmd->CommandType = 1; - foreach($inputarr as $val) { + while(list(, $val) = each($inputarr)) { + $type = gettype($val); + $len=strlen($val); + if ($type == 'boolean') + $this->adoParameterType = 11; + else if ($type == 'integer') + $this->adoParameterType = 3; + else if ($type == 'double') + $this->adoParameterType = 5; + elseif ($type == 'string') + $this->adoParameterType = 202; + else if (($val === null) || (!defined($val))) + $len=1; + else + $this->adoParameterType = 130; + // name, type, direction 1 = input, len, - $this->adoParameterType = 130; - $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,strlen($val),$val); - //print $p->Type.' '.$p->value; + $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,$len,$val); + $oCmd->Parameters->Append($p); } + $p = false; $rs = $oCmd->Execute(); $e = $dbc->Errors; @@ -375,6 +392,8 @@ class ADORecordSet_ado extends ADORecordSet { $o= new ADOFieldObject(); $rs = $this->_queryID; + if (!$rs) return false; + $f = $rs->Fields($fieldOffset); $o->name = $f->Name; $t = $f->Type; @@ -406,8 +425,12 @@ class ADORecordSet_ado extends ADORecordSet { function _initrs() { $rs = $this->_queryID; - $this->_numOfRows = $rs->RecordCount; + try { + $this->_numOfRows = $rs->RecordCount; + } catch (Exception $e) { + $this->_numOfRows = -1; + } $f = $rs->Fields; $this->_numOfFields = $f->Count; } @@ -669,7 +692,10 @@ class ADORecordSet_ado extends ADORecordSet { function _close() { $this->_flds = false; + try { @$this->_queryID->Close();// by Pete Dishman (peterd@telephonetics.co.uk) + } catch (Exception $e) { + } $this->_queryID = false; } diff --git a/lib/adodb/drivers/adodb-ado_access.inc.php b/lib/adodb/drivers/adodb-ado_access.inc.php index 05cb0e7ce6..7640af30c3 100644 --- a/lib/adodb/drivers/adodb-ado_access.inc.php +++ b/lib/adodb/drivers/adodb-ado_access.inc.php @@ -1,6 +1,6 @@ _genSeqSQL)) return false; - $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$start)); if (!$ok) return false; return true; } @@ -249,9 +249,9 @@ class ADODB_db2 extends ADOConnection { { // if you have to modify the parameter below, your database is overloaded, // or you need to implement generation of id's yourself! - $num = $this->GetOne("VALUES NEXTVAL FOR $seq"); + $num = $this->GetOne("VALUES NEXTVAL FOR $seq"); return $num; - } + } function ErrorMsg() diff --git a/lib/adodb/drivers/adodb-fbsql.inc.php b/lib/adodb/drivers/adodb-fbsql.inc.php index 3c8a15a280..b40281b7b6 100644 --- a/lib/adodb/drivers/adodb-fbsql.inc.php +++ b/lib/adodb/drivers/adodb-fbsql.inc.php @@ -1,6 +1,6 @@ _connectionID); } diff --git a/lib/adodb/drivers/adodb-firebird.inc.php b/lib/adodb/drivers/adodb-firebird.inc.php index 41b1d7be1a..09f7e4a659 100644 --- a/lib/adodb/drivers/adodb-firebird.inc.php +++ b/lib/adodb/drivers/adodb-firebird.inc.php @@ -1,6 +1,6 @@ port); + if (strpos('ldap://',$host) === 0 || strpos('ldaps://',$host) === 0) { + $this->_connectionID = @ldap_connect($host); + } else { + $conn_info = array( $host,$this->port); - if ( strstr( $host, ':' ) ) { - $conn_info = split( ':', $host ); - } + if ( strstr( $host, ':' ) ) { + $conn_info = split( ':', $host ); + } - $this->_connectionID = @ldap_connect( $conn_info[0], $conn_info[1] ); + $this->_connectionID = @ldap_connect( $conn_info[0], $conn_info[1] ); + } if (!$this->_connectionID) { $e = 'Could not connect to ' . $conn_info[0]; $this->_errorMsg = $e; @@ -150,7 +154,7 @@ class ADODB_ldap extends ADOConnection { } /* returns _queryID or false */ - function _query($sql,$inputarr) + function _query($sql,$inputarr=false) { $rs = @ldap_search( $this->_connectionID, $this->database, $sql ); $this->_errorMsg = ($rs) ? '' : 'Search error on '.$sql.': '.ldap_error($this->_connectionID); diff --git a/lib/adodb/drivers/adodb-mssql.inc.php b/lib/adodb/drivers/adodb-mssql.inc.php index 96e029ec12..65a1c20eb6 100644 --- a/lib/adodb/drivers/adodb-mssql.inc.php +++ b/lib/adodb/drivers/adodb-mssql.inc.php @@ -1,6 +1,6 @@ lastInsID; // InsID from sp_executesql call } else { return $this->GetOne($this->identitySQL); - } + } } function _affectedrows() @@ -210,7 +211,11 @@ class ADODB_mssql extends ADOConnection { if ($nrows > 0 && $offset <= 0) { $sql = preg_replace( '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop." $nrows ",$sql); - $rs = $this->Execute($sql,$inputarr); + + if ($secs2cache) + $rs = $this->CacheExecute($secs2cache, $sql, $inputarr); + else + $rs = $this->Execute($sql,$inputarr); } else $rs = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); @@ -283,8 +288,8 @@ class ADODB_mssql extends ADOConnection { { if ($this->transOff) return true; $this->transCnt += 1; - $this->Execute('BEGIN TRAN'); - return true; + $ok = $this->Execute('BEGIN TRAN'); + return $ok; } function CommitTrans($ok=true) @@ -292,15 +297,15 @@ class ADODB_mssql extends ADOConnection { if ($this->transOff) return true; if (!$ok) return $this->RollbackTrans(); if ($this->transCnt) $this->transCnt -= 1; - $this->Execute('COMMIT TRAN'); - return true; + $ok = $this->Execute('COMMIT TRAN'); + return $ok; } function RollbackTrans() { if ($this->transOff) return true; if ($this->transCnt) $this->transCnt -= 1; - $this->Execute('ROLLBACK TRAN'); - return true; + $ok = $this->Execute('ROLLBACK TRAN'); + return $ok; } function SetTransactionMode( $transaction_mode ) @@ -365,7 +370,7 @@ class ADODB_mssql extends ADOConnection { $indexes = array(); while ($row = $rs->FetchRow()) { - if (!$primary && $row[5]) continue; + if ($primary && !$row[5]) continue; $indexes[$row[0]]['unique'] = $row[6]; $indexes[$row[0]]['columns'][] = $row[1]; @@ -508,11 +513,11 @@ order by constraint_name, referenced_table_name, keyno"; else return -1; } - // returns true or false - function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + // returns true or false, newconnect supported since php 5.1.0. + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$newconnect=false) { if (!function_exists('mssql_pconnect')) return null; - $this->_connectionID = mssql_connect($argHostname,$argUsername,$argPassword); + $this->_connectionID = mssql_connect($argHostname,$argUsername,$argPassword,$newconnect); if ($this->_connectionID === false) return false; if ($argDatabasename) return $this->SelectDB($argDatabasename); return true; @@ -535,6 +540,11 @@ order by constraint_name, referenced_table_name, keyno"; return true; } + function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, true); + } + function Prepare($sql) { $sqlarr = explode('?',$sql); @@ -659,7 +669,7 @@ order by constraint_name, referenced_table_name, keyno"; } // returns query ID if successful, otherwise false - function _query($sql,$inputarr) + function _query($sql,$inputarr=false) { $this->_errorMsg = false; if (is_array($inputarr)) { @@ -1051,4 +1061,4 @@ order by constraint_name, ordinal_position http://www.databasejournal.com/scripts/article.php/1440551 */ -?> +?> \ No newline at end of file diff --git a/lib/adodb/drivers/adodb-mssql_n.inc.php b/lib/adodb/drivers/adodb-mssql_n.inc.php index 380829828f..199b100450 100644 --- a/lib/adodb/drivers/adodb-mssql_n.inc.php +++ b/lib/adodb/drivers/adodb-mssql_n.inc.php @@ -9,7 +9,7 @@ // ADOdb - Database Abstraction Library for PHP // // http://adodb.sourceforge.net/ // // // -// Copyright (C) 2000-2008 John Lim (jlim\@natsoft.com.my) // +// Copyright (C) 2000-2009 John Lim (jlim\@natsoft.com.my) // // All rights reserved. // // Released under both BSD license and LGPL library license. // // Whenever there is any discrepancy between the two licenses, // @@ -58,7 +58,7 @@ class ADODB_mssql_n extends ADODB_mssql { ADODB_mssql::ADODB_mssql(); } - function _query($sql,$inputarr) + function _query($sql,$inputarr=false) { $sql = $this->_appendN($sql); return ADODB_mssql::_query($sql,$inputarr); diff --git a/lib/adodb/drivers/adodb-mssqlnative.inc.php b/lib/adodb/drivers/adodb-mssqlnative.inc.php new file mode 100644 index 0000000000..96885de51f --- /dev/null +++ b/lib/adodb/drivers/adodb-mssqlnative.inc.php @@ -0,0 +1,922 @@ += 0x4300) { +// docs say 4.2.0, but testing shows only since 4.3.0 does it work! + ini_set('mssql.datetimeconvert',0); +} else { + global $ADODB_mssql_mths; // array, months must be upper-case + $ADODB_mssql_date_order = 'mdy'; + $ADODB_mssql_mths = array( + 'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6, + 'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12); +} + +//--------------------------------------------------------------------------- +// Call this to autoset $ADODB_mssql_date_order at the beginning of your code, +// just after you connect to the database. Supports mdy and dmy only. +// Not required for PHP 4.2.0 and above. +function AutoDetect_MSSQL_Date_Order($conn) +{ + global $ADODB_mssql_date_order; + $adate = $conn->GetOne('select getdate()'); + if ($adate) { + $anum = (int) $adate; + if ($anum > 0) { + if ($anum > 31) { + //ADOConnection::outp( "MSSQL: YYYY-MM-DD date format not supported currently"); + } else + $ADODB_mssql_date_order = 'dmy'; + } else + $ADODB_mssql_date_order = 'mdy'; + } +} + +class ADODB_mssqlnative extends ADOConnection { + var $databaseType = "mssqlnative"; + var $dataProvider = "mssqlnative"; + var $replaceQuote = "''"; // string to use to replace quotes + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $hasInsertID = true; + var $substr = "substring"; + var $length = 'len'; + var $hasAffectedRows = true; + var $poorAffectedRows = false; + var $metaDatabasesSQL = "select name from sys.sysdatabases where name <> 'master'"; + var $metaTablesSQL="select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE','dtproperties'))"; + var $metaColumnsSQL = # xtype==61 is datetime + "select c.name,t.name,c.length, + (case when c.xusertype=61 then 0 else c.xprec end), + (case when c.xusertype=61 then 0 else c.xscale end) + from syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id where o.name='%s'"; + var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE + var $hasGenID = true; + var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)'; + var $sysTimeStamp = 'GetDate()'; + var $maxParameterLen = 4000; + var $arrayClass = 'ADORecordSet_array_mssqlnative'; + var $uniqueSort = true; + var $leftOuter = '*='; + var $rightOuter = '=*'; + var $ansiOuter = true; // for mssql7 or later + var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000 + var $uniqueOrderBy = true; + var $_bindInputArray = true; + var $_dropSeqSQL = "drop table %s"; + + function ADODB_mssqlnative() + { + if ($this->debug) { + error_log("
");
+            sqlsrv_set_error_handling( SQLSRV_ERRORS_LOG_ALL );
+            sqlsrv_log_set_severity( SQLSRV_LOG_SEVERITY_ALL );
+            sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
+            sqlsrv_configure('warnings_return_as_errors', 0);
+        } else {
+            sqlsrv_set_error_handling(0);
+            sqlsrv_log_set_severity(0);
+            sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
+            sqlsrv_configure('warnings_return_as_errors', 0);
+        }
+	}
+
+	function ServerInfo()
+	{
+    	global $ADODB_FETCH_MODE;
+		if ($this->fetchMode === false) {
+			$savem = $ADODB_FETCH_MODE;
+			$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+		} else 
+			$savem = $this->SetFetchMode(ADODB_FETCH_NUM);
+		$arrServerInfo = sqlsrv_server_info($this->_connectionID);
+		$arr['description'] = $arrServerInfo['SQLServerName'].' connected to '.$arrServerInfo['CurrentDatabase'];
+		$arr['version'] = $arrServerInfo['SQLServerVersion'];//ADOConnection::_findvers($arr['description']);
+		return $arr;
+	}
+	
+	function IfNull( $field, $ifNull ) 
+	{
+		return " ISNULL($field, $ifNull) "; // if MS SQL Server
+	}
+	
+	function _insertid()
+	{
+	// SCOPE_IDENTITY()
+	// Returns the last IDENTITY value inserted into an IDENTITY column in 
+	// the same scope. A scope is a module -- a stored procedure, trigger, 
+	// function, or batch. Thus, two statements are in the same scope if 
+	// they are in the same stored procedure, function, or batch.
+		return $this->GetOne($this->identitySQL);
+	}
+
+	function _affectedrows()
+	{
+        return sqlsrv_rows_affected($this->_queryID);
+	}
+	
+	function CreateSequence($seq='adodbseq',$start=1)
+	{
+		if($this->debug) error_log("
CreateSequence($seq,$start)"); + sqlsrv_begin_transaction($this->_connectionID); + $start -= 1; + $this->Execute("create table $seq (id int)");//was float(53) + $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)"); + if (!$ok) { + if($this->debug) error_log("
Error: ROLLBACK"); + sqlsrv_rollback($this->_connectionID); + return false; + } + sqlsrv_commit($this->_connectionID); + return true; + } + + function GenID($seq='adodbseq',$start=1) + { + if($this->debug) error_log("
GenID($seq,$start)"); + sqlsrv_begin_transaction($this->_connectionID); + $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1"); + if (!$ok) { + $this->Execute("create table $seq (id int)"); + $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)"); + if (!$ok) { + if($this->debug) error_log("
Error: ROLLBACK"); + sqlsrv_rollback($this->_connectionID); + return false; + } + sqlsrv_commit($this->_connectionID); + return $start; + } + $num = $this->GetOne("select id from $seq"); + sqlsrv_commit($this->_connectionID); + if($this->debug) error_log(" Returning: $num"); + return $num; + } + + // 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 = ''; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + if ($s) $s .= '+'; + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= "datename(yyyy,$col)"; + break; + case 'M': + $s .= "convert(char(3),$col,0)"; + break; + case 'm': + $s .= "replace(str(month($col),2),' ','0')"; + break; + case 'Q': + case 'q': + $s .= "datename(quarter,$col)"; + break; + case 'D': + case 'd': + $s .= "replace(str(day($col),2),' ','0')"; + break; + case 'h': + $s .= "substring(convert(char(14),$col,0),13,2)"; + break; + + case 'H': + $s .= "replace(str(datepart(hh,$col),2),' ','0')"; + break; + + case 'i': + $s .= "replace(str(datepart(mi,$col),2),' ','0')"; + break; + case 's': + $s .= "replace(str(datepart(ss,$col),2),' ','0')"; + break; + case 'a': + case 'A': + $s .= "substring(convert(char(19),$col,0),18,2)"; + break; + + default: + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + $s .= $this->qstr($ch); + break; + } + } + return $s; + } + + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + if ($this->debug) error_log('
begin transaction'); + sqlsrv_begin_transaction($this->_connectionID); + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if ($this->debug) error_log('
commit transaction'); + if (!$ok) return $this->RollbackTrans(); + if ($this->transCnt) $this->transCnt -= 1; + sqlsrv_commit($this->_connectionID); + return true; + } + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->debug) error_log('
rollback transaction'); + if ($this->transCnt) $this->transCnt -= 1; + sqlsrv_rollback($this->_connectionID); + return true; + } + + function SetTransactionMode( $transaction_mode ) + { + $this->_transmode = $transaction_mode; + if (empty($transaction_mode)) { + $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + return; + } + if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode; + $this->Execute("SET TRANSACTION ".$transaction_mode); + } + + /* + Usage: + + $this->BeginTrans(); + $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables + + # some operation on both tables table1 and table2 + + $this->CommitTrans(); + + See http://www.swynk.com/friends/achigrik/SQL70Locks.asp + */ + function RowLock($tables,$where,$flds='top 1 null as ignore') + { + if (!$this->transCnt) $this->BeginTrans(); + return $this->GetOne("select $flds from $tables with (ROWLOCK,HOLDLOCK) where $where"); + } + + 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) { + return true; + } else return false; + } + else return false; + } + + function ErrorMsg() + { + $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL); + if($retErrors != null) { + foreach($retErrors as $arrError) { + $this->_errorMsg .= "SQLState: ".$arrError[ 'SQLSTATE']."\n"; + $this->_errorMsg .= "Error Code: ".$arrError[ 'code']."\n"; + $this->_errorMsg .= "Message: ".$arrError[ 'message']."\n"; + } + } else { + $this->_errorMsg = "No errors found"; + } + return $this->_errorMsg; + } + + function ErrorNo() + { + if ($this->_logsql && $this->_errorCode !== false) return $this->_errorCode; + $err = sqlsrv_errors(SQLSRV_ERR_ALL); + if($err[0]) return $err[0]['code']; + else return -1; + } + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('sqlsrv_connect')) return null; + $connectionInfo = array("Database"=>$argDatabasename,'UID'=>$argUsername,'PWD'=>$argPassword); + if ($this->debug) error_log("
connecting... hostname: $argHostname params: ".var_export($connectionInfo,true)); + //if ($this->debug) error_log("
_connectionID before: ".serialize($this->_connectionID)); + if(!($this->_connectionID = sqlsrv_connect($argHostname,$connectionInfo))) { + if ($this->debug) error_log( "
errors: ".print_r( sqlsrv_errors(), true)); + return false; + } + //if ($this->debug) error_log(" _connectionID after: ".serialize($this->_connectionID)); + //if ($this->debug) error_log("
defined functions:
".var_export(get_defined_functions(),true)."
"); + return true; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + //return null;//not implemented. NOTE: Persistent connections have no effect if PHP is used as a CGI program. (FastCGI!) + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename); + } + + function Prepare($sql) + { + $stmt = sqlsrv_prepare( $this->_connectionID, $sql); + if (!$stmt) return $sql; + return array($sql,$stmt); + } + + // returns concatenated string + // MSSQL requires integers to be cast as strings + // automatically cast every datatype to VARCHAR(255) + // @author David Rogers (introspectshun) + function Concat() + { + $s = ""; + $arr = func_get_args(); + + // Split single record on commas, if possible + if (sizeof($arr) == 1) { + foreach ($arr as $arg) { + $args = explode(',', $arg); + } + $arr = $args; + } + + array_walk($arr, create_function('&$v', '$v = "CAST(" . $v . " AS VARCHAR(255))";')); + $s = implode('+',$arr); + if (sizeof($arr) > 0) return "$s"; + + return ''; + } + + /* + Unfortunately, it appears that mssql cannot handle varbinary > 255 chars + So all your blobs must be of type "image". + + Remember to set in php.ini the following... + + ; Valid range 0 - 2147483647. Default = 4096. + mssql.textlimit = 0 ; zero to pass through + + ; Valid range 0 - 2147483647. Default = 4096. + mssql.textsize = 0 ; zero to pass through + */ + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + + if (strtoupper($blobtype) == 'CLOB') { + $sql = "UPDATE $table SET $column='" . $val . "' WHERE $where"; + return $this->Execute($sql) != false; + } + $sql = "UPDATE $table SET $column=0x".bin2hex($val)." WHERE $where"; + return $this->Execute($sql) != false; + } + + // returns query ID if successful, otherwise false + function _query($sql,$inputarr=false) + { + $this->_errorMsg = false; + if (is_array($inputarr)) { + $rez = sqlsrv_query($this->_connectionID,$sql,$inputarr); + } else if (is_array($sql)) { + $rez = sqlsrv_query($this->_connectionID,$sql[1],$inputarr); + } else { + $rez = sqlsrv_query($this->_connectionID,$sql); + } + if ($this->debug) error_log("
running query: ".var_export($sql,true)."
input array: ".var_export($inputarr,true)."
result: ".var_export($rez,true));//"
connection: ".serialize($this->_connectionID) + //fix for returning true on anything besides select statements + if (is_array($sql)) $sql = $sql[1]; + $sql = ltrim($sql); + if(stripos($sql, 'SELECT') !== 0 && $rez !== false) { + if ($this->debug) error_log(" isn't a select query, returning boolean true"); + return true; + } + //end fix + if(!$rez) $rez = false; + return $rez; + } + + // returns true or false + function _close() + { + if ($this->transCnt) $this->RollbackTrans(); + $rez = @sqlsrv_close($this->_connectionID); + $this->_connectionID = false; + return $rez; + } + + // mssql uses a default date like Dec 30 2000 12:00AM + static function UnixDate($v) + { + return ADORecordSet_array_mssql::UnixDate($v); + } + + static function UnixTimeStamp($v) + { + return ADORecordSet_array_mssql::UnixTimeStamp($v); + } + + function &MetaIndexes($table,$primary=false) + { + $table = $this->qstr($table); + + $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno, + CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK, + CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique + FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id + INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid + INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid + WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table + ORDER BY O.name, I.Name, K.keyno"; + + 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($sql); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if (!is_object($rs)) { + return FALSE; + } + + $indexes = array(); + while ($row = $rs->FetchRow()) { + if (!$primary && $row[5]) continue; + + $indexes[$row[0]]['unique'] = $row[6]; + $indexes[$row[0]]['columns'][] = $row[1]; + } + return $indexes; + } + + function MetaForeignKeys($table, $owner=false, $upper=false) + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $table = $this->qstr(strtoupper($table)); + + $sql = + "select object_name(constid) as constraint_name, + col_name(fkeyid, fkey) as column_name, + object_name(rkeyid) as referenced_table_name, + col_name(rkeyid, rkey) as referenced_column_name + from sysforeignkeys + where upper(object_name(fkeyid)) = $table + order by constraint_name, referenced_table_name, keyno"; + + $constraints =& $this->GetArray($sql); + + $ADODB_FETCH_MODE = $save; + + $arr = false; + foreach($constraints as $constr) { + //print_r($constr); + $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3]; + } + if (!$arr) return false; + + $arr2 = false; + + foreach($arr as $k => $v) { + foreach($v as $a => $b) { + if ($upper) $a = strtoupper($a); + $arr2[$a] = $b; + } + } + return $arr2; + } + + //From: Fernando Moreira + function MetaDatabases() + { + $this->SelectDB("master"); + $rs =& $this->Execute($this->metaDatabasesSQL); + $rows = $rs->GetRows(); + $ret = array(); + for($i=0;$iSelectDB($this->database); + if($ret) + return $ret; + else + return false; + } + + // "Stein-Aksel Basma" + // tested with MSSQL 2000 + function &MetaPrimaryKeys($table) + { + global $ADODB_FETCH_MODE; + + $schema = ''; + $this->_findschema($table,$schema); + if (!$schema) $schema = $this->database; + if ($schema) $schema = "and k.table_catalog like '$schema%'"; + + $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k, + information_schema.table_constraints tc + where tc.constraint_name = k.constraint_name and tc.constraint_type = + 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position "; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $a = $this->GetCol($sql); + $ADODB_FETCH_MODE = $savem; + + if ($a && sizeof($a)>0) return $a; + $false = false; + return $false; + } + + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + if ($mask) { + $save = $this->metaTablesSQL; + $mask = $this->qstr(($mask)); + $this->metaTablesSQL .= " AND name like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_mssqlnative extends ADORecordSet { + + var $databaseType = "mssqlnative"; + var $canSeek = false; + var $fieldOffset = 0; + // _mths works only in non-localised system + + function ADORecordset_mssqlnative($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + + } + $this->fetchMode = $mode; + return $this->ADORecordSet($id,$mode); + } + + + function _initrs() + { + global $ADODB_COUNTRECS; + if ($this->connection->debug) error_log("(before) ADODB_COUNTRECS: {$ADODB_COUNTRECS} _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}"); + /*$retRowsAff = sqlsrv_rows_affected($this->_queryID);//"If you need to determine the number of rows a query will return before retrieving the actual results, appending a SELECT COUNT ... query would let you get that information, and then a call to next_result would move you to the "real" results." + error_log("rowsaff: ".serialize($retRowsAff)); + $this->_numOfRows = ($ADODB_COUNTRECS)? $retRowsAff:-1;*/ + $this->_numOfRows = -1;//not supported + $fieldmeta = sqlsrv_field_metadata($this->_queryID); + $this->_numOfFields = ($fieldmeta)? count($fieldmeta):-1; + if ($this->connection->debug) error_log("(after) _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}"); + } + + + //Contributed by "Sven Axelsson" + // get next resultset - requires PHP 4.0.5 or later + function NextRecordSet() + { + if (!sqlsrv_next_result($this->_queryID)) return false; + $this->_inited = false; + $this->bind = false; + $this->_currentRow = -1; + $this->Init(); + return true; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode != ADODB_FETCH_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)]]; + } + + /* 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. */ + + function &FetchField($fieldOffset = -1) + { + if ($this->connection->debug) error_log("
fetchfield: $fieldOffset, fetch array:
".print_r($this->fields,true)."
backtrace: ".adodb_backtrace(false)); + if ($fieldOffset != -1) $this->fieldOffset = $fieldOffset; + $arrKeys = array_keys($this->fields); + if(array_key_exists($this->fieldOffset,$arrKeys) && !array_key_exists($arrKeys[$this->fieldOffset],$this->fields)) { + $f = false; + } else { + $f = $this->fields[ $arrKeys[$this->fieldOffset] ]; + if($fieldOffset == -1) $this->fieldOffset++; + } + + if (empty($f)) { + $f = false;//PHP Notice: Only variable references should be returned by reference + } + return $f; + } + + function _seek($row) + { + return false;//There is no support for cursors in the driver at this time. All data is returned via forward-only streams. + } + + // speedup + function MoveNext() + { + if ($this->connection->debug) error_log("movenext()"); + //if ($this->connection->debug) error_log("eof (beginning): ".$this->EOF); + if ($this->EOF) return false; + + $this->_currentRow++; + if ($this->connection->debug) error_log("_currentRow: ".$this->_currentRow); + + if ($this->_fetch()) return true; + $this->EOF = true; + //if ($this->connection->debug) error_log("eof (end): ".$this->EOF); + + return false; + } + + + // INSERT UPDATE DELETE returns false even if no error occurs in 4.0.4 + // also the date format has been changed from YYYY-mm-dd to dd MMM YYYY in 4.0.4. Idiot! + function _fetch($ignore_fields=false) + { + if ($this->connection->debug) error_log("_fetch()"); + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + if ($this->fetchMode & ADODB_FETCH_NUM) { + if ($this->connection->debug) error_log("fetch mode: both"); + $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_BOTH); + } else { + if ($this->connection->debug) error_log("fetch mode: assoc"); + $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_ASSOC); + } + + if (ADODB_ASSOC_CASE == 0) { + foreach($this->fields as $k=>$v) { + $this->fields[strtolower($k)] = $v; + } + } else if (ADODB_ASSOC_CASE == 1) { + foreach($this->fields as $k=>$v) { + $this->fields[strtoupper($k)] = $v; + } + } + } else { + if ($this->connection->debug) error_log("fetch mode: num"); + $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_NUMERIC); + } + if(is_array($this->fields) && array_key_exists(1,$this->fields) && !array_key_exists(0,$this->fields)) {//fix fetch numeric keys since they're not 0 based + $arrFixed = array(); + foreach($this->fields as $key=>$value) { + if(is_numeric($key)) { + $arrFixed[$key-1] = $value; + } else { + $arrFixed[$key] = $value; + } + } + //if($this->connection->debug) error_log("
fixing non 0 based return array, old: ".print_r($this->fields,true)." new: ".print_r($arrFixed,true)); + $this->fields = $arrFixed; + } + if(is_array($this->fields)) { + foreach($this->fields as $key=>$value) { + if (is_object($value) && method_exists($value, 'format')) {//is DateTime object + $this->fields[$key] = $value->format("Y-m-d\TH:i:s\Z"); + } + } + } + if($this->fields === null) $this->fields = false; + if ($this->connection->debug) error_log("
after _fetch, fields:
".print_r($this->fields,true)." backtrace: ".adodb_backtrace(false));
+		return $this->fields;
+	}
+	
+    /*	close() only needs to be called if you are worried about using too much memory while your script
+		is running. All associated result memory for the specified result identifier will automatically be freed.	*/
+	function _close() 
+	{
+		$rez = sqlsrv_free_stmt($this->_queryID);	
+		$this->_queryID = false;
+		return $rez;
+	}
+
+	// mssql uses a default date like Dec 30 2000 12:00AM
+	static function UnixDate($v)
+	{
+		return ADORecordSet_array_mssqlnative::UnixDate($v);
+	}
+	
+	static function UnixTimeStamp($v)
+	{
+		return ADORecordSet_array_mssqlnative::UnixTimeStamp($v);
+	}
+}
+
+
+class ADORecordSet_array_mssqlnative extends ADORecordSet_array {
+	function ADORecordSet_array_mssqlnative($id=-1,$mode=false) 
+	{
+		$this->ADORecordSet_array($id,$mode);
+	}
+	
+		// mssql uses a default date like Dec 30 2000 12:00AM
+	static function UnixDate($v)
+	{
+	
+		if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixDate($v);
+		
+    	global $ADODB_mssql_mths,$ADODB_mssql_date_order;
+	
+		//Dec 30 2000 12:00AM 
+		if ($ADODB_mssql_date_order == 'dmy') {
+			if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
+				return parent::UnixDate($v);
+			}
+			if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+			
+			$theday = $rr[1];
+			$themth =  substr(strtoupper($rr[2]),0,3);
+		} else {
+			if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
+				return parent::UnixDate($v);
+			}
+			if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+			
+			$theday = $rr[2];
+			$themth = substr(strtoupper($rr[1]),0,3);
+		}
+		$themth = $ADODB_mssql_mths[$themth];
+		if ($themth <= 0) return false;
+		// h-m-s-MM-DD-YY
+		return  mktime(0,0,0,$themth,$theday,$rr[3]);
+	}
+	
+	static function UnixTimeStamp($v)
+	{
+	
+		if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixTimeStamp($v);
+		
+	    global $ADODB_mssql_mths,$ADODB_mssql_date_order;
+	
+		//Dec 30 2000 12:00AM
+		 if ($ADODB_mssql_date_order == 'dmy') {
+			 if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
+			,$v, $rr)) return parent::UnixTimeStamp($v);
+			if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+		
+			$theday = $rr[1];
+			$themth =  substr(strtoupper($rr[2]),0,3);
+		} else {
+			if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
+			,$v, $rr)) return parent::UnixTimeStamp($v);
+			if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
+		
+			$theday = $rr[2];
+			$themth = substr(strtoupper($rr[1]),0,3);
+		}
+		
+		$themth = $ADODB_mssql_mths[$themth];
+		if ($themth <= 0) return false;
+		
+		switch (strtoupper($rr[6])) {
+		case 'P':
+			if ($rr[4]<12) $rr[4] += 12;
+			break;
+		case 'A':
+			if ($rr[4]==12) $rr[4] = 0;
+			break;
+		default:
+			break;
+		}
+		// h-m-s-MM-DD-YY
+		return  mktime($rr[4],$rr[5],0,$themth,$theday,$rr[3]);
+	}
+}
+
+/*
+Code Example 1:
+
+select 	object_name(constid) as constraint_name,
+       	object_name(fkeyid) as table_name, 
+        col_name(fkeyid, fkey) as column_name,
+	object_name(rkeyid) as referenced_table_name,
+   	col_name(rkeyid, rkey) as referenced_column_name
+from sysforeignkeys
+where object_name(fkeyid) = x
+order by constraint_name, table_name, referenced_table_name,  keyno
+
+Code Example 2:
+select 	constraint_name,
+	column_name,
+	ordinal_position
+from information_schema.key_column_usage
+where constraint_catalog = db_name()
+and table_name = x
+order by constraint_name, ordinal_position
+
+http://www.databasejournal.com/scripts/article.php/1440551
+*/
+
+?>
\ No newline at end of file
diff --git a/lib/adodb/drivers/adodb-mssqlpo.inc.php b/lib/adodb/drivers/adodb-mssqlpo.inc.php
index 1d7845e5d7..d10e1b8455 100644
--- a/lib/adodb/drivers/adodb-mssqlpo.inc.php
+++ b/lib/adodb/drivers/adodb-mssqlpo.inc.php
@@ -1,6 +1,6 @@
 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);
+			$fld->binary = (strpos($type,'blob') !== false || strpos($type,'binary') !== false);
 			$fld->unsigned = (strpos($type,'unsigned') !== false);
 			$fld->zerofill = (strpos($type,'zerofill') !== false);
 
@@ -499,7 +499,7 @@ class ADODB_mysql extends ADOConnection {
 	}
 	
 	// returns queryID or false
-	function _query($sql,$inputarr)
+	function _query($sql,$inputarr=false)
 	{
 	//global $ADODB_COUNTRECS;
 		//if($ADODB_COUNTRECS) 
@@ -559,8 +559,9 @@ class ADODB_mysql extends ADOConnection {
             $table = "$owner.$table";
          }
          $a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE %s', $table));
-		 if ($associative) $create_sql = $a_create_table["Create Table"];
-         else $create_sql  = $a_create_table[1];
+		 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();
 
@@ -576,9 +577,12 @@ class ADODB_mysql extends ADOConnection {
                  $ref_table = strtoupper($ref_table);
              }
 
-             $foreign_keys[$ref_table] = array();
-             $num_fields = count($my_field);
-             for ( $j = 0;  $j < $num_fields;  $j ++ ) {
+			// see https://sourceforge.net/tracker/index.php?func=detail&aid=2287278&group_id=42718&atid=433976
+			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 {
@@ -733,6 +737,7 @@ class ADORecordSet_mysql extends ADORecordSet{
 		case 'LONGBLOB': 
 		case 'BLOB':
 		case 'MEDIUMBLOB':
+		case 'BINARY':
 			return !empty($fieldobj->binary) ? 'B' : 'X';
 			
 		case 'YEAR':
diff --git a/lib/adodb/drivers/adodb-mysqli.inc.php b/lib/adodb/drivers/adodb-mysqli.inc.php
index 7315d0c9d0..72616f6f90 100644
--- a/lib/adodb/drivers/adodb-mysqli.inc.php
+++ b/lib/adodb/drivers/adodb-mysqli.inc.php
@@ -1,6 +1,6 @@
 getRow(sprintf('SHOW CREATE TABLE %s', $table));
-		if ($associative) $create_sql = $a_create_table["Create Table"];
-	    else $create_sql  = $a_create_table[1];
+		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();
 	
@@ -508,8 +509,11 @@ class ADODB_mysqli extends ADOConnection {
 	            $ref_table = strtoupper($ref_table);
 	        }
 	
-	        $foreign_keys[$ref_table] = array();
-	        $num_fields               = count($my_field);
+	        // see https://sourceforge.net/tracker/index.php?func=detail&aid=2287278&group_id=42718&atid=433976
+			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];
@@ -558,8 +562,8 @@ class ADODB_mysqli extends ADOConnection {
 				$fld->type = $query_array[1];
 				$arr = explode(",",$query_array[2]);
 				$fld->enums = $arr;
-				$fld->max_length = max(array_map("strlen",explode(",",$query_array[2]))) - 2; // PHP >= 4.0.6
-				$fld->max_length = ($fld->max_length == 0 ? 1 : $fld->max_length);
+				$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;
@@ -632,7 +636,6 @@ class ADODB_mysqli extends ADOConnection {
 	function Prepare($sql)
 	{
 		return $sql;
-		
 		$stmt = $this->_connectionID->prepare($sql);
 		if (!$stmt) {
 			echo $this->ErrorMsg();
@@ -646,8 +649,18 @@ class ADODB_mysqli extends ADOConnection {
 	function _query($sql, $inputarr)
 	{
 	global $ADODB_COUNTRECS;
-		
+		// Move to the next recordset, or return false if there is none. In a stored proc
+		// call, mysqli_next_result returns true for the last "recordset", but mysqli_store_result
+		// returns false. I think this is because the last "recordset" is actually just the
+		// return value of the stored proc (ie the number of rows affected).
+		// Commented out for reasons of performance. You should retrieve every recordset yourself.
+		//	if (!mysqli_next_result($this->connection->_connectionID))	return false;
+	
 		if (is_array($sql)) {
+		
+			// Prepare() not supported because mysqli_stmt_execute does not return a recordset, but
+			// returns as bound variables.
+		
 			$stmt = $sql[1];
 			$a = '';
 			foreach($inputarr as $k => $v) {
@@ -658,16 +671,28 @@ class ADODB_mysqli extends ADOConnection {
 			
 			$fnarr = array_merge( array($stmt,$a) , $inputarr);
 			$ret = call_user_func_array('mysqli_stmt_bind_param',$fnarr);
-
 			$ret = mysqli_stmt_execute($stmt);
 			return $ret;
 		}
+		
+		/*
 		if (!$mysql_res =  mysqli_query($this->_connectionID, $sql, ($ADODB_COUNTRECS) ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT)) {
 		    if ($this->debug) ADOConnection::outp("Query: " . $sql . " failed. " . $this->ErrorMsg());
 		    return false;
 		}
 		
 		return $mysql_res;
+		*/
+		
+		if( $rs = mysqli_multi_query($this->_connectionID, $sql.';') )//Contributed by "Geisel Sierote" 
+		{
+			$rs = ($ADODB_COUNTRECS) ? @mysqli_store_result( $this->_connectionID ) : @mysqli_use_result( $this->_connectionID );
+			return $rs ? $rs : true; // mysqli_more_results( $this->_connectionID )
+		} else {
+			if($this->debug)
+			ADOConnection::outp("Query: " . $sql . " failed. " . $this->ErrorMsg());
+			return false;
+		}
 	}
 
 	/*	Returns: the last error message from previous database operation	*/	
@@ -821,9 +846,10 @@ class ADORecordSet_mysqli extends ADORecordSet{
 	{	
 		$fieldnr = $fieldOffset;
 		if ($fieldOffset != -1) {
-		  $fieldOffset = mysqli_field_seek($this->_queryID, $fieldnr);
+		  $fieldOffset = @mysqli_field_seek($this->_queryID, $fieldnr);
 		}
-		$o = mysqli_fetch_field($this->_queryID);
+		$o = @mysqli_fetch_field($this->_queryID);
+		if (!$o) return false;
 		/* Properties of an ADOFieldObject as set by MetaColumns */
 		$o->primary_key = $o->flags & MYSQLI_PRI_KEY_FLAG;
 		$o->not_null = $o->flags & MYSQLI_NOT_NULL_FLAG;
@@ -872,6 +898,33 @@ class ADORecordSet_mysqli extends ADORecordSet{
 	  return true;
 	}
 		
+		
+	function NextRecordSet()
+	{
+	global $ADODB_COUNTRECS;
+	
+		mysqli_free_result($this->_queryID);
+		$this->_queryID = -1;
+		// Move to the next recordset, or return false if there is none. In a stored proc
+		// call, mysqli_next_result returns true for the last "recordset", but mysqli_store_result
+		// returns false. I think this is because the last "recordset" is actually just the
+		// return value of the stored proc (ie the number of rows affected).
+		if(!mysqli_next_result($this->connection->_connectionID)) {
+		return false;
+		}
+		// CD: There is no $this->_connectionID variable, at least in the ADO version I'm using
+		$this->_queryID = ($ADODB_COUNTRECS) ? @mysqli_store_result( $this->connection->_connectionID )
+						: @mysqli_use_result( $this->connection->_connectionID );
+		if(!$this->_queryID) {
+			return false;
+		}
+		$this->_inited = false;
+		$this->bind = false;
+		$this->_currentRow = -1;
+		$this->Init();
+		return true;
+	}
+
 	// 10% speedup to move MoveNext to child class
 	// This is the only implementation that works now (23-10-2003).
 	// Other functions return no or the wrong results.
diff --git a/lib/adodb/drivers/adodb-mysqlpo.inc.php b/lib/adodb/drivers/adodb-mysqlpo.inc.php
index 37b1b80832..22c93598b3 100644
--- a/lib/adodb/drivers/adodb-mysqlpo.inc.php
+++ b/lib/adodb/drivers/adodb-mysqlpo.inc.php
@@ -1,7 +1,7 @@
 RollbackTrans();
 		
 		if ($this->transCnt) $this->transCnt -= 1;
-		$this->Execute('COMMIT');
+		$ok = $this->Execute('COMMIT');
 		$this->Execute('SET AUTOCOMMIT=1');
-		return true;
+		return $ok ? true : false;
 	}
 	
 	function RollbackTrans()
 	{
 		if ($this->transOff) return true;
 		if ($this->transCnt) $this->transCnt -= 1;
-		$this->Execute('ROLLBACK');
+		$ok = $this->Execute('ROLLBACK');
 		$this->Execute('SET AUTOCOMMIT=1');
-		return true;
+		return $ok ? true : false;
 	}
 	
 	function RowLock($tables,$where='',$flds='1 as adodb_ignore') 
diff --git a/lib/adodb/drivers/adodb-netezza.inc.php b/lib/adodb/drivers/adodb-netezza.inc.php
index 07256336fe..372d288f49 100644
--- a/lib/adodb/drivers/adodb-netezza.inc.php
+++ b/lib/adodb/drivers/adodb-netezza.inc.php
@@ -1,6 +1,6 @@
 _errorMsg = false;
 		$this->_errorCode = false;
@@ -200,6 +200,11 @@ NATSOFT.DOMAIN =
 					$argHostport = empty($this->port)?  "1521" : $this->port;
 	   			}
 				
+				if (strncasecmp($argDatabasename,'SID=',4) == 0) {
+					$argDatabasename = substr($argDatabasename,4);
+					$this->connectSID = true;
+				}
+				
 				if ($this->connectSID) {
 					$argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname
 					.")(PORT=$argHostport))(CONNECT_DATA=(SID=$argDatabasename)))";
@@ -272,10 +277,10 @@ NATSOFT.DOMAIN =
 	}
 	
 	// format and return date string in database date format
-	function DBDate($d)
+	function DBDate($d,$isfld=false)
 	{
 		if (empty($d) && $d !== 0) return 'null';
-		
+		if ($isfld) return 'TO_DATE('.$d.",'".$this->dateformat."')";
 		if (is_string($d)) $d = ADORecordSet::UnixDate($d);
 		return "TO_DATE(".adodb_date($this->fmtDate,$d).",'".$this->dateformat."')";
 	}
@@ -297,9 +302,10 @@ NATSOFT.DOMAIN =
 	}
 	
 	// format and return date string in database timestamp format
-	function DBTimeStamp($ts)
+	function DBTimeStamp($ts,$isfld=false)
 	{
 		if (empty($ts) && $ts !== 0) return 'null';
+		if ($isfld) return 'TO_DATE(substr('.$ts.",1,19),'RRRR-MM-DD, HH24:MI:SS')";
 		if (is_string($ts)) $ts = ADORecordSet::UnixTimeStamp($ts);
 		return 'TO_DATE('.adodb_date("'Y-m-d H:i:s'",$ts).",'RRRR-MM-DD, HH24:MI:SS')";
 	}
@@ -399,8 +405,10 @@ NATSOFT.DOMAIN =
 		$this->autoCommit = false;
 		$this->_commit = OCI_DEFAULT;
 		
-		if ($this->_transmode) $this->Execute("SET TRANSACTION ".$this->_transmode);
-		return true;
+		if ($this->_transmode) $ok = $this->Execute("SET TRANSACTION ".$this->_transmode);
+		else $ok = true;
+		
+		return $ok ? true : false;
 	}
 	
 	function CommitTrans($ok=true) 
@@ -573,7 +581,7 @@ NATSOFT.DOMAIN =
 				$sql = preg_replace('/^[ \t\n]*select/i','SELECT /*+FIRST_ROWS*/',$sql);
 		}
 		
-		if ($offset < $this->selectOffsetAlg1 && 0 < $nrows && $nrows < 1000) {
+		if ($offset == -1 || ($offset < $this->selectOffsetAlg1 && 0 < $nrows && $nrows < 1000)) {
 			if ($nrows > 0) {	
 				if ($offset > 0) $nrows += $offset;
 				//$inputarr['adodb_rownum'] = $nrows;
@@ -595,14 +603,14 @@ NATSOFT.DOMAIN =
 			
 			 // Let Oracle return the name of the columns
 			$q_fields = "SELECT * FROM (".$sql.") WHERE NULL = NULL";
-			 
+		
 			$false = false;
 			if (! $stmt_arr = $this->Prepare($q_fields)) {
 				return $false;
 			}
 			$stmt = $stmt_arr[1];
 			 
-			 if (is_array($inputarr)) {
+			if (is_array($inputarr)) {
 			 	foreach($inputarr as $k => $v) {
 					if (is_array($v)) {
 						if (sizeof($v) == 2) // suggested by g.giunta@libero.
@@ -616,6 +624,7 @@ NATSOFT.DOMAIN =
 							$bindarr[$k] = $v;
 						} else { 				// dynamic sql, so rebind every time
 							OCIBindByName($stmt,":$k",$inputarr[$k],$len);
+							
 						}
 					}
 				}
@@ -634,16 +643,17 @@ NATSOFT.DOMAIN =
 			
 			 OCIFreeStatement($stmt); 
 			 $fields = implode(',', $cols);
-			 $nrows += $offset;
+			 if ($nrows <= 0) $nrows = 999999999999;
+			 else $nrows += $offset;
 			 $offset += 1; // in Oracle rownum starts at 1
 			
 			if ($this->databaseType == 'oci8po') {
-					 $sql = "SELECT $fields FROM".
+					 $sql = "SELECT /*+ FIRST_ROWS */ $fields FROM".
 					  "(SELECT rownum as adodb_rownum, $fields FROM".
 					  " ($sql) WHERE rownum <= ?".
 					  ") WHERE adodb_rownum >= ?";
 				} else {
-					 $sql = "SELECT $fields FROM".
+					 $sql = "SELECT /*+ FIRST_ROWS */ $fields FROM".
 					  "(SELECT rownum as adodb_rownum, $fields FROM".
 					  " ($sql) WHERE rownum <= :adodb_nrows".
 					  ") WHERE adodb_rownum >= :adodb_offset";
@@ -710,7 +720,7 @@ NATSOFT.DOMAIN =
 	}
 	
 	/**
-	* Usage:  store file pointed to by $var in a blob
+	* Usage:  store file pointed to by $val in a blob
 	*/
 	function UpdateBlobFile($table,$column,$val,$where,$blobtype='BLOB')
 	{
@@ -945,7 +955,7 @@ NATSOFT.DOMAIN =
 			if ($isOutput == false) {
 				$var = $this->BlobEncode($var);
 				$tmp->WriteTemporary($var);
-				$this->_refLOBs[$numlob]['VAR'] = $var;
+				$this->_refLOBs[$numlob]['VAR'] = &$var;
 				if ($this->debug) {
 					ADOConnection::outp("Bind: LOB has been written to temp");
 				}
@@ -1010,7 +1020,7 @@ NATSOFT.DOMAIN =
 		  $db->bind($stmt,1); $db->bind($stmt,2); $db->bind($stmt,3); 
 		  $db->execute($stmt);
 	*/ 
-	function _query($sql,$inputarr)
+	function _query($sql,$inputarr=false)
 	{
 		if (is_array($sql)) { // is prepared sql
 			$stmt = $sql[1];
@@ -1122,6 +1132,32 @@ NATSOFT.DOMAIN =
 		return false;
 	}
 	
+	// From Oracle Whitepaper: PHP Scalability and High Availability
+	function IsConnectionError($err)
+	{
+		switch($err) {
+			case 378: /* buffer pool param incorrect */
+			case 602: /* core dump */
+			case 603: /* fatal error */
+			case 609: /* attach failed */
+			case 1012: /* not logged in */
+			case 1033: /* init or shutdown in progress */
+			case 1043: /* Oracle not available */
+			case 1089: /* immediate shutdown in progress */
+			case 1090: /* shutdown in progress */
+			case 1092: /* instance terminated */
+			case 3113: /* disconnect */
+			case 3114: /* not connected */
+			case 3122: /* closing window */
+			case 3135: /* lost contact */
+			case 12153: /* TNS: not connected */
+			case 27146: /* fatal or instance terminated */
+			case 28511: /* Lost RPC */
+			return true;
+		}
+		return false;
+	}
+	
 	// returns true or false
 	function _close()
 	{
diff --git a/lib/adodb/drivers/adodb-oci8.old.inc.php b/lib/adodb/drivers/adodb-oci8.old.inc.php
new file mode 100644
index 0000000000..6e63c2a3a0
--- /dev/null
+++ b/lib/adodb/drivers/adodb-oci8.old.inc.php
@@ -0,0 +1,1532 @@
+
+  
+  13 Nov 2000 jlim - removed all ora_* references.
+*/
+
+// security - hide paths
+if (!defined('ADODB_DIR')) die();
+
+/*
+NLS_Date_Format
+Allows you to use a date format other than the Oracle Lite default. When a literal
+character string appears where a date value is expected, the Oracle Lite database
+tests the string to see if it matches the formats of Oracle, SQL-92, or the value
+specified for this parameter in the POLITE.INI file. Setting this parameter also
+defines the default format used in the TO_CHAR or TO_DATE functions when no
+other format string is supplied.
+
+For Oracle the default is dd-mon-yy or dd-mon-yyyy, and for SQL-92 the default is
+yy-mm-dd or yyyy-mm-dd.
+
+Using 'RR' in the format forces two-digit years less than or equal to 49 to be
+interpreted as years in the 21st century (2000–2049), and years over 50 as years in
+the 20th century (1950–1999). Setting the RR format as the default for all two-digit
+year entries allows you to become year-2000 compliant. For example:
+NLS_DATE_FORMAT='RR-MM-DD'
+
+You can also modify the date format using the ALTER SESSION command. 
+*/
+
+# define the LOB descriptor type for the given type
+# returns false if no LOB descriptor
+function oci_lob_desc($type) {
+	switch ($type) {
+		case OCI_B_BFILE: $result = OCI_D_FILE; break;
+		case OCI_B_CFILEE: $result = OCI_D_FILE; break;
+		case OCI_B_CLOB: $result = OCI_D_LOB; break;
+		case OCI_B_BLOB: $result = OCI_D_LOB; break;
+		case OCI_B_ROWID: $result = OCI_D_ROWID; break;
+		default: $result = false; break;
+	}
+	return $result;
+}
+
+class ADODB_oci8 extends ADOConnection {
+	var $databaseType = 'oci8';
+	var $dataProvider = 'oci8';
+	var $replaceQuote = "''"; // string to use to replace quotes
+	var $concat_operator='||';
+	var $sysDate = "TRUNC(SYSDATE)";
+	var $sysTimeStamp = 'SYSDATE';
+	var $metaDatabasesSQL = "SELECT USERNAME FROM ALL_USERS WHERE USERNAME NOT IN ('SYS','SYSTEM','DBSNMP','OUTLN') ORDER BY 1";
+	var $_stmt;
+	var $_commit = OCI_COMMIT_ON_SUCCESS;
+	var $_initdate = true; // init date to YYYY-MM-DD
+	var $metaTablesSQL = "select table_name,table_type from cat where table_type in ('TABLE','VIEW') and table_name not like 'BIN\$%'"; // bin$ tables are recycle bin tables
+	var $metaColumnsSQL = "select cname,coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno"; //changed by smondino@users.sourceforge. net
+	var $_bindInputArray = true;
+	var $hasGenID = true;
+	var $_genIDSQL = "SELECT (%s.nextval) FROM DUAL";
+	var $_genSeqSQL = "CREATE SEQUENCE %s START WITH %s";
+	var $_dropSeqSQL = "DROP SEQUENCE %s";
+	var $hasAffectedRows = true;
+	var $random = "abs(mod(DBMS_RANDOM.RANDOM,10000001)/10000000)";
+	var $noNullStrings = false;
+	var $connectSID = false;
+	var $_bind = false;
+	var $_nestedSQL = true;
+	var $_hasOCIFetchStatement = false;
+	var $_getarray = false; // currently not working
+	var $leftOuter = '';  // oracle wierdness, $col = $value (+) for LEFT OUTER, $col (+)= $value for RIGHT OUTER
+	var $session_sharing_force_blob = false; // alter session on updateblob if set to true 
+	var $firstrows = true; // enable first rows optimization on SelectLimit()
+	var $selectOffsetAlg1 = 100; // when to use 1st algorithm of selectlimit.
+	var $NLS_DATE_FORMAT = 'YYYY-MM-DD';  // To include time, use 'RRRR-MM-DD HH24:MI:SS'
+	var $dateformat = 'YYYY-MM-DD'; // DBDate format
+ 	var $useDBDateFormatForTextInput=false;
+	var $datetime = false; // MetaType('DATE') returns 'D' (datetime==false) or 'T' (datetime == true)
+	var $_refLOBs = array();
+		
+	// var $ansiOuter = true; // if oracle9
+    
+	function ADODB_oci8() 
+	{
+		$this->_hasOCIFetchStatement = ADODB_PHPVER >= 0x4200;
+		if (defined('ADODB_EXTENSION')) $this->rsPrefix .= 'ext_';
+	}
+	
+	/*  function MetaColumns($table) added by smondino@users.sourceforge.net*/
+	function MetaColumns($table) 
+	{
+	global $ADODB_FETCH_MODE;
+	
+		$false = false;
+		$save = $ADODB_FETCH_MODE;
+		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
+		if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
+		
+		$rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table)));
+		
+		if (isset($savem)) $this->SetFetchMode($savem);
+		$ADODB_FETCH_MODE = $save;
+		if (!$rs) {
+			return $false;
+		}
+		$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->scale = $rs->fields[3];
+			if ($rs->fields[1] == 'NUMBER') {
+				if ($rs->fields[3] == 0) $fld->type = 'INT';
+	     		$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];
+			
+			if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;	
+			else $retarr[strtoupper($fld->name)] = $fld;
+			$rs->MoveNext();
+		}
+		$rs->Close();
+		if (empty($retarr))
+			return  $false;
+		else 
+			return $retarr;
+	}
+	
+	function Time()
+	{
+		$rs = $this->Execute("select TO_CHAR($this->sysTimeStamp,'YYYY-MM-DD HH24:MI:SS') from dual");
+		if ($rs && !$rs->EOF) return $this->UnixTimeStamp(reset($rs->fields));
+		
+		return false;
+	}
+ 
+/*
+
+  Multiple modes of connection are supported:
+  
+  a. Local Database
+    $conn->Connect(false,'scott','tiger');
+  
+  b. From tnsnames.ora
+    $conn->Connect(false,'scott','tiger',$tnsname); 
+    $conn->Connect($tnsname,'scott','tiger'); 
+  
+  c. Server + service name
+    $conn->Connect($serveraddress,'scott,'tiger',$service_name);
+  
+  d. Server + SID
+  	$conn->connectSID = true;
+	$conn->Connect($serveraddress,'scott,'tiger',$SID);
+
+
+Example TNSName:
+---------------
+NATSOFT.DOMAIN =
+  (DESCRIPTION =
+	(ADDRESS_LIST =
+	  (ADDRESS = (PROTOCOL = TCP)(HOST = kermit)(PORT = 1523))
+	)
+	(CONNECT_DATA =
+	  (SERVICE_NAME = natsoft.domain)
+	)
+  )
+  
+  There are 3 connection modes, 0 = non-persistent, 1 = persistent, 2 = force new connection
+	
+*/
+	function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$mode=0)
+	{
+		if (!function_exists('OCIPLogon')) return null;
+		
+		
+        $this->_errorMsg = false;
+		$this->_errorCode = false;
+		
+		if($argHostname) { // added by Jorma Tuomainen 
+			if (empty($argDatabasename)) $argDatabasename = $argHostname;
+			else {
+				if(strpos($argHostname,":")) {
+					$argHostinfo=explode(":",$argHostname);
+				   	$argHostname=$argHostinfo[0];
+					$argHostport=$argHostinfo[1];
+			 	} else {
+					$argHostport = empty($this->port)?  "1521" : $this->port;
+	   			}
+				
+				if ($this->connectSID) {
+					$argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname
+					.")(PORT=$argHostport))(CONNECT_DATA=(SID=$argDatabasename)))";
+				} else
+					$argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname
+					.")(PORT=$argHostport))(CONNECT_DATA=(SERVICE_NAME=$argDatabasename)))";
+			}
+		}
+				
+ 		//if ($argHostname) print "

Connect: 1st argument should be left blank for $this->databaseType

"; + if ($mode==1) { + $this->_connectionID = ($this->charSet) ? + OCIPLogon($argUsername,$argPassword, $argDatabasename,$this->charSet) + : + OCIPLogon($argUsername,$argPassword, $argDatabasename) + ; + if ($this->_connectionID && $this->autoRollback) OCIrollback($this->_connectionID); + } else if ($mode==2) { + $this->_connectionID = ($this->charSet) ? + OCINLogon($argUsername,$argPassword, $argDatabasename,$this->charSet) + : + OCINLogon($argUsername,$argPassword, $argDatabasename); + + } else { + $this->_connectionID = ($this->charSet) ? + OCILogon($argUsername,$argPassword, $argDatabasename,$this->charSet) + : + OCILogon($argUsername,$argPassword, $argDatabasename); + } + if (!$this->_connectionID) return false; + if ($this->_initdate) { + $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='".$this->NLS_DATE_FORMAT."'"); + } + + // looks like: + // Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production With the Partitioning option JServer Release 8.1.7.0.0 - Production + // $vers = OCIServerVersion($this->_connectionID); + // if (strpos($vers,'8i') !== false) $this->ansiOuter = true; + return true; + } + + function ServerInfo() + { + $arr['compat'] = $this->GetOne('select value from sys.database_compatible_level'); + $arr['description'] = @OCIServerVersion($this->_connectionID); + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,1); + } + + // returns true or false + function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,2); + } + + function _affectedrows() + { + if (is_resource($this->_stmt)) return @OCIRowCount($this->_stmt); + return 0; + } + + function IfNull( $field, $ifNull ) + { + return " NVL($field, $ifNull) "; // if Oracle + } + + // format and return date string in database date format + function DBDate($d) + { + if (empty($d) && $d !== 0) return 'null'; + + if (is_string($d)) $d = ADORecordSet::UnixDate($d); + return "TO_DATE(".adodb_date($this->fmtDate,$d).",'".$this->dateformat."')"; + } + + function BindDate($d) + { + $d = ADOConnection::DBDate($d); + if (strncmp($d,"'",1)) return $d; + + return substr($d,1,strlen($d)-2); + } + + function BindTimeStamp($d) + { + $d = ADOConnection::DBTimeStamp($d); + if (strncmp($d,"'",1)) return $d; + + return substr($d,1,strlen($d)-2); + } + + // format and return date string in database timestamp format + function DBTimeStamp($ts) + { + if (empty($ts) && $ts !== 0) return 'null'; + if (is_string($ts)) $ts = ADORecordSet::UnixTimeStamp($ts); + return 'TO_DATE('.adodb_date("'Y-m-d H:i:s'",$ts).",'RRRR-MM-DD, HH24:MI:SS')"; + } + + function RowLock($tables,$where,$flds='1 as ignore') + { + if ($this->autoCommit) $this->BeginTrans(); + return $this->GetOne("select $flds from $tables where $where for update"); + } + + function MetaTables($ttype=false,$showSchema=false,$mask=false) + { + if ($mask) { + $save = $this->metaTablesSQL; + $mask = $this->qstr(strtoupper($mask)); + $this->metaTablesSQL .= " AND upper(table_name) like $mask"; + } + $ret = ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } + + // Mark Newnham + function MetaIndexes ($table, $primary = FALSE, $owner=false) + { + // save old fetch mode + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + + // get index details + $table = strtoupper($table); + + // get Primary index + $primary_key = ''; + + $false = false; + $rs = $this->Execute(sprintf("SELECT * FROM ALL_CONSTRAINTS WHERE UPPER(TABLE_NAME)='%s' AND CONSTRAINT_TYPE='P'",$table)); + if ($row = $rs->FetchRow()) + $primary_key = $row[1]; //constraint_name + + if ($primary==TRUE && $primary_key=='') { + if (isset($savem)) + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + return $false; //There is no primary key + } + + $rs = $this->Execute(sprintf("SELECT ALL_INDEXES.INDEX_NAME, ALL_INDEXES.UNIQUENESS, ALL_IND_COLUMNS.COLUMN_POSITION, ALL_IND_COLUMNS.COLUMN_NAME FROM ALL_INDEXES,ALL_IND_COLUMNS WHERE UPPER(ALL_INDEXES.TABLE_NAME)='%s' AND ALL_IND_COLUMNS.INDEX_NAME=ALL_INDEXES.INDEX_NAME",$table)); + + + if (!is_object($rs)) { + if (isset($savem)) + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + return $false; + } + + $indexes = array (); + // parse index data into array + + while ($row = $rs->FetchRow()) { + if ($primary && $row[0] != $primary_key) continue; + if (!isset($indexes[$row[0]])) { + $indexes[$row[0]] = array( + 'unique' => ($row[1] == 'UNIQUE'), + 'columns' => array() + ); + } + $indexes[$row[0]]['columns'][$row[2] - 1] = $row[3]; + } + + // sort columns by order in the index + foreach ( array_keys ($indexes) as $index ) { + ksort ($indexes[$index]['columns']); + } + + if (isset($savem)) { + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + } + return $indexes; + } + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->autoCommit = false; + $this->_commit = OCI_DEFAULT; + + if ($this->_transmode) $this->Execute("SET TRANSACTION ".$this->_transmode); + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + + if ($this->transCnt) $this->transCnt -= 1; + $ret = OCIcommit($this->_connectionID); + $this->_commit = OCI_COMMIT_ON_SUCCESS; + $this->autoCommit = true; + return $ret; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $ret = OCIrollback($this->_connectionID); + $this->_commit = OCI_COMMIT_ON_SUCCESS; + $this->autoCommit = true; + return $ret; + } + + + function SelectDB($dbName) + { + return false; + } + + function ErrorMsg() + { + if ($this->_errorMsg !== false) return $this->_errorMsg; + + if (is_resource($this->_stmt)) $arr = @OCIError($this->_stmt); + if (empty($arr)) { + if (is_resource($this->_connectionID)) $arr = @OCIError($this->_connectionID); + else $arr = @OCIError(); + if ($arr === false) return ''; + } + $this->_errorMsg = $arr['message']; + $this->_errorCode = $arr['code']; + return $this->_errorMsg; + } + + function ErrorNo() + { + if ($this->_errorCode !== false) return $this->_errorCode; + + if (is_resource($this->_stmt)) $arr = @OCIError($this->_stmt); + if (empty($arr)) { + $arr = @OCIError($this->_connectionID); + if ($arr == false) $arr = @OCIError(); + if ($arr == false) return ''; + } + + $this->_errorMsg = $arr['message']; + $this->_errorCode = $arr['code']; + + return $arr['code']; + } + + // 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 = 'TO_CHAR('.$col.",'"; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= 'YYYY'; + break; + case 'Q': + case 'q': + $s .= 'Q'; + break; + + case 'M': + $s .= 'Mon'; + break; + + case 'm': + $s .= 'MM'; + break; + case 'D': + case 'd': + $s .= 'DD'; + break; + + case 'H': + $s.= 'HH24'; + break; + + case 'h': + $s .= 'HH'; + break; + + case 'i': + $s .= 'MI'; + break; + + case 's': + $s .= 'SS'; + break; + + case 'a': + case 'A': + $s .= 'AM'; + break; + + case 'w': + $s .= 'D'; + break; + + case 'l': + $s .= 'DAY'; + break; + + case 'W': + $s .= 'WW'; + break; + + default: + // handle escape characters... + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + if (strpos('-/.:;, ',$ch) !== false) $s .= $ch; + else $s .= '"'.$ch.'"'; + + } + } + return $s. "')"; + } + + function GetRandRow($sql, $arr = false) + { + $sql = "SELECT * FROM ($sql ORDER BY dbms_random.value) WHERE rownum = 1"; + + return $this->GetRow($sql,$arr); + } + + /* + This algorithm makes use of + + a. FIRST_ROWS hint + The FIRST_ROWS hint explicitly chooses the approach to optimize response time, + that is, minimum resource usage to return the first row. Results will be returned + as soon as they are identified. + + b. Uses rownum tricks to obtain only the required rows from a given offset. + As this uses complicated sql statements, we only use this if the $offset >= 100. + This idea by Tomas V V Cox. + + This implementation does not appear to work with oracle 8.0.5 or earlier. Comment + out this function then, and the slower SelectLimit() in the base class will be used. + */ + function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + // seems that oracle only supports 1 hint comment in 8i + if ($this->firstrows) { + if (strpos($sql,'/*+') !== false) + $sql = str_replace('/*+ ','/*+FIRST_ROWS ',$sql); + else + $sql = preg_replace('/^[ \t\n]*select/i','SELECT /*+FIRST_ROWS*/',$sql); + } + + if ($offset < $this->selectOffsetAlg1 && 0 < $nrows && $nrows < 1000) { + if ($nrows > 0) { + if ($offset > 0) $nrows += $offset; + //$inputarr['adodb_rownum'] = $nrows; + if ($this->databaseType == 'oci8po') { + $sql = "select * from (".$sql.") where rownum <= ?"; + } else { + $sql = "select * from (".$sql.") where rownum <= :adodb_offset"; + } + $inputarr['adodb_offset'] = $nrows; + $nrows = -1; + } + // note that $nrows = 0 still has to work ==> no rows returned + + $rs = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + return $rs; + + } else { + // Algorithm by Tomas V V Cox, from PEAR DB oci8.php + + // Let Oracle return the name of the columns + $q_fields = "SELECT * FROM (".$sql.") WHERE NULL = NULL"; + + $false = false; + if (! $stmt_arr = $this->Prepare($q_fields)) { + return $false; + } + $stmt = $stmt_arr[1]; + + if (is_array($inputarr)) { + foreach($inputarr as $k => $v) { + if (is_array($v)) { + if (sizeof($v) == 2) // suggested by g.giunta@libero. + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1]); + else + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1],$v[2]); + } else { + $len = -1; + if ($v === ' ') $len = 1; + if (isset($bindarr)) { // is prepared sql, so no need to ocibindbyname again + $bindarr[$k] = $v; + } else { // dynamic sql, so rebind every time + OCIBindByName($stmt,":$k",$inputarr[$k],$len); + } + } + } + } + + if (!OCIExecute($stmt, OCI_DEFAULT)) { + OCIFreeStatement($stmt); + return $false; + } + + $ncols = OCINumCols($stmt); + for ( $i = 1; $i <= $ncols; $i++ ) { + $cols[] = '"'.OCIColumnName($stmt, $i).'"'; + } + $result = false; + + OCIFreeStatement($stmt); + $fields = implode(',', $cols); + $nrows += $offset; + $offset += 1; // in Oracle rownum starts at 1 + + if ($this->databaseType == 'oci8po') { + $sql = "SELECT $fields FROM". + "(SELECT rownum as adodb_rownum, $fields FROM". + " ($sql) WHERE rownum <= ?". + ") WHERE adodb_rownum >= ?"; + } else { + $sql = "SELECT $fields FROM". + "(SELECT rownum as adodb_rownum, $fields FROM". + " ($sql) WHERE rownum <= :adodb_nrows". + ") WHERE adodb_rownum >= :adodb_offset"; + } + $inputarr['adodb_nrows'] = $nrows; + $inputarr['adodb_offset'] = $offset; + + if ($secs2cache>0) $rs = $this->CacheExecute($secs2cache, $sql,$inputarr); + else $rs = $this->Execute($sql,$inputarr); + return $rs; + } + + } + + /** + * Usage: + * Store BLOBs and CLOBs + * + * Example: to store $var in a blob + * + * $conn->Execute('insert into TABLE (id,ablob) values(12,empty_blob())'); + * $conn->UpdateBlob('TABLE', 'ablob', $varHoldingBlob, 'ID=12', 'BLOB'); + * + * $blobtype supports 'BLOB' and 'CLOB', but you need to change to 'empty_clob()'. + * + * to get length of LOB: + * select DBMS_LOB.GETLENGTH(ablob) from TABLE + * + * If you are using CURSOR_SHARING = force, it appears this will case a segfault + * under oracle 8.1.7.0. Run: + * $db->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT'); + * before UpdateBlob() then... + */ + + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + + //if (strlen($val) < 4000) return $this->Execute("UPDATE $table SET $column=:blob WHERE $where",array('blob'=>$val)) != false; + + switch(strtoupper($blobtype)) { + default: ADOConnection::outp("UpdateBlob: Unknown blobtype=$blobtype"); return false; + case 'BLOB': $type = OCI_B_BLOB; break; + case 'CLOB': $type = OCI_B_CLOB; break; + } + + if ($this->databaseType == 'oci8po') + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?"; + else + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob"; + + $desc = OCINewDescriptor($this->_connectionID, OCI_D_LOB); + $arr['blob'] = array($desc,-1,$type); + if ($this->session_sharing_force_blob) $this->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT'); + $commit = $this->autoCommit; + if ($commit) $this->BeginTrans(); + $rs = $this->_Execute($sql,$arr); + if ($rez = !empty($rs)) $desc->save($val); + $desc->free(); + if ($commit) $this->CommitTrans(); + if ($this->session_sharing_force_blob) $this->Execute('ALTER SESSION SET CURSOR_SHARING=FORCE'); + + if ($rez) $rs->Close(); + return $rez; + } + + /** + * Usage: store file pointed to by $var in a blob + */ + function UpdateBlobFile($table,$column,$val,$where,$blobtype='BLOB') + { + switch(strtoupper($blobtype)) { + default: ADOConnection::outp( "UpdateBlob: Unknown blobtype=$blobtype"); return false; + case 'BLOB': $type = OCI_B_BLOB; break; + case 'CLOB': $type = OCI_B_CLOB; break; + } + + if ($this->databaseType == 'oci8po') + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?"; + else + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob"; + + $desc = OCINewDescriptor($this->_connectionID, OCI_D_LOB); + $arr['blob'] = array($desc,-1,$type); + + $this->BeginTrans(); + $rs = ADODB_oci8::Execute($sql,$arr); + if ($rez = !empty($rs)) $desc->savefile($val); + $desc->free(); + $this->CommitTrans(); + + if ($rez) $rs->Close(); + 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) { + $fn = $this->fnExecute; + $ret = $fn($this,$sql,$inputarr); + if (isset($ret)) return $ret; + } + if ($inputarr) { + #if (!is_array($inputarr)) $inputarr = array($inputarr); + + $element0 = reset($inputarr); + + # is_object check because oci8 descriptors can be passed in + if (is_array($element0) && !is_object(reset($element0))) { + if (is_string($sql)) + $stmt = $this->Prepare($sql); + else + $stmt = $sql; + + foreach($inputarr as $arr) { + $ret = $this->_Execute($stmt,$arr); + if (!$ret) return $ret; + } + } else { + $ret = $this->_Execute($sql,$inputarr); + } + + } else { + $ret = $this->_Execute($sql,false); + } + + return $ret; + } + + /* + Example of usage: + + $stmt = $this->Prepare('insert into emp (empno, ename) values (:empno, :ename)'); + */ + function Prepare($sql,$cursor=false) + { + static $BINDNUM = 0; + + $stmt = OCIParse($this->_connectionID,$sql); + + if (!$stmt) { + $this->_errorMsg = false; + $this->_errorCode = false; + $arr = @OCIError($this->_connectionID); + if ($arr === false) return false; + + $this->_errorMsg = $arr['message']; + $this->_errorCode = $arr['code']; + return false; + } + + $BINDNUM += 1; + + $sttype = @OCIStatementType($stmt); + if ($sttype == 'BEGIN' || $sttype == 'DECLARE') { + return array($sql,$stmt,0,$BINDNUM, ($cursor) ? OCINewCursor($this->_connectionID) : false); + } + return array($sql,$stmt,0,$BINDNUM); + } + + /* + Call an oracle stored procedure and returns a cursor variable as a recordset. + Concept by Robert Tuttle robert@ud.com + + Example: + Note: we return a cursor variable in :RS2 + $rs = $db->ExecuteCursor("BEGIN adodb.open_tab(:RS2); END;",'RS2'); + + $rs = $db->ExecuteCursor( + "BEGIN :RS2 = adodb.getdata(:VAR1); END;", + 'RS2', + array('VAR1' => 'Mr Bean')); + + */ + function ExecuteCursor($sql,$cursorName='rs',$params=false) + { + if (is_array($sql)) $stmt = $sql; + else $stmt = ADODB_oci8::Prepare($sql,true); # true to allocate OCINewCursor + + if (is_array($stmt) && sizeof($stmt) >= 5) { + $hasref = true; + $ignoreCur = false; + $this->Parameter($stmt, $ignoreCur, $cursorName, false, -1, OCI_B_CURSOR); + if ($params) { + foreach($params as $k => $v) { + $this->Parameter($stmt,$params[$k], $k); + } + } + } else + $hasref = false; + + $rs = $this->Execute($stmt); + if ($rs) { + if ($rs->databaseType == 'array') OCIFreeCursor($stmt[4]); + else if ($hasref) $rs->_refcursor = $stmt[4]; + } + return $rs; + } + + /* + Bind a variable -- very, very fast for executing repeated statements in oracle. + Better than using + for ($i = 0; $i < $max; $i++) { + $p1 = ?; $p2 = ?; $p3 = ?; + $this->Execute("insert into table (col0, col1, col2) values (:0, :1, :2)", + array($p1,$p2,$p3)); + } + + Usage: + $stmt = $DB->Prepare("insert into table (col0, col1, col2) values (:0, :1, :2)"); + $DB->Bind($stmt, $p1); + $DB->Bind($stmt, $p2); + $DB->Bind($stmt, $p3); + for ($i = 0; $i < $max; $i++) { + $p1 = ?; $p2 = ?; $p3 = ?; + $DB->Execute($stmt); + } + + Some timings: + ** Test table has 3 cols, and 1 index. Test to insert 1000 records + Time 0.6081s (1644.60 inserts/sec) with direct OCIParse/OCIExecute + Time 0.6341s (1577.16 inserts/sec) with ADOdb Prepare/Bind/Execute + Time 1.5533s ( 643.77 inserts/sec) with pure SQL using Execute + + Now if PHP only had batch/bulk updating like Java or PL/SQL... + + Note that the order of parameters differs from OCIBindByName, + because we default the names to :0, :1, :2 + */ + function Bind(&$stmt,&$var,$size=4000,$type=false,$name=false,$isOutput=false) + { + + if (!is_array($stmt)) return false; + + if (($type == OCI_B_CURSOR) && sizeof($stmt) >= 5) { + return OCIBindByName($stmt[1],":".$name,$stmt[4],$size,$type); + } + + if ($name == false) { + if ($type !== false) $rez = OCIBindByName($stmt[1],":".$stmt[2],$var,$size,$type); + else $rez = OCIBindByName($stmt[1],":".$stmt[2],$var,$size); // +1 byte for null terminator + $stmt[2] += 1; + } else if (oci_lob_desc($type)) { + if ($this->debug) { + ADOConnection::outp("Bind: name = $name"); + } + //we have to create a new Descriptor here + $numlob = count($this->_refLOBs); + $this->_refLOBs[$numlob]['LOB'] = OCINewDescriptor($this->_connectionID, oci_lob_desc($type)); + $this->_refLOBs[$numlob]['TYPE'] = $isOutput; + + $tmp = $this->_refLOBs[$numlob]['LOB']; + $rez = OCIBindByName($stmt[1], ":".$name, $tmp, -1, $type); + if ($this->debug) { + ADOConnection::outp("Bind: descriptor has been allocated, var (".$name.") binded"); + } + + // if type is input then write data to lob now + if ($isOutput == false) { + $var = $this->BlobEncode($var); + $tmp->WriteTemporary($var); + $this->_refLOBs[$numlob]['VAR'] = $var; + if ($this->debug) { + ADOConnection::outp("Bind: LOB has been written to temp"); + } + } else { + $this->_refLOBs[$numlob]['VAR'] = $var; + } + $rez = $tmp; + } else { + if ($this->debug) + ADOConnection::outp("Bind: name = $name"); + + if ($type !== false) $rez = OCIBindByName($stmt[1],":".$name,$var,$size,$type); + else $rez = OCIBindByName($stmt[1],":".$name,$var,$size); // +1 byte for null terminator + } + + return $rez; + } + + function Param($name,$type=false) + { + return ':'.$name; + } + + /* + Usage: + $stmt = $db->Prepare('select * from table where id =:myid and group=:group'); + $db->Parameter($stmt,$id,'myid'); + $db->Parameter($stmt,$group,'group'); + $db->Execute($stmt); + + @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. + + See OCIBindByName documentation at php.net. + */ + function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) + { + if ($this->debug) { + $prefix = ($isOutput) ? 'Out' : 'In'; + $ztype = (empty($type)) ? 'false' : $type; + ADOConnection::outp( "{$prefix}Parameter(\$stmt, \$php_var='$var', \$name='$name', \$maxLen=$maxLen, \$type=$ztype);"); + } + return $this->Bind($stmt,$var,$maxLen,$type,$name,$isOutput); + } + + /* + returns query ID if successful, otherwise false + this version supports: + + 1. $db->execute('select * from table'); + + 2. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)'); + $db->execute($prepared_statement, array(1,2,3)); + + 3. $db->execute('insert into table (a,b,c) values (:a,:b,:c)',array('a'=>1,'b'=>2,'c'=>3)); + + 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); + */ + function _query($sql,$inputarr) + { + if (is_array($sql)) { // is prepared sql + $stmt = $sql[1]; + + // we try to bind to permanent array, so that OCIBindByName is persistent + // and carried out once only - note that max array element size is 4000 chars + if (is_array($inputarr)) { + $bindpos = $sql[3]; + if (isset($this->_bind[$bindpos])) { + // all tied up already + $bindarr = $this->_bind[$bindpos]; + } else { + // one statement to bind them all + $bindarr = array(); + foreach($inputarr as $k => $v) { + $bindarr[$k] = $v; + OCIBindByName($stmt,":$k",$bindarr[$k],is_string($v) && strlen($v)>4000 ? -1 : 4000); + } + $this->_bind[$bindpos] = $bindarr; + } + } + } else { + $stmt=OCIParse($this->_connectionID,$sql); + } + + $this->_stmt = $stmt; + if (!$stmt) return false; + + if (defined('ADODB_PREFETCH_ROWS')) @OCISetPrefetch($stmt,ADODB_PREFETCH_ROWS); + + if (is_array($inputarr)) { + foreach($inputarr as $k => $v) { + if (is_array($v)) { + if (sizeof($v) == 2) // suggested by g.giunta@libero. + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1]); + else + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1],$v[2]); + + if ($this->debug==99) { + if (is_object($v[0])) + echo "name=:$k",' len='.$v[1],' type='.$v[2],'
'; + else + echo "name=:$k",' var='.$inputarr[$k][0],' len='.$v[1],' type='.$v[2],'
'; + + } + } else { + $len = -1; + if ($v === ' ') $len = 1; + if (isset($bindarr)) { // is prepared sql, so no need to ocibindbyname again + $bindarr[$k] = $v; + } else { // dynamic sql, so rebind every time + OCIBindByName($stmt,":$k",$inputarr[$k],$len); + } + } + } + } + + $this->_errorMsg = false; + $this->_errorCode = false; + if (OCIExecute($stmt,$this->_commit)) { +//OCIInternalDebug(1); + if (count($this -> _refLOBs) > 0) { + + foreach ($this -> _refLOBs as $key => $value) { + if ($this -> _refLOBs[$key]['TYPE'] == true) { + $tmp = $this -> _refLOBs[$key]['LOB'] -> load(); + if ($this -> debug) { + ADOConnection::outp("OUT LOB: LOB has been loaded.
"); + } + //$_GLOBALS[$this -> _refLOBs[$key]['VAR']] = $tmp; + $this -> _refLOBs[$key]['VAR'] = $tmp; + } else { + $this->_refLOBs[$key]['LOB']->save($this->_refLOBs[$key]['VAR']); + $this -> _refLOBs[$key]['LOB']->free(); + unset($this -> _refLOBs[$key]); + if ($this->debug) { + ADOConnection::outp("IN LOB: LOB has been saved.
"); + } + } + } + } + + switch (@OCIStatementType($stmt)) { + case "SELECT": + return $stmt; + + case 'DECLARE': + case "BEGIN": + if (is_array($sql) && !empty($sql[4])) { + $cursor = $sql[4]; + if (is_resource($cursor)) { + $ok = OCIExecute($cursor); + return $cursor; + } + return $stmt; + } else { + if (is_resource($stmt)) { + OCIFreeStatement($stmt); + return true; + } + return $stmt; + } + break; + default : + // ociclose -- no because it could be used in a LOB? + return true; + } + } + return false; + } + + // returns true or false + function _close() + { + if (!$this->_connectionID) return; + + if (!$this->autoCommit) OCIRollback($this->_connectionID); + if (count($this->_refLOBs) > 0) { + foreach ($this ->_refLOBs as $key => $value) { + $this->_refLOBs[$key]['LOB']->free(); + unset($this->_refLOBs[$key]); + } + } + OCILogoff($this->_connectionID); + + $this->_stmt = false; + $this->_connectionID = false; + } + + function MetaPrimaryKeys($table, $owner=false,$internalKey=false) + { + if ($internalKey) return array('ROWID'); + + // tested with oracle 8.1.7 + $table = strtoupper($table); + if ($owner) { + $owner_clause = "AND ((a.OWNER = b.OWNER) AND (a.OWNER = UPPER('$owner')))"; + $ptab = 'ALL_'; + } else { + $owner_clause = ''; + $ptab = 'USER_'; + } + $sql = " +SELECT /*+ RULE */ distinct b.column_name + FROM {$ptab}CONSTRAINTS a + , {$ptab}CONS_COLUMNS b + WHERE ( UPPER(b.table_name) = ('$table')) + AND (UPPER(a.table_name) = ('$table') and a.constraint_type = 'P') + $owner_clause + AND (a.constraint_name = b.constraint_name)"; + + $rs = $this->Execute($sql); + if ($rs && !$rs->EOF) { + $arr = $rs->GetArray(); + $a = array(); + foreach($arr as $v) { + $a[] = reset($v); + } + return $a; + } + else return false; + } + + // http://gis.mit.edu/classes/11.521/sqlnotes/referential_integrity.html + function MetaForeignKeys($table, $owner=false) + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $table = $this->qstr(strtoupper($table)); + if (!$owner) { + $owner = $this->user; + $tabp = 'user_'; + } else + $tabp = 'all_'; + + $owner = ' and owner='.$this->qstr(strtoupper($owner)); + + $sql = +"select constraint_name,r_owner,r_constraint_name + from {$tabp}constraints + where constraint_type = 'R' and table_name = $table $owner"; + + $constraints = $this->GetArray($sql); + $arr = false; + foreach($constraints as $constr) { + $cons = $this->qstr($constr[0]); + $rowner = $this->qstr($constr[1]); + $rcons = $this->qstr($constr[2]); + $cols = $this->GetArray("select column_name from {$tabp}cons_columns where constraint_name=$cons $owner order by position"); + $tabcol = $this->GetArray("select table_name,column_name from {$tabp}cons_columns where owner=$rowner and constraint_name=$rcons order by position"); + + if ($cols && $tabcol) + for ($i=0, $max=sizeof($cols); $i < $max; $i++) { + $arr[$tabcol[$i][0]] = $cols[$i][0].'='.$tabcol[$i][1]; + } + } + $ADODB_FETCH_MODE = $save; + + return $arr; + } + + + function CharMax() + { + return 4000; + } + + function TextMax() + { + return 4000; + } + + /** + * Quotes a string. + * An example is $db->qstr("Don't bother",magic_quotes_runtime()); + * + * @param s the string to quote + * @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc(). + * This undoes the stupidity of magic quotes for GPC. + * + * @return quoted string to be sent back to database + */ + function qstr($s,$magic_quotes=false) + { + //$nofixquotes=false; + + if ($this->noNullStrings && strlen($s)==0)$s = ' '; + if (!$magic_quotes) { + if ($this->replaceQuote[0] == '\\'){ + $s = str_replace('\\','\\\\',$s); + } + return "'".str_replace("'",$this->replaceQuote,$s)."'"; + } + + // undo magic quotes for " + $s = str_replace('\\"','"',$s); + + $s = str_replace('\\\\','\\',$s); + return "'".str_replace("\\'",$this->replaceQuote,$s)."'"; + + } + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_oci8 extends ADORecordSet { + + var $databaseType = 'oci8'; + var $bind=false; + var $_fieldobjs; + + //var $_arr = false; + + function ADORecordset_oci8($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_ASSOC:$this->fetchMode = OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH:$this->fetchMode = OCI_NUM+OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_NUM: + default: + $this->fetchMode = OCI_NUM+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + } + + $this->adodbFetchMode = $mode; + $this->_queryID = $queryID; + } + + + function Init() + { + if ($this->_inited) return; + + $this->_inited = true; + if ($this->_queryID) { + + $this->_currentRow = 0; + @$this->_initrs(); + $this->EOF = !$this->_fetch(); + + /* + // based on idea by Gaetano Giunta to detect unusual oracle errors + // see http://phplens.com/lens/lensforum/msgs.php?id=6771 + $err = OCIError($this->_queryID); + if ($err && $this->connection->debug) ADOConnection::outp($err); + */ + + if (!is_array($this->fields)) { + $this->_numOfRows = 0; + $this->fields = array(); + } + } else { + $this->fields = array(); + $this->_numOfRows = 0; + $this->_numOfFields = 0; + $this->EOF = true; + } + } + + function _initrs() + { + $this->_numOfRows = -1; + $this->_numOfFields = OCInumcols($this->_queryID); + if ($this->_numOfFields>0) { + $this->_fieldobjs = array(); + $max = $this->_numOfFields; + for ($i=0;$i<$max; $i++) $this->_fieldobjs[] = $this->_FetchField($i); + } + } + + /* 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. */ + + function _FetchField($fieldOffset = -1) + { + $fld = new ADOFieldObject; + $fieldOffset += 1; + $fld->name =OCIcolumnname($this->_queryID, $fieldOffset); + $fld->type = OCIcolumntype($this->_queryID, $fieldOffset); + $fld->max_length = OCIcolumnsize($this->_queryID, $fieldOffset); + switch($fld->type) { + case 'NUMBER': + $p = OCIColumnPrecision($this->_queryID, $fieldOffset); + $sc = OCIColumnScale($this->_queryID, $fieldOffset); + if ($p != 0 && $sc == 0) $fld->type = 'INT'; + break; + + case 'CLOB': + case 'NCLOB': + case 'BLOB': + $fld->max_length = -1; + break; + } + return $fld; + } + + /* For some reason, OCIcolumnname fails when called after _initrs() so we cache it */ + function FetchField($fieldOffset = -1) + { + return $this->_fieldobjs[$fieldOffset]; + } + + + /* + // 10% speedup to move MoveNext to child class + function _MoveNext() + { + //global $ADODB_EXTENSION;if ($ADODB_EXTENSION) return @adodb_movenext($this); + + if ($this->EOF) return false; + + $this->_currentRow++; + if(@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) + return true; + $this->EOF = true; + + return false; + } */ + + + function MoveNext() + { + if (@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) { + $this->_currentRow += 1; + return true; + } + if (!$this->EOF) { + $this->_currentRow += 1; + $this->EOF = true; + } + return false; + } + + /* + # does not work as first record is retrieved in _initrs(), so is not included in GetArray() + function GetArray($nRows = -1) + { + global $ADODB_OCI8_GETARRAY; + + if (true || !empty($ADODB_OCI8_GETARRAY)) { + # does not support $ADODB_ANSI_PADDING_OFF + + //OCI_RETURN_NULLS and OCI_RETURN_LOBS is set by OCIfetchstatement + switch($this->adodbFetchMode) { + case ADODB_FETCH_NUM: + + $ncols = @OCIfetchstatement($this->_queryID, $results, 0, $nRows, OCI_FETCHSTATEMENT_BY_ROW+OCI_NUM); + $results = array_merge(array($this->fields),$results); + return $results; + + case ADODB_FETCH_ASSOC: + if (ADODB_ASSOC_CASE != 2 || $this->databaseType != 'oci8') break; + + $ncols = @OCIfetchstatement($this->_queryID, $assoc, 0, $nRows, OCI_FETCHSTATEMENT_BY_ROW); + $results = array_merge(array($this->fields),$assoc); + return $results; + + default: + break; + } + } + + $results = ADORecordSet::GetArray($nRows); + return $results; + + } */ + + /* Optimize SelectLimit() by using OCIFetch() instead of OCIFetchInto() */ + function GetArrayLimit($nrows,$offset=-1) + { + if ($offset <= 0) { + $arr = $this->GetArray($nrows); + return $arr; + } + $arr = array(); + for ($i=1; $i < $offset; $i++) + if (!@OCIFetch($this->_queryID)) return $arr; + + if (!@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) return $arr;; + $results = array(); + $cnt = 0; + while (!$this->EOF && $nrows != $cnt) { + $results[$cnt++] = $this->fields; + $this->MoveNext(); + } + + return $results; + } + + + /* Use associative array to get fields array */ + function 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) + { + return false; + } + + function _fetch() + { + return @OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode); + } + + /* close() only needs to be called if you are worried about using too much memory while your script + is running. All associated result memory for the specified result identifier will automatically be freed. */ + + function _close() + { + if ($this->connection->_stmt === $this->_queryID) $this->connection->_stmt = false; + if (!empty($this->_refcursor)) { + OCIFreeCursor($this->_refcursor); + $this->_refcursor = false; + } + @OCIFreeStatement($this->_queryID); + $this->_queryID = false; + + } + + function MetaType($t,$len=-1) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + switch (strtoupper($t)) { + case 'VARCHAR': + case 'VARCHAR2': + case 'CHAR': + case 'VARBINARY': + case 'BINARY': + case 'NCHAR': + case 'NVARCHAR': + case 'NVARCHAR2': + if (isset($this) && $len <= $this->blobSize) return 'C'; + + case 'NCLOB': + case 'LONG': + case 'LONG VARCHAR': + case 'CLOB': + return 'X'; + + case 'LONG RAW': + case 'LONG VARBINARY': + case 'BLOB': + return 'B'; + + case 'DATE': + return ($this->connection->datetime) ? 'T' : 'D'; + + + case 'TIMESTAMP': return 'T'; + + case 'INT': + case 'SMALLINT': + case 'INTEGER': + return 'I'; + + default: return 'N'; + } + } +} + +class ADORecordSet_ext_oci8 extends ADORecordSet_oci8 { + function ADORecordSet_ext_oci8($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_ASSOC:$this->fetchMode = OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH:$this->fetchMode = OCI_NUM+OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_NUM: + default: $this->fetchMode = OCI_NUM+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + } + $this->adodbFetchMode = $mode; + $this->_queryID = $queryID; + } + + function MoveNext() + { + return adodb_movenext($this); + } +} +?> diff --git a/lib/adodb/drivers/adodb-oci805.inc.php b/lib/adodb/drivers/adodb-oci805.inc.php index b1c75b148a..30518721ee 100644 --- a/lib/adodb/drivers/adodb-oci805.inc.php +++ b/lib/adodb/drivers/adodb-oci805.inc.php @@ -1,6 +1,6 @@ name = strtolower(OCIcolumnname($this->_queryID, $fieldOffset)); + $fld->name = OCIcolumnname($this->_queryID, $fieldOffset); + if (ADODB_ASSOC_CASE == 0) $fld->name = strtolower($fld->name); $fld->type = OCIcolumntype($this->_queryID, $fieldOffset); $fld->max_length = OCIcolumnsize($this->_queryID, $fieldOffset); if ($fld->type == 'NUMBER') { diff --git a/lib/adodb/drivers/adodb-odbc.inc.php b/lib/adodb/drivers/adodb-odbc.inc.php index 38099920ac..c799c00b01 100644 --- a/lib/adodb/drivers/adodb-odbc.inc.php +++ b/lib/adodb/drivers/adodb-odbc.inc.php @@ -1,6 +1,6 @@ _errorMsg !== false) return $this->_errorMsg; if (empty($this->_connectionID)) return @odbtp_last_error(); return @odbtp_last_error($this->_connectionID); } function ErrorNo() { + if ($this->_errorCode !== false) return $this->_errorCode; if (empty($this->_connectionID)) return @odbtp_last_error_state(); return @odbtp_last_error_state($this->_connectionID); } - +/* + function DBDate($d,$isfld=false) + { + if (empty($d) && $d !== 0) return 'null'; + if ($isfld) return "convert(date, $d, 120)"; + + if (is_string($d)) $d = ADORecordSet::UnixDate($d); + $d = adodb_date($this->fmtDate,$d); + return "convert(date, $d, 120)"; + } + + function DBTimeStamp($d,$isfld=false) + { + if (empty($d) && $d !== 0) return 'null'; + if ($isfld) return "convert(datetime, $d, 120)"; + + if (is_string($d)) $d = ADORecordSet::UnixDate($d); + $d = adodb_date($this->fmtDate,$d); + return "convert(datetime, $d, 120)"; + } +*/ + function _insertid() { // SCOPE_IDENTITY() @@ -437,6 +460,10 @@ class ADODB_odbtp extends ADOConnection{ function Prepare($sql) { if (! $this->_bindInputArray) return $sql; // no binding + + $this->_errorMsg = false; + $this->_errorCode = false; + $stmt = @odbtp_prepare($sql,$this->_connectionID); if (!$stmt) { // print "Prepare Error for ($sql) ".$this->ErrorMsg()."
"; @@ -449,6 +476,9 @@ class ADODB_odbtp extends ADOConnection{ { if (!$this->_canPrepareSP) return $sql; // Can't prepare procedures + $this->_errorMsg = false; + $this->_errorCode = false; + $stmt = @odbtp_prepare_proc($sql,$this->_connectionID); if (!$stmt) return false; return array($sql,$stmt); @@ -511,6 +541,56 @@ class ADODB_odbtp extends ADOConnection{ return @odbtp_execute( $stmt ) != false; } + function MetaIndexes($table,$primary=false) + { + switch ( $this->odbc_driver) { + case ODB_DRIVER_MSSQL: + return $this->MetaIndexes_mssql($table, $primary); + default: + return array(); + } + } + + function MetaIndexes_mssql($table,$primary=false) + { + $table = strtolower($this->qstr($table)); + + $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno, + CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK, + CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique + FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id + INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid + INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid + WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND lower(O.Name) = $table + ORDER BY O.name, I.Name, K.keyno"; + + 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($sql); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if (!is_object($rs)) { + return FALSE; + } + + $indexes = array(); + while ($row = $rs->FetchRow()) { + if ($primary && !$row[5]) continue; + + $indexes[$row[0]]['unique'] = $row[6]; + $indexes[$row[0]]['columns'][] = $row[1]; + } + return $indexes; + } + function IfNull( $field, $ifNull ) { switch( $this->odbc_driver ) { @@ -526,6 +606,9 @@ class ADODB_odbtp extends ADOConnection{ { global $php_errormsg; + $this->_errorMsg = false; + $this->_errorCode = false; + if ($inputarr) { if (is_array($sql)) { $stmtid = $sql[1]; @@ -537,10 +620,20 @@ class ADODB_odbtp extends ADOConnection{ } } $num_params = @odbtp_num_params( $stmtid ); + /* for( $param = 1; $param <= $num_params; $param++ ) { @odbtp_input( $stmtid, $param ); @odbtp_set( $stmtid, $param, $inputarr[$param-1] ); + }*/ + + $param = 1; + foreach($inputarr as $v) { + @odbtp_input( $stmtid, $param ); + @odbtp_set( $stmtid, $param, $v ); + $param += 1; + if ($param > $num_params) break; } + if (!@odbtp_execute($stmtid) ) { return false; } diff --git a/lib/adodb/drivers/adodb-odbtp_unicode.inc.php b/lib/adodb/drivers/adodb-odbtp_unicode.inc.php index 5af24c0f97..ad26c528bd 100644 --- a/lib/adodb/drivers/adodb-odbtp_unicode.inc.php +++ b/lib/adodb/drivers/adodb-odbtp_unicode.inc.php @@ -1,6 +1,6 @@ dsnType.'.inc.php'); break; } @@ -174,6 +175,15 @@ class ADODB_pdo extends ADOConnection { return false; } + function Concat() + { + $args = func_get_args(); + if(method_exists($this->_driver, 'Concat')) + return call_user_func_array(array($this->_driver, 'Concat'), $args); + + return call_user_func_array(array($this,'parent::Concat'), $args); + } + // returns true or false function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename) { @@ -216,6 +226,10 @@ class ADODB_pdo extends ADOConnection { else $obj->bindParam($name, $var); } + function OffsetDate($dayFraction,$date=false) + { + return $this->_driver->OffsetDate($dayFraction,$date); + } function ErrorMsg() { @@ -248,8 +262,19 @@ class ADODB_pdo extends ADOConnection { return $err; } + function SetTransactionMode($transaction_mode) + { + if(method_exists($this->_driver, 'SetTransactionMode')) + return $this->_driver->SetTransactionMode($transaction_mode); + + return parent::SetTransactionMode($seqname); + } + function BeginTrans() { + if(method_exists($this->_driver, 'BeginTrans')) + return $this->_driver->BeginTrans(); + if (!$this->hasTransactions) return false; if ($this->transOff) return true; $this->transCnt += 1; @@ -260,6 +285,9 @@ class ADODB_pdo extends ADOConnection { function CommitTrans($ok=true) { + if(method_exists($this->_driver, 'CommitTrans')) + return $this->_driver->CommitTrans($ok); + if (!$this->hasTransactions) return false; if ($this->transOff) return true; if (!$ok) return $this->RollbackTrans(); @@ -273,6 +301,9 @@ class ADODB_pdo extends ADOConnection { function RollbackTrans() { + if(method_exists($this->_driver, 'RollbackTrans')) + return $this->_driver->RollbackTrans(); + if (!$this->hasTransactions) return false; if ($this->transOff) return true; if ($this->transCnt) $this->transCnt -= 1; @@ -299,6 +330,30 @@ class ADODB_pdo extends ADOConnection { return $obj; } + function CreateSequence($seqname='adodbseq',$startID=1) + { + if(method_exists($this->_driver, 'CreateSequence')) + return $this->_driver->CreateSequence($seqname, $startID); + + return parent::CreateSequence($seqname, $startID); + } + + function DropSequence($seqname='adodbseq') + { + if(method_exists($this->_driver, 'DropSequence')) + return $this->_driver->DropSequence($seqname); + + return parent::DropSequence($seqname); + } + + function GenID($seqname='adodbseq',$startID=1) + { + if(method_exists($this->_driver, 'GenID')) + return $this->_driver->GenID($seqname, $startID); + + return parent::GenID($seqname, $startID); + } + /* returns queryID or false */ function _query($sql,$inputarr=false) diff --git a/lib/adodb/drivers/adodb-pdo_mssql.inc.php b/lib/adodb/drivers/adodb-pdo_mssql.inc.php index a683094272..270b57cc08 100644 --- a/lib/adodb/drivers/adodb-pdo_mssql.inc.php +++ b/lib/adodb/drivers/adodb-pdo_mssql.inc.php @@ -2,7 +2,7 @@ /* -V5.04a 25 Mar 2008 (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved. +V5.08 6 Apr 2009 (c) 2000-2009 John Lim (jlim#natsoft.com). All rights reserved. Released under both BSD license and Lesser GPL library license. Whenever there is any discrepancy between the two licenses, the BSD license will take precedence. @@ -47,12 +47,12 @@ class ADODB_pdo_mssql extends ADODB_pdo { $this->Execute("SET TRANSACTION ".$transaction_mode); } - function MetaTables() + function MetaTables($ttype=false,$showSchema=false,$mask=false) { return false; } - function MetaColumns() + function MetaColumns($table,$normalize=true) { return false; } diff --git a/lib/adodb/drivers/adodb-pdo_mysql.inc.php b/lib/adodb/drivers/adodb-pdo_mysql.inc.php index 70b73b0174..b6498e437a 100644 --- a/lib/adodb/drivers/adodb-pdo_mysql.inc.php +++ b/lib/adodb/drivers/adodb-pdo_mysql.inc.php @@ -2,7 +2,7 @@ /* -V5.04a 25 Mar 2008 (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved. +V5.08 6 Apr 2009 (c) 2000-2009 John Lim (jlim#natsoft.com). All rights reserved. Released under both BSD license and Lesser GPL library license. Whenever there is any discrepancy between the two licenses, the BSD license will take precedence. @@ -18,14 +18,14 @@ class ADODB_pdo_mysql extends ADODB_pdo { var $hasGenID = true; var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);"; var $_dropSeqSQL = "drop table %s"; - + var $fmtTimeStamp = "'Y-m-d, H:i:s'"; var $nameQuote = '`'; function _init($parentDriver) { $parentDriver->hasTransactions = false; - $parentDriver->_bindInputArray = false; + #$parentDriver->_bindInputArray = false; $parentDriver->hasInsertID = true; $parentDriver->_connectionID->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true); } @@ -41,6 +41,16 @@ class ADODB_pdo_mysql extends ADODB_pdo { // return "from_unixtime(unix_timestamp($date)+$fraction)"; } + function Concat() + { + $s = ""; + $arr = func_get_args(); + + // suggestion by andrew005#mnogo.ru + $s = implode(',',$arr); + if (strlen($s) > 0) return "CONCAT($s)"; return ''; + } + function ServerInfo() { $arr['description'] = ADOConnection::GetOne("select version()"); @@ -76,7 +86,7 @@ class ADODB_pdo_mysql extends ADODB_pdo { $this->Execute("SET SESSION TRANSACTION ".$transaction_mode); } - function MetaColumns($table) + function MetaColumns($table,$normalize=true) { $this->_findschema($table,$schema); if ($schema) { diff --git a/lib/adodb/drivers/adodb-pdo_oci.inc.php b/lib/adodb/drivers/adodb-pdo_oci.inc.php index b5f5868a4a..1f58ea8ed1 100644 --- a/lib/adodb/drivers/adodb-pdo_oci.inc.php +++ b/lib/adodb/drivers/adodb-pdo_oci.inc.php @@ -2,7 +2,7 @@ /* -V5.04a 25 Mar 2008 (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved. +V5.08 6 Apr 2009 (c) 2000-2009 John Lim (jlim#natsoft.com). All rights reserved. Released under both BSD license and Lesser GPL library license. Whenever there is any discrepancy between the two licenses, the BSD license will take precedence. @@ -47,7 +47,7 @@ class ADODB_pdo_oci extends ADODB_pdo_base { return $ret; } - function MetaColumns($table) + function MetaColumns($table,$normalize=true) { global $ADODB_FETCH_MODE; diff --git a/lib/adodb/drivers/adodb-pdo_pgsql.inc.php b/lib/adodb/drivers/adodb-pdo_pgsql.inc.php index 1bf137f8c3..f8443bcdc7 100644 --- a/lib/adodb/drivers/adodb-pdo_pgsql.inc.php +++ b/lib/adodb/drivers/adodb-pdo_pgsql.inc.php @@ -1,7 +1,7 @@ pdoDriver = $parentDriver; + $parentDriver->_bindInputArray = true; + $parentDriver->hasTransactions = true; + $parentDriver->hasInsertID = true; + } + + function ServerInfo() + { + $parent = $this->pdoDriver; + @($ver = array_pop($parent->GetCol("SELECT sqlite_version()"))); + @($end = array_pop($parent->GetCol("PRAGMA encoding"))); + + $arr['version'] = $ver; + $arr['description'] = 'SQLite '; + $arr['encoding'] = $enc; + + return $arr; + } + + function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + $parent = $this->pdoDriver; + $offsetStr = ($offset >= 0) ? " OFFSET $offset" : ''; + $limitStr = ($nrows >= 0) ? " LIMIT $nrows" : ($offset >= 0 ? ' LIMIT 999999999' : ''); + if ($secs2cache) + $rs = $parent->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr); + else + $rs = $parent->Execute($sql."$limitStr$offsetStr",$inputarr); + + return $rs; + } + + function GenID($seq='adodbseq',$start=1) + { + $parent = $this->pdoDriver; + // if you have to modify the parameter below, your database is overloaded, + // or you need to implement generation of id's yourself! + $MAXLOOPS = 100; + while (--$MAXLOOPS>=0) { + @($num = array_pop($parent->GetCol("SELECT id FROM {$seq}"))); + if ($num === false || !is_numeric($num)) { + @$parent->Execute(sprintf($this->_genSeqSQL ,$seq)); + $start -= 1; + $num = '0'; + $cnt = $parent->GetOne(sprintf($this->_genSeqCountSQL,$seq)); + if (!$cnt) { + $ok = $parent->Execute(sprintf($this->_genSeq2SQL,$seq,$start)); + } + if (!$ok) return false; + } + $parent->Execute(sprintf($this->_genIDSQL,$seq,$num)); + + if ($parent->affected_rows() > 0) { + $num += 1; + $parent->genID = intval($num); + return intval($num); + } + } + if ($fn = $parent->raiseErrorFn) { + $fn($parent->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num); + } + return false; + } + + function CreateSequence($seqname='adodbseq',$start=1) + { + $parent = $this->pdoDriver; + $ok = $parent->Execute(sprintf($this->_genSeqSQL,$seqname)); + if (!$ok) return false; + $start -= 1; + return $parent->Execute("insert into $seqname values($start)"); + } + + function SetTransactionMode($transaction_mode) + { + $parent = $this->pdoDriver; + $parent->_transmode = strtoupper($transaction_mode); + } + + function BeginTrans() + { + $parent = $this->pdoDriver; + if ($parent->transOff) return true; + $parent->transCnt += 1; + $parent->_autocommit = false; + return $parent->Execute("BEGIN {$parent->_transmode}"); + } + + function CommitTrans($ok=true) + { + $parent = $this->pdoDriver; + if ($parent->transOff) return true; + if (!$ok) return $parent->RollbackTrans(); + if ($parent->transCnt) $parent->transCnt -= 1; + $parent->_autocommit = true; + + $ret = $parent->Execute('COMMIT'); + return $ret; + } + + function RollbackTrans() + { + $parent = $this->pdoDriver; + if ($parent->transOff) return true; + if ($parent->transCnt) $parent->transCnt -= 1; + $parent->_autocommit = true; + + $ret = $parent->Execute('ROLLBACK'); + return $ret; + } + + + // 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; + } + + function MetaTables($ttype=false,$showSchema=false,$mask=false) + { + $parent = $this->pdoDriver; + return $parent->GetCol($this->metaTablesSQL); + } +} +?> \ No newline at end of file diff --git a/lib/adodb/drivers/adodb-postgres.inc.php b/lib/adodb/drivers/adodb-postgres.inc.php index 916ad98eca..ffd885b33e 100644 --- a/lib/adodb/drivers/adodb-postgres.inc.php +++ b/lib/adodb/drivers/adodb-postgres.inc.php @@ -1,6 +1,6 @@ = 0x5200) { return "'".pg_escape_string($this->_connectionID,$s)."'"; @@ -713,7 +715,7 @@ WHERE (c2.relname=\'%s\' or c2.relname=lower(\'%s\'))'; // returns queryID or false - function _query($sql,$inputarr) + function _query($sql,$inputarr=false) { $this->_errorMsg = false; if ($inputarr) { @@ -943,6 +945,7 @@ class ADORecordSet_postgres64 extends ADORecordSet{ function _decode($blob) { + if ($blob === NULL) return NULL; eval('$realblob="'.adodb_str_replace(array('"','$'),array('\"','\$'),$blob).'";'); return $realblob; } diff --git a/lib/adodb/drivers/adodb-postgres7.inc.php b/lib/adodb/drivers/adodb-postgres7.inc.php index ded804ec50..63fc9a14c3 100644 --- a/lib/adodb/drivers/adodb-postgres7.inc.php +++ b/lib/adodb/drivers/adodb-postgres7.inc.php @@ -1,6 +1,6 @@ Execute($sql); + + if (!$rs || $rs->EOF) return false; + + $a = array(); + while (!$rs->EOF) { + 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')); + } + $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 @@ -91,7 +140,7 @@ class ADODB_postgres7 extends ADODB_postgres64 { return $a; } - function _query($sql,$inputarr) + function _query($sql,$inputarr=false) { if (! $this->_bindInputArray) { // We don't have native support for parameterized queries, so let's emulate it at the parent diff --git a/lib/adodb/drivers/adodb-postgres8.inc.php b/lib/adodb/drivers/adodb-postgres8.inc.php index 08267ecc9d..f4bab5c6dc 100644 --- a/lib/adodb/drivers/adodb-postgres8.inc.php +++ b/lib/adodb/drivers/adodb-postgres8.inc.php @@ -1,6 +1,6 @@ charSet) { + $this->_connectionID = sybase_connect($argHostname,$argUsername,$argPassword, $this->charSet); + } else { + $this->_connectionID = sybase_connect($argHostname,$argUsername,$argPassword); + } + $this->_connectionID = sybase_connect($argHostname,$argUsername,$argPassword); if ($this->_connectionID === false) return false; if ($argDatabasename) return $this->SelectDB($argDatabasename); @@ -133,14 +139,18 @@ class ADODB_sybase extends ADOConnection { { if (!function_exists('sybase_connect')) return null; - $this->_connectionID = sybase_pconnect($argHostname,$argUsername,$argPassword); + if ($this->charSet) { + $this->_connectionID = sybase_pconnect($argHostname,$argUsername,$argPassword, $this->charSet); + } else { + $this->_connectionID = sybase_pconnect($argHostname,$argUsername,$argPassword); + } if ($this->_connectionID === false) return false; if ($argDatabasename) return $this->SelectDB($argDatabasename); return true; } // returns query ID if successful, otherwise false - function _query($sql,$inputarr) + function _query($sql,$inputarr=false) { global $ADODB_COUNTRECS; @@ -177,12 +187,12 @@ class ADODB_sybase extends ADOConnection { return @sybase_close($this->_connectionID); } - function UnixDate($v) + static function UnixDate($v) { return ADORecordSet_array_sybase::UnixDate($v); } - function UnixTimeStamp($v) + static function UnixTimeStamp($v) { return ADORecordSet_array_sybase::UnixTimeStamp($v); } @@ -211,7 +221,7 @@ class ADODB_sybase extends ADOConnection { $s .= "convert(char(3),$col,0)"; break; case 'm': - $s .= "replace(str(month($col),2),' ','0')"; + $s .= "str_replace(str(month($col),2),' ','0')"; break; case 'Q': case 'q': @@ -219,21 +229,21 @@ class ADODB_sybase extends ADOConnection { break; case 'D': case 'd': - $s .= "replace(str(datepart(dd,$col),2),' ','0')"; + $s .= "str_replace(str(datepart(dd,$col),2),' ','0')"; break; case 'h': $s .= "substring(convert(char(14),$col,0),13,2)"; break; case 'H': - $s .= "replace(str(datepart(hh,$col),2),' ','0')"; + $s .= "str_replace(str(datepart(hh,$col),2),' ','0')"; break; case 'i': - $s .= "replace(str(datepart(mi,$col),2),' ','0')"; + $s .= "str_replace(str(datepart(mi,$col),2),' ','0')"; break; case 's': - $s .= "replace(str(datepart(ss,$col),2),' ','0')"; + $s .= "str_replace(str(datepart(ss,$col),2),' ','0')"; break; case 'a': case 'A': @@ -353,12 +363,12 @@ class ADORecordset_sybase extends ADORecordSet { } // sybase/mssql uses a default date like Dec 30 2000 12:00AM - function UnixDate($v) + static function UnixDate($v) { return ADORecordSet_array_sybase::UnixDate($v); } - function UnixTimeStamp($v) + static function UnixTimeStamp($v) { return ADORecordSet_array_sybase::UnixTimeStamp($v); } @@ -371,12 +381,12 @@ class ADORecordSet_array_sybase extends ADORecordSet_array { } // sybase/mssql uses a default date like Dec 30 2000 12:00AM - function UnixDate($v) + static function UnixDate($v) { global $ADODB_sybase_mths; //Dec 30 2000 12:00AM - if (!ereg( "([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})" + if (!preg_match( "/([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})/" ,$v, $rr)) return parent::UnixDate($v); if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; @@ -388,12 +398,12 @@ class ADORecordSet_array_sybase extends ADORecordSet_array { return mktime(0,0,0,$themth,$rr[2],$rr[3]); } - function UnixTimeStamp($v) + static function UnixTimeStamp($v) { global $ADODB_sybase_mths; //11.02.2001 Toni Tunkkari toni.tunkkari@finebyte.com //Changed [0-9] to [0-9 ] in day conversion - if (!ereg( "([A-Za-z]{3})[-/\. ]([0-9 ]{1,2})[-/\. ]([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})" + if (!preg_match( "/([A-Za-z]{3})[-/\. ]([0-9 ]{1,2})[-/\. ]([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})/" ,$v, $rr)) return parent::UnixTimeStamp($v); if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; diff --git a/lib/adodb/drivers/adodb-sybase_ase.inc.php b/lib/adodb/drivers/adodb-sybase_ase.inc.php index 5360c2a46c..ca6db057a5 100644 --- a/lib/adodb/drivers/adodb-sybase_ase.inc.php +++ b/lib/adodb/drivers/adodb-sybase_ase.inc.php @@ -1,6 +1,6 @@ */ + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'fa', + DB_ERROR => 'خطای ناشناخته', + DB_ERROR_ALREADY_EXISTS => 'وجود دارد', + DB_ERROR_CANNOT_CREATE => 'امکان create وجود ندارد', + DB_ERROR_CANNOT_DELETE => 'امکان حذف وجود ندارد', + DB_ERROR_CANNOT_DROP => 'امکان drop وجود ندارد', + DB_ERROR_CONSTRAINT => 'نقض شرط', + DB_ERROR_DIVZERO => 'تقسیم بر صفر', + DB_ERROR_INVALID => 'نامعتبر', + DB_ERROR_INVALID_DATE => 'زمان یا تاریخ نامعتبر', + DB_ERROR_INVALID_NUMBER => 'عدد نامعتبر', + DB_ERROR_MISMATCH => 'عدم مطابقت', + DB_ERROR_NODBSELECTED => 'بانک اطلاعاتی انتخاب نشده است', + DB_ERROR_NOSUCHFIELD => 'چنین ستونی وجود ندارد', + DB_ERROR_NOSUCHTABLE => 'چنین جدولی وجود ندارد', + DB_ERROR_NOT_CAPABLE => 'backend بانک اطلاعاتی قادر نیست', + DB_ERROR_NOT_FOUND => 'پیدا نشد', + DB_ERROR_NOT_LOCKED => 'قفل نشده', + DB_ERROR_SYNTAX => 'خطای دستوری', + DB_ERROR_UNSUPPORTED => 'پشتیبانی نمی شود', + DB_ERROR_VALUE_COUNT_ON_ROW => 'شمارش مقادیر روی ردیف', + DB_ERROR_INVALID_DSN => 'DSN نامعتبر', + DB_ERROR_CONNECT_FAILED => 'ارتباط برقرار نشد', + 0 => 'بدون خطا', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'داده ناکافی است', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension پیدا نشد', + DB_ERROR_NOSUCHDB => 'چنین بانک اطلاعاتی وجود ندارد', + DB_ERROR_ACCESS_VIOLATION => 'حق دسترسی ناکافی' +); +?> \ No newline at end of file diff --git a/lib/adodb/perf/perf-db2.inc.php b/lib/adodb/perf/perf-db2.inc.php index 7b0479b556..5cf0fdccf4 100644 --- a/lib/adodb/perf/perf-db2.inc.php +++ b/lib/adodb/perf/perf-db2.inc.php @@ -1,6 +1,6 @@ array('RATIO', + "select round((a.cntr_value*100.0)/b.cntr_value,2) from master.dbo.sysperfinfo a, master.dbo.sysperfinfo b where a.counter_name = 'Buffer cache hit ratio' and b.counter_name='Buffer cache hit ratio base'", + '=WarnCacheRatio'), + 'prepared sql hit ratio' => array('RATIO', + array('dbcc cachestats','Prepared',1,100), + ''), + 'adhoc sql hit ratio' => array('RATIO', + array('dbcc cachestats','Adhoc',1,100), + ''), + 'IO', + 'data reads' => array('IO', + "select cntr_value from master.dbo.sysperfinfo where counter_name = 'Page reads/sec'"), + 'data writes' => array('IO', + "select cntr_value from master.dbo.sysperfinfo where counter_name = 'Page writes/sec'"), + + 'Data Cache', + 'data cache size' => array('DATAC', + "select cntr_value*8192 from master.dbo.sysperfinfo where counter_name = 'Total Pages' and object_name='SQLServer:Buffer Manager'", + '' ), + 'data cache blocksize' => array('DATAC', + "select 8192",'page size'), + 'Connections', + 'current connections' => array('SESS', + '=sp_who', + ''), + 'max connections' => array('SESS', + "SELECT @@MAX_CONNECTIONS", + ''), + + false + ); + + + function perf_mssqlnative(&$conn) + { + if ($conn->dataProvider == 'odbc') { + $this->sql1 = 'sql1'; + //$this->explain = false; + } + $this->conn =& $conn; + } + + function Explain($sql,$partial=false) + { + + $save = $this->conn->LogSQL(false); + if ($partial) { + $sqlq = $this->conn->qstr($sql.'%'); + $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq"); + if ($arr) { + foreach($arr as $row) { + $sql = reset($row); + if (crc32($sql) == $partial) break; + } + } + } + + $s = '

Explain: '.htmlspecialchars($sql).'

'; + $this->conn->Execute("SET SHOWPLAN_ALL ON;"); + $sql = str_replace('?',"''",$sql); + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $rs =& $this->conn->Execute($sql); + //adodb_printr($rs); + $ADODB_FETCH_MODE = $save; + if ($rs) { + $rs->MoveNext(); + $s .= ''; + while (!$rs->EOF) { + $s .= '\n"; ## NOTE CORRUPT tag is intentional!!!! + $rs->MoveNext(); + } + $s .= '
Rows IO CPU     Plan
'.round($rs->fields[8],1).''.round($rs->fields[9],3).''.round($rs->fields[10],3).'
'.htmlspecialchars($rs->fields[0])."
'; + + $rs->NextRecordSet(); + } + + $this->conn->Execute("SET SHOWPLAN_ALL OFF;"); + $this->conn->LogSQL($save); + $s .= $this->Tracer($sql); + return $s; + } + + function Tables() + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + //$this->conn->debug=1; + $s = ''; + $rs1 = $this->conn->Execute("select distinct name from sysobjects where xtype='U'"); + if ($rs1) { + while (!$rs1->EOF) { + $tab = $rs1->fields[0]; + $tabq = $this->conn->qstr($tab); + $rs2 = $this->conn->Execute("sp_spaceused $tabq"); + if ($rs2) { + $s .= ''; + $rs2->Close(); + } + $rs1->MoveNext(); + } + $rs1->Close(); + } + $ADODB_FETCH_MODE = $save; + return $s.'
tablenamesize_in_kindex sizereserved size
'.$tab.''.$rs2->fields[3].''.$rs2->fields[4].''.$rs2->fields[2].'
'; + } + + function sp_who() + { + $arr = $this->conn->GetArray('sp_who'); + return sizeof($arr); + } + + function HealthCheck($cli=false) + { + + $this->conn->Execute('dbcc traceon(3604)'); + $html = adodb_perf::HealthCheck($cli); + $this->conn->Execute('dbcc traceoff(3604)'); + return $html; + } + + +} + +?> \ No newline at end of file diff --git a/lib/adodb/perf/perf-mysql.inc.php b/lib/adodb/perf/perf-mysql.inc.php index ad79da6570..955ce14b4c 100644 --- a/lib/adodb/perf/perf-mysql.inc.php +++ b/lib/adodb/perf/perf-mysql.inc.php @@ -1,6 +1,6 @@ array('RATIO', - '=GetKeyHitRatio', - '=WarnCacheRatio'), - 'InnoDB cache hit ratio' => array('RATIO', - '=GetInnoDBHitRatio', - '=WarnCacheRatio'), - 'data cache hit ratio' => array('HIDE', # only if called - '=FindDBHitRatio', - '=WarnCacheRatio'), - 'sql cache hit ratio' => array('RATIO', - '=GetQHitRatio', - ''), - 'IO', - 'data reads' => array('IO', - '=GetReads', - 'Number of selects (Key_reads is not accurate)'), - 'data writes' => array('IO', - '=GetWrites', - 'Number of inserts/updates/deletes * coef (Key_writes is not accurate)'), - - 'Data Cache', - 'MyISAM data cache size' => array('DATAC', - array("show variables", 'key_buffer_size'), - '' ), - 'BDB data cache size' => array('DATAC', - array("show variables", 'bdb_cache_size'), - '' ), - 'InnoDB data cache size' => array('DATAC', - array("show variables", 'innodb_buffer_pool_size'), - '' ), - 'Memory Usage', - 'read buffer size' => array('CACHE', - array("show variables", 'read_buffer_size'), - '(per session)'), - 'sort buffer size' => array('CACHE', - array("show variables", 'sort_buffer_size'), - 'Size of sort buffer (per session)' ), - 'table cache' => array('CACHE', - array("show variables", 'table_cache'), - 'Number of tables to keep open'), - 'Connections', - 'current connections' => array('SESS', - array('show status','Threads_connected'), - ''), - 'max connections' => array( 'SESS', - array("show variables",'max_connections'), - ''), - - false - ); - - function perf_mysql(&$conn) - { - $this->conn = $conn; - } - - function Explain($sql,$partial=false) - { - - if (strtoupper(substr(trim($sql),0,6)) !== 'SELECT') return '

Unable to EXPLAIN non-select statement

'; - $save = $this->conn->LogSQL(false); - if ($partial) { - $sqlq = $this->conn->qstr($sql.'%'); - $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq"); - if ($arr) { - foreach($arr as $row) { - $sql = reset($row); - if (crc32($sql) == $partial) break; - } - } - } - $sql = str_replace('?',"''",$sql); - - if ($partial) { - $sqlq = $this->conn->qstr($sql.'%'); - $sql = $this->conn->GetOne("select sql1 from adodb_logsql where sql1 like $sqlq"); - } - - $s = '

Explain: '.htmlspecialchars($sql).'

'; - $rs = $this->conn->Execute('EXPLAIN '.$sql); - $s .= rs2html($rs,false,false,false,false); - $this->conn->LogSQL($save); - $s .= $this->Tracer($sql); - return $s; - } - - function Tables() - { - if (!$this->tablesSQL) return false; - - $rs = $this->conn->Execute($this->tablesSQL); - if (!$rs) return false; - - $html = rs2html($rs,false,false,false,false); - return $html; - } - - function GetReads() - { - global $ADODB_FETCH_MODE; - $save = $ADODB_FETCH_MODE; - $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); - - $rs = $this->conn->Execute('show status'); - - if (isset($savem)) $this->conn->SetFetchMode($savem); - $ADODB_FETCH_MODE = $save; - - if (!$rs) return 0; - $val = 0; - while (!$rs->EOF) { - switch($rs->fields[0]) { - case 'Com_select': - $val = $rs->fields[1]; - $rs->Close(); - return $val; - } - $rs->MoveNext(); - } - - $rs->Close(); - - return $val; - } - - function GetWrites() - { - global $ADODB_FETCH_MODE; - $save = $ADODB_FETCH_MODE; - $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); - - $rs = $this->conn->Execute('show status'); - - if (isset($savem)) $this->conn->SetFetchMode($savem); - $ADODB_FETCH_MODE = $save; - - if (!$rs) return 0; - $val = 0.0; - while (!$rs->EOF) { - switch($rs->fields[0]) { - case 'Com_insert': - $val += $rs->fields[1]; break; - case 'Com_delete': - $val += $rs->fields[1]; break; - case 'Com_update': - $val += $rs->fields[1]/2; - $rs->Close(); - return $val; - } - $rs->MoveNext(); - } - - $rs->Close(); - - return $val; - } - - function FindDBHitRatio() - { - // first find out type of table - //$this->conn->debug=1; - - global $ADODB_FETCH_MODE; - $save = $ADODB_FETCH_MODE; - $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); - - $rs = $this->conn->Execute('show table status'); - - if (isset($savem)) $this->conn->SetFetchMode($savem); - $ADODB_FETCH_MODE = $save; - - if (!$rs) return ''; - $type = strtoupper($rs->fields[1]); - $rs->Close(); - switch($type){ - case 'MYISAM': - case 'ISAM': - return $this->DBParameter('MyISAM cache hit ratio').' (MyISAM)'; - case 'INNODB': - return $this->DBParameter('InnoDB cache hit ratio').' (InnoDB)'; - default: - return $type.' not supported'; - } - - } - - function GetQHitRatio() - { - //Total number of queries = Qcache_inserts + Qcache_hits + Qcache_not_cached - $hits = $this->_DBParameter(array("show status","Qcache_hits")); - $total = $this->_DBParameter(array("show status","Qcache_inserts")); - $total += $this->_DBParameter(array("show status","Qcache_not_cached")); - - $total += $hits; - if ($total) return round(($hits*100)/$total,2); - return 0; - } - - /* - Use session variable to store Hit percentage, because MySQL - does not remember last value of SHOW INNODB STATUS hit ratio - - # 1st query to SHOW INNODB STATUS - 0.00 reads/s, 0.00 creates/s, 0.00 writes/s - Buffer pool hit rate 1000 / 1000 - - # 2nd query to SHOW INNODB STATUS - 0.00 reads/s, 0.00 creates/s, 0.00 writes/s - No buffer pool activity since the last printout - */ - function GetInnoDBHitRatio() - { - global $ADODB_FETCH_MODE; - - $save = $ADODB_FETCH_MODE; - $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); - - $rs = $this->conn->Execute('show innodb status'); - - if (isset($savem)) $this->conn->SetFetchMode($savem); - $ADODB_FETCH_MODE = $save; - - if (!$rs || $rs->EOF) return 0; - $stat = $rs->fields[0]; - $rs->Close(); - $at = strpos($stat,'Buffer pool hit rate'); - $stat = substr($stat,$at,200); - if (preg_match('!Buffer pool hit rate\s*([0-9]*) / ([0-9]*)!',$stat,$arr)) { - $val = 100*$arr[1]/$arr[2]; - $_SESSION['INNODB_HIT_PCT'] = $val; - return round($val,2); - } else { - if (isset($_SESSION['INNODB_HIT_PCT'])) return $_SESSION['INNODB_HIT_PCT']; - return 0; - } - } - - function GetKeyHitRatio() - { - $hits = $this->_DBParameter(array("show status","Key_read_requests")); - $reqs = $this->_DBParameter(array("show status","Key_reads")); - if ($reqs == 0) return 0; - - return round(($hits/($reqs+$hits))*100,2); - } - + + var $tablesSQL = 'show table status'; + + 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( + 'Ratios', + 'MyISAM cache hit ratio' => array('RATIO', + '=GetKeyHitRatio', + '=WarnCacheRatio'), + 'InnoDB cache hit ratio' => array('RATIO', + '=GetInnoDBHitRatio', + '=WarnCacheRatio'), + 'data cache hit ratio' => array('HIDE', # only if called + '=FindDBHitRatio', + '=WarnCacheRatio'), + 'sql cache hit ratio' => array('RATIO', + '=GetQHitRatio', + ''), + 'IO', + 'data reads' => array('IO', + '=GetReads', + 'Number of selects (Key_reads is not accurate)'), + 'data writes' => array('IO', + '=GetWrites', + 'Number of inserts/updates/deletes * coef (Key_writes is not accurate)'), + + 'Data Cache', + 'MyISAM data cache size' => array('DATAC', + array("show variables", 'key_buffer_size'), + '' ), + 'BDB data cache size' => array('DATAC', + array("show variables", 'bdb_cache_size'), + '' ), + 'InnoDB data cache size' => array('DATAC', + array("show variables", 'innodb_buffer_pool_size'), + '' ), + 'Memory Usage', + 'read buffer size' => array('CACHE', + array("show variables", 'read_buffer_size'), + '(per session)'), + 'sort buffer size' => array('CACHE', + array("show variables", 'sort_buffer_size'), + 'Size of sort buffer (per session)' ), + 'table cache' => array('CACHE', + array("show variables", 'table_cache'), + 'Number of tables to keep open'), + 'Connections', + 'current connections' => array('SESS', + array('show status','Threads_connected'), + ''), + 'max connections' => array( 'SESS', + array("show variables",'max_connections'), + ''), + + false + ); + + function perf_mysql(&$conn) + { + $this->conn = $conn; + } + + function Explain($sql,$partial=false) + { + + if (strtoupper(substr(trim($sql),0,6)) !== 'SELECT') return '

Unable to EXPLAIN non-select statement

'; + $save = $this->conn->LogSQL(false); + if ($partial) { + $sqlq = $this->conn->qstr($sql.'%'); + $arr = $this->conn->GetArray("select distinct sql1 from adodb_logsql where sql1 like $sqlq"); + if ($arr) { + foreach($arr as $row) { + $sql = reset($row); + if (crc32($sql) == $partial) break; + } + } + } + $sql = str_replace('?',"''",$sql); + + if ($partial) { + $sqlq = $this->conn->qstr($sql.'%'); + $sql = $this->conn->GetOne("select sql1 from adodb_logsql where sql1 like $sqlq"); + } + + $s = '

Explain: '.htmlspecialchars($sql).'

'; + $rs = $this->conn->Execute('EXPLAIN '.$sql); + $s .= rs2html($rs,false,false,false,false); + $this->conn->LogSQL($save); + $s .= $this->Tracer($sql); + return $s; + } + + function Tables() + { + if (!$this->tablesSQL) return false; + + $rs = $this->conn->Execute($this->tablesSQL); + if (!$rs) return false; + + $html = rs2html($rs,false,false,false,false); + return $html; + } + + function GetReads() + { + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + + $rs = $this->conn->Execute('show status'); + + if (isset($savem)) $this->conn->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if (!$rs) return 0; + $val = 0; + while (!$rs->EOF) { + switch($rs->fields[0]) { + case 'Com_select': + $val = $rs->fields[1]; + $rs->Close(); + return $val; + } + $rs->MoveNext(); + } + + $rs->Close(); + + return $val; + } + + function GetWrites() + { + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + + $rs = $this->conn->Execute('show status'); + + if (isset($savem)) $this->conn->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if (!$rs) return 0; + $val = 0.0; + while (!$rs->EOF) { + switch($rs->fields[0]) { + case 'Com_insert': + $val += $rs->fields[1]; break; + case 'Com_delete': + $val += $rs->fields[1]; break; + case 'Com_update': + $val += $rs->fields[1]/2; + $rs->Close(); + return $val; + } + $rs->MoveNext(); + } + + $rs->Close(); + + return $val; + } + + function FindDBHitRatio() + { + // first find out type of table + //$this->conn->debug=1; + + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + + $rs = $this->conn->Execute('show table status'); + + if (isset($savem)) $this->conn->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if (!$rs) return ''; + $type = strtoupper($rs->fields[1]); + $rs->Close(); + switch($type){ + case 'MYISAM': + case 'ISAM': + return $this->DBParameter('MyISAM cache hit ratio').' (MyISAM)'; + case 'INNODB': + return $this->DBParameter('InnoDB cache hit ratio').' (InnoDB)'; + default: + return $type.' not supported'; + } + + } + + function GetQHitRatio() + { + //Total number of queries = Qcache_inserts + Qcache_hits + Qcache_not_cached + $hits = $this->_DBParameter(array("show status","Qcache_hits")); + $total = $this->_DBParameter(array("show status","Qcache_inserts")); + $total += $this->_DBParameter(array("show status","Qcache_not_cached")); + + $total += $hits; + if ($total) return round(($hits*100)/$total,2); + return 0; + } + + /* + Use session variable to store Hit percentage, because MySQL + does not remember last value of SHOW INNODB STATUS hit ratio + + # 1st query to SHOW INNODB STATUS + 0.00 reads/s, 0.00 creates/s, 0.00 writes/s + Buffer pool hit rate 1000 / 1000 + + # 2nd query to SHOW INNODB STATUS + 0.00 reads/s, 0.00 creates/s, 0.00 writes/s + No buffer pool activity since the last printout + */ + function GetInnoDBHitRatio() + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + + $rs = $this->conn->Execute('show innodb status'); + + if (isset($savem)) $this->conn->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if (!$rs || $rs->EOF) return 0; + $stat = $rs->fields[0]; + $rs->Close(); + $at = strpos($stat,'Buffer pool hit rate'); + $stat = substr($stat,$at,200); + if (preg_match('!Buffer pool hit rate\s*([0-9]*) / ([0-9]*)!',$stat,$arr)) { + $val = 100*$arr[1]/$arr[2]; + $_SESSION['INNODB_HIT_PCT'] = $val; + return round($val,2); + } else { + if (isset($_SESSION['INNODB_HIT_PCT'])) return $_SESSION['INNODB_HIT_PCT']; + return 0; + } + return 0; + } + + function GetKeyHitRatio() + { + $hits = $this->_DBParameter(array("show status","Key_read_requests")); + $reqs = $this->_DBParameter(array("show status","Key_reads")); + if ($reqs == 0) return 0; + + return round(($hits/($reqs+$hits))*100,2); + } + // start hack var $optimizeTableLow = 'CHECK TABLE %s FAST QUICK'; var $optimizeTableHigh = 'OPTIMIZE TABLE %s'; @@ -311,4 +312,4 @@ class perf_mysql extends adodb_perf{ } // end hack } -?> +?> \ No newline at end of file diff --git a/lib/adodb/perf/perf-oci8.inc.php b/lib/adodb/perf/perf-oci8.inc.php index 751280516f..1ea47f9a44 100644 --- a/lib/adodb/perf/perf-oci8.inc.php +++ b/lib/adodb/perf/perf-oci8.inc.php @@ -1,6 +1,6 @@ array( 'DATAC', "select a.value/b.value from v\$parameter a, v\$parameter b where a.name = 'db_cache_size' and b.name= 'db_block_size'", @@ -75,8 +79,15 @@ AND b.name = 'sorts (memory)'", 'data cache blocksize' => array('DATAC', "select value from v\$parameter where name='db_block_size'", '' ), + 'Memory Pools', - 'data cache size' => array('DATAC', + 'SGA Max Size' => array( 'DATAC', + "select value from v\$parameter where name = 'sga_max_size'", + 'The sga_max_size is the maximum value to which sga_target can be set.' ), + 'SGA target' => array( 'DATAC', + "select value from v\$parameter where name = 'sga_target'", + 'If sga_target is defined then data cache, shared, java and large pool size can be 0. This is because all these pools are consolidated into one sga_target.' ), + 'data cache size' => array('DATAC', "select value from v\$parameter where name = 'db_cache_size'", 'db_cache_size' ), 'shared pool size' => array('DATAC', @@ -430,7 +441,11 @@ order by if (isset($_GET['sql'])) return $this->_SuspiciousSQL($numsql); $s = ''; + $timer = time(); $s .= $this->_SuspiciousSQL($numsql); + $timer = time() - $timer; + + if ($timer > $this->noShowIxora) return $s; $s .= '

'; $save = $ADODB_CACHE_MODE; @@ -502,7 +517,11 @@ order by } $s = ''; + $timer = time(); $s .= $this->_ExpensiveSQL($numsql); + $timer = time() - $timer; + if ($timer > $this->noShowIxora) return $s; + $s .= '

'; $save = $ADODB_CACHE_MODE; $ADODB_CACHE_MODE = ADODB_FETCH_NUM; @@ -536,11 +555,12 @@ BEGIN LOOP cnt := cnt + 1; DELETE FROM $perf_table WHERE ROWID=rec.rr; - IF cnt = 10000 THEN + IF cnt = 1000 THEN COMMIT; cnt := 0; END IF; END LOOP; + commit; END;"; $ok = $this->conn->Execute($sql); diff --git a/lib/adodb/perf/perf-postgres.inc.php b/lib/adodb/perf/perf-postgres.inc.php index 87436443fd..136b83f979 100644 --- a/lib/adodb/perf/perf-postgres.inc.php +++ b/lib/adodb/perf/perf-postgres.inc.php @@ -1,7 +1,7 @@ FieldTypesArray(); reset($fieldTypes); + $i = 0; while(list(,$o) = each($fieldTypes)) { - if (!$o) $v = ''; - else $v = $o->name; + + $v = ($o) ? $o->name : 'Field'.($i++); if ($escquote) $v = str_replace($quote,$escquotequote,$v); $v = strip_tags(str_replace("\n", $replaceNewLine, str_replace("\r\n",$replaceNewLine,str_replace($sep,$sepreplace,$v)))); $elements[] = $v; diff --git a/lib/adodb/tohtml.inc.php b/lib/adodb/tohtml.inc.php index c0135cbc48..6a4e3ba471 100644 --- a/lib/adodb/tohtml.inc.php +++ b/lib/adodb/tohtml.inc.php @@ -1,13 +1,13 @@ */ - + // specific code for tohtml GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND; @@ -84,14 +84,18 @@ GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND; $type = $typearr[$i]; switch($type) { case 'D': - if (empty($v)) $s .= "   \n"; - else if (!strpos($v,':')) { - $s .= " ".$rs->UserDate($v,"D d, M Y") ." \n"; + if (strpos($v,':') !== false); + else { + if (empty($v)) { + $s .= "   \n"; + } else { + $s .= " ".$rs->UserDate($v,"D d, M Y") ."\n"; + } + break; } - break; case 'T': if (empty($v)) $s .= "   \n"; - else $s .= " ".$rs->UserTimeStamp($v,"D d, M Y, h:i:s") ." \n"; + else $s .= " ".$rs->UserTimeStamp($v,"D d, M Y, H:i:s") ."\n"; break; case 'N': @@ -100,7 +104,9 @@ GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND; else $v = round($v,$ADODB_ROUND); case 'I': - $s .= " ".stripslashes((trim($v))) ." \n"; + $vv = stripslashes((trim($v))); + if (strlen($vv) == 0) $vv .= ' '; + $s .= " ".$vv ."\n"; break; /* -- 2.39.5