3496 lines
131 KiB
PHP
3496 lines
131 KiB
PHP
<?php
|
|
|
|
// vim: set et ts=4 sw=4 fdm=marker:
|
|
|
|
/**
|
|
* DB_Table_Database relational database abstraction class
|
|
*
|
|
* PHP versions 4 and 5
|
|
*
|
|
* LICENSE:
|
|
*
|
|
* Copyright (c) 1997-2007, Paul M. Jones <pmjones@php.net>
|
|
* David C. Morse <morse@php.net>
|
|
* Mark Wiesemann <wiesemann@php.net>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * The names of the authors may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* @category Database
|
|
* @package DB_Table
|
|
* @author David C. Morse <morse@php.net>
|
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
|
* @version CVS: $Id: Database.php,v 1.15 2007/12/13 16:52:14 wiesemann Exp $
|
|
* @link http://pear.php.net/package/DB_Table
|
|
*/
|
|
|
|
// {{{ Error code constants
|
|
|
|
/**
|
|
* Parameter is not a DB/MDB2 object
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_DB_OBJECT', -201);
|
|
|
|
/**
|
|
* Error in addTable, parameter $table_obj is not a DB_Table object
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT', -202);
|
|
|
|
/**
|
|
* Error for table name that does not exist in the database
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_TBL', -203);
|
|
|
|
/**
|
|
* Error for table name parameter that is not a string
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_TBL_NOT_STRING', -204);
|
|
|
|
/**
|
|
* Error in getCol for a non-existent column name
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_COL', -205);
|
|
|
|
/**
|
|
* Error in getForeignCol for a non-existent foreign key column
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL', -206);
|
|
|
|
/**
|
|
* Error for column name that is not a string
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_COL_NOT_STRING', -207);
|
|
|
|
/**
|
|
* Error in addTable for multiple primary keys
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_MULT_PKEY', -208);
|
|
|
|
/**
|
|
* Error in addRef for a non-existent foreign key table
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_FTABLE', -209);
|
|
|
|
/**
|
|
* Error in addRef for non-existence referenced table
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_RTABLE', -210);
|
|
|
|
/**
|
|
* Error in addRef for null referenced key in a table with no primary key
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_PKEY', -211);
|
|
|
|
/**
|
|
* Error in addRef for an invalid foreign key, neither string nor array
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_FKEY', -212);
|
|
|
|
/**
|
|
* Error in addRef for referenced key that is not a string, string foreign key
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING', -213);
|
|
|
|
/**
|
|
* Error in addRef for referenced key that is not an array, array foreign key
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY', -214);
|
|
|
|
/**
|
|
* Error in addRef for wrong number of columns in referenced key
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER', -215);
|
|
|
|
/**
|
|
* Error in addRef for non-existence foreign key (referencing) column
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_FCOL', -216);
|
|
|
|
/**
|
|
* Error in addRef for non-existence referenced column
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_RCOL', -217);
|
|
|
|
/**
|
|
* Error in addRef for referencing and referenced columns of different types
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_REF_TYPE', -218);
|
|
|
|
/**
|
|
* Error in addRef for multiple references from one table to another
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_MULT_REF', -219);
|
|
|
|
/**
|
|
* Error due to invalid ON DELETE action name
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION', -220);
|
|
|
|
/**
|
|
* Error due to invalid ON UPDATE action name
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION', -221);
|
|
|
|
/**
|
|
* Error in addLink due to missing required reference
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_REF_LINK', -222);
|
|
|
|
/**
|
|
* Error in validCol for a column name that does not exist in the datase
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_COL_DB', -223);
|
|
|
|
/**
|
|
* Error in validCol for column name that does not exist in the specified table
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_COL_TBL', -224);
|
|
|
|
/**
|
|
* Error in a buildSQL or select* method for an undefined key of $this->sql
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_SQL_UNDEF', -225);
|
|
|
|
/**
|
|
* Error in a buildSQL or select* method for a key of $this->sql that is
|
|
* not a string
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_SQL_NOT_STRING', -226);
|
|
|
|
/**
|
|
* Error in buildFilter due to invalid match type
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_MATCH_TYPE', -227);
|
|
|
|
/**
|
|
* Error in buildFilter due to invalid key for full match
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_DATA_KEY', -228);
|
|
|
|
/**
|
|
* Error in buildFilter due to invalid key for full match
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_FILT_KEY', -229);
|
|
|
|
/**
|
|
* Error in buildFilter due to invalid key for full match
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_FULL_KEY', -230);
|
|
|
|
/**
|
|
* Error in insert for a failed foreign key constraint
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT', -231);
|
|
|
|
/**
|
|
* Error in delete due to a referentially triggered 'restrict' action
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_RESTRICT_DELETE', -232);
|
|
|
|
/**
|
|
* Error in update due to a referentially triggered 'restrict' action
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE', -233);
|
|
|
|
/**
|
|
* Error in fromXML for table with multiple auto_increment columns
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_XML_MULT_AUTO_INC', -234);
|
|
|
|
/**
|
|
* Error in autoJoin, column and tables parameter both null
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL', -235);
|
|
|
|
/**
|
|
* Error in autoJoin for ambiguous column name
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE', -236);
|
|
|
|
/**
|
|
* Error in autoJoin for non-unique set of join conditions
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_AMBIG_JOIN', -237);
|
|
|
|
/**
|
|
* Error in autoJoin for failed construction of join
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_FAIL_JOIN', -238);
|
|
|
|
/**
|
|
* Error in fromXML for PHP 4 (this function requires PHP 5)
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_PHP_VERSION', -239);
|
|
|
|
/**
|
|
* Error parsing XML string in fromXML
|
|
*/
|
|
define('DB_TABLE_DATABASE_ERR_XML_PARSE', -240);
|
|
|
|
// }}}
|
|
// {{{ Includes
|
|
|
|
/**
|
|
* DB_Table_Base base class
|
|
*/
|
|
require_once 'DB/Table/Base.php';
|
|
|
|
/**
|
|
* DB_Table table abstraction class
|
|
*/
|
|
require_once 'DB/Table.php';
|
|
|
|
/**
|
|
* The PEAR class for errors
|
|
*/
|
|
require_once 'PEAR.php';
|
|
|
|
// }}}
|
|
// {{{ Error messages
|
|
|
|
/**
|
|
* US-English default error messages. If you want to internationalize, you can
|
|
* set the translated messages via $GLOBALS['_DB_TABLE_DATABASE']['error'].
|
|
* You can also use DB_Table_Database::setErrorMessage(). Examples:
|
|
*
|
|
* <code>
|
|
* (1) $GLOBALS['_DB_TABLE_DATABASE']['error'] = array(
|
|
* DB_TABLE_DATABASE_ERR_.. => '...',
|
|
* DB_TABLE_DATABASE_ERR_.. => '...');
|
|
* (2) DB_Table_Database::setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
|
|
* DB_Table_Database::setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
|
|
* (3) DB_Table_Database::setErrorMessage(array(
|
|
* DB_TABLE_DATABASE_ERR_.. => '...');
|
|
* DB_TABLE_DATABASE_ERR_.. => '...');
|
|
* (4) $obj =& new DB_Table();
|
|
* $obj->setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
|
|
* $obj->setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
|
|
* (5) $obj =& new DB_Table();
|
|
* $obj->setErrorMessage(array(DB_TABLE_DATABASE_ERR_.. => '...');
|
|
* DB_TABLE_DATABASE_ERR_.. => '...');
|
|
* </code>
|
|
*
|
|
* For errors that can occur with-in the constructor call (i.e. e.g. creating
|
|
* or altering the database table), only the code from examples (1) to (3)
|
|
* will alter the default error messages early enough. For errors that can
|
|
* occur later, examples (4) and (5) are also valid.
|
|
*/
|
|
$GLOBALS['_DB_TABLE_DATABASE']['default_error'] = array(
|
|
DB_TABLE_DATABASE_ERR_DB_OBJECT =>
|
|
'Invalid DB/MDB2 object parameter. Function',
|
|
DB_TABLE_DATABASE_ERR_NO_TBL =>
|
|
'Table does not exist in database. Method, Table =',
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING =>
|
|
'Table name parameter is not a string in method',
|
|
DB_TABLE_DATABASE_ERR_NO_COL =>
|
|
'In getCol, non-existent column name parameter',
|
|
DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL =>
|
|
'In getForeignCol, non-existent column name parameter',
|
|
DB_TABLE_DATABASE_ERR_COL_NOT_STRING =>
|
|
'Column name parameter is not a string in method',
|
|
DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT =>
|
|
'Parameter of addTable is not a DB_Table object',
|
|
DB_TABLE_DATABASE_ERR_MULT_PKEY =>
|
|
'Multiple primary keys in one table detected in addTable. Table',
|
|
DB_TABLE_DATABASE_ERR_NO_FTABLE =>
|
|
'Foreign key reference from non-existent table in addRef. Reference',
|
|
DB_TABLE_DATABASE_ERR_NO_RTABLE =>
|
|
'Reference to a non-existent referenced table in addRef. Reference',
|
|
DB_TABLE_DATABASE_ERR_NO_PKEY =>
|
|
'Missing primary key of referenced table in addRef. Reference',
|
|
DB_TABLE_DATABASE_ERR_FKEY =>
|
|
'Foreign / referencing key is not a string or array in addRef',
|
|
DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING =>
|
|
'Foreign key is a string, referenced key is not a string in addRef',
|
|
DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY =>
|
|
'Foreign key is an array, referenced key is not an array in addRef',
|
|
DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER =>
|
|
'Wrong number of columns in referencing key in addRef',
|
|
DB_TABLE_DATABASE_ERR_NO_FCOL =>
|
|
'Nonexistent foreign / referencing key column in addRef. Reference',
|
|
DB_TABLE_DATABASE_ERR_NO_RCOL =>
|
|
'Nonexistent referenced key column in addRef. Reference',
|
|
DB_TABLE_DATABASE_ERR_REF_TYPE =>
|
|
'Different referencing and referenced column types in addRef. Reference',
|
|
DB_TABLE_DATABASE_ERR_MULT_REF =>
|
|
'Multiple references between two tables in addRef. Reference',
|
|
DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION =>
|
|
'Invalid ON DELETE action. Reference',
|
|
DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION =>
|
|
'Invalid ON UPDATE action. Reference',
|
|
DB_TABLE_DATABASE_ERR_NO_REF_LINK =>
|
|
'Error in addLink due to missing required reference(s)',
|
|
DB_TABLE_DATABASE_ERR_NO_COL_DB =>
|
|
'In validCol, column name does not exist in database. Column',
|
|
DB_TABLE_DATABASE_ERR_NO_COL_TBL =>
|
|
'In validCol, column does not exist in specified table. Column',
|
|
DB_TABLE_DATABASE_ERR_SQL_UNDEF =>
|
|
'Query string is not a key of $sql property array. Key is',
|
|
DB_TABLE_DATABASE_ERR_SQL_NOT_STRING =>
|
|
'Query is neither an array nor a string',
|
|
DB_TABLE_DATABASE_ERR_MATCH_TYPE =>
|
|
'Invalid match parameter of buildFilter',
|
|
DB_TABLE_DATABASE_ERR_DATA_KEY =>
|
|
'Invalid data_key in buildFilter, neither string nor array',
|
|
DB_TABLE_DATABASE_ERR_FILT_KEY =>
|
|
'Incompatible data_key and filter_key in buildFilter',
|
|
DB_TABLE_DATABASE_ERR_FULL_KEY =>
|
|
'Invalid key value in buildFilter: Mixed null and not null',
|
|
DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT =>
|
|
'Foreign key constraint failure: Key does not reference any rows',
|
|
DB_TABLE_DATABASE_ERR_RESTRICT_DELETE =>
|
|
'Referentially trigger restrict of delete from table',
|
|
DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE =>
|
|
'Referentially trigger restrict of update of table',
|
|
DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL =>
|
|
'No columns or tables provided as parameters to autoJoin',
|
|
DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE =>
|
|
'Ambiguous column name in autoJoin. Column',
|
|
DB_TABLE_DATABASE_ERR_AMBIG_JOIN =>
|
|
'Ambiguous join in autoJoin, during join of table',
|
|
DB_TABLE_DATABASE_ERR_FAIL_JOIN =>
|
|
'Failed join in autoJoin, failed to join table',
|
|
DB_TABLE_DATABASE_ERR_PHP_VERSION =>
|
|
'PHP 5 is required for fromXML method. Interpreter version is',
|
|
DB_TABLE_DATABASE_ERR_XML_PARSE =>
|
|
'Error parsing XML in fromXML method'
|
|
);
|
|
|
|
// merge default and user-defined error messages
|
|
if (!isset($GLOBALS['_DB_TABLE_DATABASE']['error'])) {
|
|
$GLOBALS['_DB_TABLE_DATABASE']['error'] = array();
|
|
}
|
|
foreach ($GLOBALS['_DB_TABLE_DATABASE']['default_error'] as $code => $message) {
|
|
if (!array_key_exists($code, $GLOBALS['_DB_TABLE_DATABASE']['error'])) {
|
|
$GLOBALS['_DB_TABLE_DATABASE']['error'][$code] = $message;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ DB_Table_Database
|
|
|
|
/**
|
|
* Relational database abstraction class
|
|
*
|
|
* DB_Table_Database is an abstraction class for a relational database.
|
|
* It is a layer built on top of DB_Table, in which each table in a
|
|
* database is represented as an instance of DB_Table. It provides:
|
|
*
|
|
* - an object-oriented representation of the database schema
|
|
* - automated construction of SQL commands for simple joins
|
|
* - an API for insert, update, and select commands very similar
|
|
* to that of DB_Table, with optional emulation of standard SQL
|
|
* foreign key integrity checks and referential triggered actions
|
|
* such as cascading deletes.
|
|
* - Serialization and unserialization of the database schema via
|
|
* either php serialization or XML, using the MDB2 XML schema.
|
|
*
|
|
* @category Database
|
|
* @package DB_Table
|
|
* @author David C. Morse <morse@php.net>
|
|
* @version Release: 1.5.6
|
|
* @link http://pear.php.net/package/DB_Table
|
|
*/
|
|
class DB_Table_Database extends DB_Table_Base
|
|
{
|
|
|
|
// {{{ properties
|
|
|
|
/**
|
|
* Name of the database
|
|
*
|
|
* @var string
|
|
* @access public
|
|
*/
|
|
var $name = null;
|
|
|
|
/**
|
|
* Associative array of DB_Table object references. Keys are table names.
|
|
*
|
|
* Associative array in which keys are table names, values are references to
|
|
* DB_Table objects. Each referenced DB_Table object represents one table in
|
|
* the database.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_table = array();
|
|
|
|
/**
|
|
* Array in which keys are table names, values are DB_Table subclass names.
|
|
*
|
|
* See the getTableSubclass() method docblock for further details.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_table_subclass = array();
|
|
|
|
/**
|
|
* Path to directory containing DB_Table subclass declaration files
|
|
*
|
|
* See the setTableSubclassPath() method docblock for further details.
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
var $_table_subclass_path = '';
|
|
|
|
/**
|
|
* Array in which keys are table names, values are primary keys.
|
|
*
|
|
* Each primary key value may be a column name string, a sequential array of
|
|
* column name strings, or null.
|
|
*
|
|
* See the getPrimaryKey() method docblock for details.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_primary_key = array();
|
|
|
|
/**
|
|
* Associative array that maps column names keys to table names.
|
|
*
|
|
* Each key is the name string of a column in the database. Each value
|
|
* is a numerical array containing the names of all tables that contain
|
|
* a column with that name.
|
|
*
|
|
* See the getCol() method docblock for details.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_col = array();
|
|
|
|
/**
|
|
* Associative array that maps names of foreign key columns to table names
|
|
*
|
|
* Each key is the name string of a foreign key column. Each value is a
|
|
* sequential array containing the names of all tables that contain a
|
|
* foreign key column with that name.
|
|
*
|
|
* See the getForeignCol() method docblock for further details.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_foreign_col = array();
|
|
|
|
/**
|
|
* Two-dimensional associative array of foreign key references.
|
|
*
|
|
* Keys are pairs of table names (referencing table first, referenced
|
|
* table second). Each value is an array containing information about
|
|
* the referencing and referenced keys, and about any referentially
|
|
* triggered actions (e.g., cascading delete).
|
|
*
|
|
* See the getRef() docblock for further details.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_ref = array();
|
|
|
|
/**
|
|
* Array in which each key is the names of a referenced tables, each value
|
|
* an sequential array containing names of referencing tables.
|
|
*
|
|
* See the docblock for the getRefTo() method for further discussion.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_ref_to = array();
|
|
|
|
/**
|
|
* Two-dimensional associative array of linking tables.
|
|
*
|
|
* Two-dimensional associative array in which pairs of keys are names
|
|
* of pairs of tables that are linked by one or more linking/association
|
|
* table. Each value is an array containing the names of all table that
|
|
* link the tables specified by the pair of keys. A linking table is a
|
|
* table that creates a many-to-many relationship between two linked
|
|
* tables, via foreign key references from the linking table to the two
|
|
* linked tables. The $_link property is used by the autoJoin() method
|
|
* to join tables that are related only through such a linking table.
|
|
*
|
|
* See the getLink() method docblock for further details.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_link = array();
|
|
|
|
/**
|
|
* Take on_update actions if $_act_on_update is true
|
|
*
|
|
* By default, on_update actions are enabled ($_act_on_update = true)
|
|
*
|
|
* @var boolean
|
|
* @access private
|
|
*/
|
|
var $_act_on_update = true;
|
|
|
|
/**
|
|
* Take on_delete actions if $_act_on_delete is true
|
|
*
|
|
* By default, on_delete actions are enabled ($_act_on_delete = true)
|
|
*
|
|
* @var boolean
|
|
* @access private
|
|
*/
|
|
var $_act_on_delete = true;
|
|
|
|
/**
|
|
* Validate foreign keys before insert or update if $_check_fkey is true
|
|
*
|
|
* By default, validation is disabled ($_check_fkey = false)
|
|
*
|
|
* @var boolean
|
|
* @access private
|
|
*/
|
|
var $_check_fkey = false;
|
|
|
|
/**
|
|
* If the column keys in associative array return sets are fixed case
|
|
* (all upper or lower case) this property should be set true.
|
|
*
|
|
* The column keys in rows of associative array return sets may either
|
|
* preserve capitalization of the column names or they may be fixed case,
|
|
* depending on the options set in the backend (DB/MDB2) and on phptype.
|
|
* If these column names are returned with a fixed case (either upper
|
|
* or lower), $_fix_case must be set true in order for php emulation of
|
|
* ON DELETE and ON UPDATE actions to work correctly. Otherwise, the
|
|
* $_fix_case property should be false (the default).
|
|
*
|
|
* The choice between mixed or fixed case column keys may be made by using
|
|
* using the setFixCase() method, which resets both the behavior of the
|
|
* backend and the $_fix_case property. It may also be changed by using the
|
|
* setOption() method of the DB or MDB2 backend object to directly set the
|
|
* DB_PORTABILITY_LOWERCASE or MDB2_PORTABILITY_FIX_CASE bits of the
|
|
* DB/MDB2 'portability' option.
|
|
*
|
|
* By default, DB returns mixed case and MDB2 returns lower case.
|
|
*
|
|
* @see DB_Table_Database::setFixCase()
|
|
* @see DB::setOption()
|
|
* @see MDB2::setOption()
|
|
*
|
|
* @var boolean
|
|
* @access private
|
|
*/
|
|
var $_fix_case = false;
|
|
|
|
// }}}
|
|
// {{{ Methods
|
|
|
|
// {{{ function DB_Table_Database(&$db, $name)
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* If an error is encountered during instantiation, the error
|
|
* message is stored in the $this->error property of the resulting
|
|
* object. See $error property docblock for a discussion of error
|
|
* handling.
|
|
*
|
|
* @param object &$db DB/MDB2 database connection object
|
|
* @param string $name the database name
|
|
* @return object DB_Table_Database
|
|
* @access public
|
|
*/
|
|
function __construct(&$db, $name)
|
|
{
|
|
// Is $db an DB/MDB2 object or null?
|
|
if (is_a($db, 'db_common')) {
|
|
$this->backend = 'db';
|
|
$this->fetchmode = DB_FETCHMODE_ORDERED;
|
|
} elseif (is_a($db, 'mdb2_driver_common')) {
|
|
$this->backend = 'mdb2';
|
|
$this->fetchmode = MDB2_FETCHMODE_ORDERED;
|
|
} else {
|
|
$code = DB_TABLE_DATABASE_ERR_DB_OBJECT ;
|
|
$text = $GLOBALS['_DB_TABLE_DATABASE']['error'][$code]
|
|
. ' DB_Table_Database';
|
|
$this->error = PEAR::throwError($text, $code);
|
|
return;
|
|
}
|
|
$this->db =& $db;
|
|
$this->name = $name;
|
|
|
|
$this->_primary_subclass = 'DB_TABLE_DATABASE';
|
|
$this->setFixCase(false);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setDBconnection(&$db)
|
|
|
|
/**
|
|
* Set DB/MDB2 connection instance for database and all tables
|
|
*
|
|
* Assign a reference to the DB/MDB2 object $db to $this->db, set
|
|
* $this->backend to 'db' or 'mdb2', and set the same pair of
|
|
* values for the $db and $backend properties of every DB_Table
|
|
* object in the database.
|
|
*
|
|
* @param object &$db DB/MDB2 connection object
|
|
* @return boolean True on success (PEAR_Error on failure)
|
|
*
|
|
* @throws PEAR_Error if
|
|
* $db is not a DB or MDB2 object(DB_TABLE_DATABASE_ERR_DB_OBJECT)
|
|
*
|
|
* @access public
|
|
*/
|
|
function setDBconnection(&$db)
|
|
{
|
|
// Is the first argument a DB/MDB2 object ?
|
|
if (is_subclass_of($db, 'DB_Common')) {
|
|
$backend = 'db';
|
|
} elseif (is_subclass_of($db, 'MDB2_Driver_Common')) {
|
|
$backend = 'mdb2';
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_DB_OBJECT,
|
|
"setDBconnection");
|
|
}
|
|
|
|
// Set db and backend for database and all of its tables
|
|
$this->db =& $db;
|
|
$this->backend = $backend;
|
|
foreach ($this->_table as $name => $table) {
|
|
$table->db =& $db;
|
|
$table->backend = $backend;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setActOnDelete($flag = true)
|
|
|
|
/**
|
|
* Turns on (or off) automatic php emulation of SQL ON DELETE actions
|
|
*
|
|
* @param bool $flag True to enable action, false to disable
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function setActOnDelete($flag = true)
|
|
{
|
|
if ($flag) {
|
|
$this->_act_on_delete = true;
|
|
} else {
|
|
$this->_act_on_delete = false;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setActOnUpdate($flag = true)
|
|
|
|
/**
|
|
* Turns on (or off) automatic php emulation of ON UPDATE actions
|
|
*
|
|
* @param bool $flag True to enable action, false to disable
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function setActOnUpdate($flag = true)
|
|
{
|
|
if ($flag) {
|
|
$this->_act_on_update = true;
|
|
} else {
|
|
$this->_act_on_update = false;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setCheckFKey($flag = true)
|
|
|
|
/**
|
|
* Turns on (or off) validation of foreign key values on insert and update
|
|
*
|
|
* @param bool $flag True to enable foreign key validation, false to disable
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function setCheckFKey($flag = true)
|
|
{
|
|
if ($flag) {
|
|
$this->_check_fkey = true;
|
|
} else {
|
|
$this->_check_fkey = false;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setFixCase($flag = false)
|
|
|
|
/**
|
|
* Sets backend option such that column keys in associative array return
|
|
* sets are converted to fixed case, if true, or mixed case, if false.
|
|
*
|
|
* Sets the DB/MDB2 'portability' option, and sets $this->_fix_case = $flag.
|
|
* Because it sets an option in the underlying DB/MDB2 connection object,
|
|
* this effects the behavior of all objects that share the connection.
|
|
*
|
|
* @param bool $flag True for fixed lower case, false for mixed
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function setFixCase($flag = false)
|
|
{
|
|
$flag = (bool) $flag;
|
|
$option = $this->db->getOption('portability');
|
|
if ($this->backend == 'db') {
|
|
$option = $option | DB_PORTABILITY_LOWERCASE;
|
|
if (!$flag) {
|
|
$option = $option ^ DB_PORTABILITY_LOWERCASE;
|
|
}
|
|
} else {
|
|
$option = $option | MDB2_PORTABILITY_FIX_CASE;
|
|
if (!$flag) {
|
|
$option = $option ^ MDB2_PORTABILITY_FIX_CASE;
|
|
}
|
|
}
|
|
$this->db->setOption('portability', $option);
|
|
$this->_fix_case = $flag;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function &getDBInstance()
|
|
|
|
/**
|
|
* Return reference to $this->db DB/MDB2 object wrapped by $this
|
|
*
|
|
* @return object Reference to DB/MDB2 object
|
|
* @access public
|
|
*/
|
|
function &getDBInstance()
|
|
{
|
|
return $this->db;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getTable($name = null)
|
|
|
|
/**
|
|
* Returns all or part of $_table property array
|
|
*
|
|
* If $name is absent or null, return entire $_table property array.
|
|
* If $name is a table name, return $this->_table[$name] DB_Table object
|
|
* reference
|
|
*
|
|
* The $_table property is an associative array in which keys are table
|
|
* name strings and values are references to DB_Table objects. Each of
|
|
* the referenced objects represents one table in the database.
|
|
*
|
|
* @param string $name Name of table
|
|
* @return mixed $_table property, or one element of $_table
|
|
* (PEAR_Error on failure)
|
|
*
|
|
* @throws PEAR_Error if:
|
|
* - $name is not a string ( DB_TABLE_DATABASE_ERR_TBL_NOT_STRING )
|
|
* - $name is not valid table name ( DB_TABLE_DATABASE_ERR_NO_TBL )
|
|
*
|
|
* @access public
|
|
*/
|
|
function getTable($name = null)
|
|
{
|
|
if (is_null($name)) {
|
|
return $this->_table;
|
|
} elseif (is_string($name)) {
|
|
if (isset($this->_table[$name])) {
|
|
return $this->_table[$name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getTable, $name");
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getTable");
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getPrimaryKey($name = null)
|
|
|
|
/**
|
|
* Returns all or part of the $_primary_key property array
|
|
*
|
|
* If $name is null, return the $this->_primary_key property array
|
|
* If $name is a table name, return $this->_primary_key[$name]
|
|
*
|
|
* The $_primary_key property is an associative array in which each key
|
|
* a table name, and each value is the primary key of that table. Each
|
|
* primary key value may be a column name string, a sequential array of
|
|
* column name strings (for a multi-column key), or null (if no primary
|
|
* key has been declared).
|
|
*
|
|
* @param string $name Name of table
|
|
* @return mixed $this->primary_key array or $this->_primary_key[$name]
|
|
* (PEAR_Error on failure)
|
|
*
|
|
* @throws PEAR_Error if:
|
|
* - $name is not a string ( DB_TABLE_DATABASE_ERR_TBL_NOT_STRING )
|
|
* - $name is not valid table name ( DB_TABLE_DATABASE_ERR_NO_TBL )
|
|
*
|
|
* @access public
|
|
*/
|
|
function getPrimaryKey($name = null)
|
|
{
|
|
if (is_null($name)) {
|
|
return $this->_primary_key;
|
|
} elseif (is_string($name)) {
|
|
if (isset($this->_primary_key[$name])) {
|
|
return $this->_primary_key[$name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getPrimaryKey, $name");
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getPrimaryKey");
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getTableSubclass($name = null)
|
|
|
|
/**
|
|
* Returns all or part of the $_table_subclass property array
|
|
*
|
|
* If $name is null, return the $this->_table_subclass property array
|
|
* If $name is a table name, return $this->_table_subclass[$name]
|
|
*
|
|
* The $_table_subclass property is an associative array in which each key
|
|
* is a table name string, and each value is the name of the corresponding
|
|
* subclass of DB_Table. The value is null if the table is an instance of
|
|
* DB_Table itself.
|
|
*
|
|
* Subclass names are set within the addTable method by applying the
|
|
* built in get_class() function to a DB_Table object. The class names
|
|
* returned by get_class() are stored unmodified. In PHP 4, get_class
|
|
* converts all class names to lower case. In PHP 5, it preserves the
|
|
* capitalization of the name used in the class definition.
|
|
*
|
|
* For autoloading of class definitions to work properly in the
|
|
* __wakeup() method, the base name of each subclass definition
|
|
* file (excluding the .php extension) should thus be a identical
|
|
* to the class name in PHP 5, and a lower case version of the
|
|
* class name in PHP 4 or
|
|
*
|
|
* @param string $name Name of table
|
|
* @return mixed $_table_subclass array or $this->_table_subclass[$name]
|
|
* (PEAR_Error on failure)
|
|
*
|
|
* @throws PEAR_Error if:
|
|
* - $name is not a string ( DB_TABLE_DATABASE_TBL_NOT_STRING )
|
|
* - $name is not valid table name ( DB_TABLE_DATABASE_NO_TBL )
|
|
*
|
|
* @access public
|
|
*
|
|
@ @see DB_Table_Database::__wakeup()
|
|
*/
|
|
function getTableSubclass($name = null)
|
|
{
|
|
if (is_null($name)) {
|
|
return $this->_table_subclass;
|
|
} elseif (is_string($name)) {
|
|
if (isset($this->_table_subclass[$name])) {
|
|
return $this->_table_subclass[$name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getTableSubclass, $name");
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getTableSubclass");
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getCol($column_name = null)
|
|
|
|
/**
|
|
* Returns all or part of the $_col property array
|
|
*
|
|
* If $column_name is null, return $_col property array
|
|
* If $column_name is valid, return $_col[$column_name] subarray
|
|
*
|
|
* The $_col property is an associative array in which each key is the
|
|
* name of a column in the database, and each value is a numerical array
|
|
* containing the names of all tables that contain a column with that
|
|
* name.
|
|
*
|
|
* @param string $column_name a column name string
|
|
* @return mixed $this->_col property array or $this->_col[$column_name]
|
|
* (PEAR_Error on failure)
|
|
*
|
|
* @throws PEAR_Error if:
|
|
* - $column_name is not a string (DB_TABLE_DATABASE_ERR_COL_NOT_STRING)
|
|
* - $column_name is not valid column name (DB_TABLE_DATABASE_NO_COL)
|
|
*
|
|
* @access public
|
|
*/
|
|
function getCol($column_name = null)
|
|
{
|
|
if (is_null($column_name)) {
|
|
return $this->_col;
|
|
} elseif (is_string($column_name)) {
|
|
if (isset($this->_col[$column_name])) {
|
|
return $this->_col[$column_name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_COL,
|
|
"'$column_name'");
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_COL_NOT_STRING,
|
|
'getCol');
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getForeignCol($column_name = null)
|
|
|
|
/**
|
|
* Returns all or part of the $_foreign_col property array
|
|
*
|
|
* If $column_name is null, return $this->_foreign_col property array
|
|
* If $column_name is valid, return $this->_foreign_col[$column_name]
|
|
*
|
|
* The $_foreign_col property is an associative array in which each
|
|
* key is the name string of a foreign key column, and each value is a
|
|
* sequential array containing the names of all tables that contain a
|
|
* foreign key column with that name.
|
|
*
|
|
* If a column $column in a referencing table $ftable is part of the
|
|
* foreign key for references to two or more different referenced tables
|
|
* tables, the name $ftable will also appear multiple times in the array
|
|
* $this->_foreign_col[$column].
|
|
*
|
|
* Returns a PEAR_Error with the following DB_TABLE_DATABASE_* error
|
|
* codes if:
|
|
* - $column_name is not a string ( _COL_NOT_STRING )
|
|
* - $column_name is not valid foreign column name ( _NO_FOREIGN_COL )
|
|
*
|
|
* @param string column name string for foreign key column
|
|
* @return array $_foreign_col property array
|
|
* @access public
|
|
*/
|
|
function getForeignCol($column_name = null)
|
|
{
|
|
if (is_null($column_name)) {
|
|
return $this->_foreign_col;
|
|
} elseif (is_string($column_name)) {
|
|
if (isset($this->_foreign_col[$column_name])) {
|
|
return $this->_foreign_col[$column_name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL,
|
|
$column_name);
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_COL_NOT_STRING,
|
|
'getForeignCol');
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getRef($table1 = null, $table2 = null)
|
|
|
|
/**
|
|
* Returns all or part of the $_ref two-dimensional property array
|
|
*
|
|
* Returns $this->_ref 2D property array if $table1 and $table2 are null.
|
|
* Returns $this->_ref[$table1] subarray if only $table2 is null.
|
|
* Returns $this->_ref[$table1][$table2] if both parameters are present.
|
|
*
|
|
* Returns null if $table1 is a table that references no others, or
|
|
* if $table1 and $table2 are both valid table names, but there is no
|
|
* reference from $table1 to $table2.
|
|
*
|
|
* The $_ref property is a two-dimensional associative array in which
|
|
* the keys are pairs of table names, each value is an array containing
|
|
* information about referenced and referencing keys, and referentially
|
|
* triggered actions (if any). An element of the $_ref array is of the
|
|
* form $ref[$ftable][$rtable] = $reference, where $ftable is the name
|
|
* of a referencing (or foreign key) table and $rtable is the name of
|
|
* a corresponding referenced table. The value $reference is an array
|
|
* $reference = array($fkey, $rkey, $on_delete, $on_update) in which
|
|
* $fkey and $rkey are the foreign (or referencing) and referenced
|
|
* keys, respectively: Foreign key $fkey of table $ftable references
|
|
* key $rkey of table $rtable. The values of $fkey and $rkey must either
|
|
* both be valid column name strings for columns of the same type, or
|
|
* they may both be sequential arrays of column name names, with equal
|
|
* numbers of columns of corresponding types, for multi-column keys. The
|
|
* $on_delete and $on_update values may be either null or string values
|
|
* that indicate actions to be taken upon deletion or updating of a
|
|
* referenced row (e.g., cascading deletes). A null value of $on_delete
|
|
* or $on_update indicates that no referentially triggered action will
|
|
* be taken. See addRef() for further details about allowed values of
|
|
* these action strings.
|
|
*
|
|
* @param string $table1 name of referencing table
|
|
* @param string $table2 name of referenced table
|
|
* @return mixed $ref property array, sub-array, or value
|
|
*
|
|
* @throws a PEAR_Error if:
|
|
* - $table1 or $table2 is not a string (.._DATABASE_ERR_TBL_NOT_STRING)
|
|
* - $table1 or $table2 is not a table name (.._DATABASE_ERR_NO_TBL)
|
|
*
|
|
* @access public
|
|
*/
|
|
function getRef($table1 = null, $table2 = null)
|
|
{
|
|
if (is_null($table1)) {
|
|
return $this->_ref;
|
|
} elseif (is_string($table1)) {
|
|
if (isset($this->_ref[$table1])) {
|
|
if (is_null($table2)) {
|
|
return $this->_ref[$table1];
|
|
} elseif (is_string($table2)) {
|
|
if (isset($this->_ref[$table1][$table2])) {
|
|
return $this->_ref[$table1][$table2];
|
|
} else {
|
|
if (isset($this->_table[$table2])) {
|
|
// Valid table names but no references to
|
|
return null;
|
|
} else {
|
|
// Invalid table name
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getRef, $table2");
|
|
}
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getRef");
|
|
}
|
|
} else {
|
|
if (isset($this->_table[$table1])) {
|
|
// Valid table name, but no references from
|
|
return null;
|
|
} else {
|
|
// Invalid table name
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getRef, $table1");
|
|
}
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getRef");
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getRefTo($table_name = null)
|
|
|
|
/**
|
|
* Returns all or part of the $_ref_to property array
|
|
*
|
|
* Returns $this->_ref_to property array if $table_name is null.
|
|
* Returns $this->_ref_to[$table_name] if $table_name is not null.
|
|
*
|
|
* The $_ref_to property is an associative array in which each key
|
|
* is the name of a referenced table, and each value is a sequential
|
|
* array containing the names of all tables that contain foreign keys
|
|
* that reference that table. Each element is thus of the form
|
|
* $_ref_to[$rtable] = array($ftable1, $ftable2,...), where
|
|
* $ftable1, $ftable2, ... are the names of tables that reference
|
|
* the table named $rtable.
|
|
*
|
|
* @param string $table_name name of table
|
|
* @return mixed $_ref_to property array or subarray
|
|
* (PEAR_Error on failure)
|
|
*
|
|
* @throws PEAR_Error if:
|
|
* - $table_name is not a string ( .._DATABASE_ERR_TBL_NOT_STRING )
|
|
* - $table_name is not a table name ( .._DATABASE_ERR_NO_TBL )
|
|
*
|
|
* @access public
|
|
*/
|
|
function getRefTo($table_name = null)
|
|
{
|
|
if (is_null($table_name)) {
|
|
return $this->_ref_to;
|
|
} elseif (is_string($table_name)) {
|
|
if (isset($this->_ref_to[$table_name])) {
|
|
return $this->_ref_to[$table_name];
|
|
} else {
|
|
if (isset($this->_table[$table_name])) {
|
|
// Valid table name, but no references to
|
|
return null;
|
|
} else {
|
|
// Invalid table name
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getRefTo, $table_name");
|
|
}
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getRefTo");
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function getLink($table1 = null, $table2 = null)
|
|
|
|
/**
|
|
* Returns all or part of the $link two-dimensional property array
|
|
*
|
|
* Returns $this->_link 2D property array if $table1 and $table2 are null.
|
|
* Returns $this->_link[$table1] subarray if only $table2 is null.
|
|
* Returns $this->_link[$table1][$table2] if both parameters are present.
|
|
*
|
|
* Returns null if $table1 is a valid table with links to no others, or
|
|
* if $table1 and $table2 are both valid table names but there is no
|
|
* link between them.
|
|
*
|
|
* The $_link property is a two-dimensional associative array with
|
|
* elements of the form $this->_link[$table1][$table2] = array($link1, ...),
|
|
* in which the value is an array containing the names of all tables
|
|
* that `link' tables named $table1 and $table2, and thereby create a
|
|
* many-to-many relationship between these two tables.
|
|
*
|
|
* The $_link property is used in the autoJoin method to join tables
|
|
* that are related by a many-to-many relationship via a linking table,
|
|
* rather than via a direct foreign key reference. A table that is
|
|
* declared to be linking table for tables $table1 and $table2 must
|
|
* contain foreign keys that reference both of these tables.
|
|
*
|
|
* Each binary link in a database is listed twice in $_link, in
|
|
* $_link[$table1][$table2] and in $_link[$table2][$table1]. If a
|
|
* linking table contains foreign key references to N tables, with
|
|
* N > 2, each of the resulting binary links is listed separately.
|
|
* For example, a table with references to 3 tables A, B, and C can
|
|
* create three binary links (AB, AC, and BC) and six entries in the
|
|
* link property array (i.e., in $_link[A][B], $_link[B][A], ... ).
|
|
*
|
|
* Linking tables may be added to the $_link property by using the
|
|
* addLink method or deleted using the delLink method. Alternatively,
|
|
* all possible linking tables can be identified and added to the
|
|
* $_link array at once by the addAllLinks() method.
|
|
*
|
|
* @param string $table1 name of linked table
|
|
* @param string $table2 name of linked table
|
|
* @return mixed $_link property array, sub-array, or value
|
|
*
|
|
* @throws PEAR_Error:
|
|
* - $table1 or $table2 is not a string (..DATABASE_ERR_TBL_NOT_STRING)
|
|
* - $table1 or $table2 is not a table name (..DATABASE_ERR_NO_TBL)
|
|
*
|
|
* @access public
|
|
*/
|
|
function getLink($table1 = null, $table2 = null)
|
|
{
|
|
if (is_null($table1)) {
|
|
return $this->_link;
|
|
} elseif (is_string($table1)) {
|
|
if (isset($this->_link[$table1])) {
|
|
if (is_null($table2)) {
|
|
return $this->_link[$table1];
|
|
} elseif (is_string($table2)) {
|
|
if (isset($this->_link[$table1][$table2])) {
|
|
return $this->_link[$table1][$table2];
|
|
} else {
|
|
if (isset($this->_table[$table2])) {
|
|
// Valid table names, but no links
|
|
return null;
|
|
} else {
|
|
// Invalid 2nd table name string
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getLink, $table2");
|
|
}
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getLink");
|
|
}
|
|
} else {
|
|
if (isset($this->_table[$table1])) {
|
|
// Valid first table name, but no links
|
|
return null;
|
|
} else {
|
|
// Invalid 1st table name string
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"getLink, $table1");
|
|
}
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
|
|
"getLink");
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setTableSubclassPath($path)
|
|
|
|
/**
|
|
* Sets path to a directory containing DB_Table subclass definitions.
|
|
*
|
|
* This method sets the $_table_subclass_path string property. The value of
|
|
* this property is the path to the directory containing DB_Table subclass
|
|
* definitions, without a trailing directory separator.
|
|
*
|
|
* This path may be used by the __wakeup(), if necessary, in an attempt to
|
|
* autoload class definitions when unserializing a DB_Table_Database object
|
|
* and its child DB_Table objects. If a DB_Table subclass $subclass_name
|
|
* has not been defined when it is needed in DB_Table_Database::__wakeup(),
|
|
* to unserialize an instance of this class, the __wakeup() method attempts
|
|
* to include a class definition file from this directory, as follows:
|
|
* <code>
|
|
* $dir = $this->_table_subclass_path;
|
|
* require_once $dir . '/' . $subclass . '.php';
|
|
* </code>
|
|
* See the getTableSubclass() docblock for a discusion of capitalization
|
|
* conventions in PHP 4 and 5 for subclass file names.
|
|
*
|
|
* @param string $path path to directory containing class definitions
|
|
* @return void
|
|
* @access public
|
|
*
|
|
* @see DB_Table_Database::getTableSubclass()
|
|
*/
|
|
function setTableSubclassPath($path)
|
|
{
|
|
$this->_table_subclass_path = $path;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function addTable(&$table_obj)
|
|
|
|
/**
|
|
* Adds a table to the database.
|
|
*
|
|
* Creates references between $this DB_Table_Database object and
|
|
* the child DB_Table object, by adding a reference to $table_obj
|
|
* to the $this->_table array, and setting $table_obj->database =
|
|
* $this.
|
|
*
|
|
* Adds the primary key to $this->_primary_key array. The relevant
|
|
* element of $this->_primary_key is set to null if no primary key
|
|
* index is declared. Returns an error if more than one primary key
|
|
* is declared.
|
|
*
|
|
* Returns true on success, and PEAR error on failure. Returns the
|
|
* following DB_TABLE_DATABASE_ERR_* error codes if:
|
|
* - $table_obj is not a DB_Table ( _DBTABLE_OBJECT )
|
|
* - more than one primary key is defined ( _ERR_MULT_PKEY )
|
|
*
|
|
* @param object &$table_obj the DB_Table object (reference)
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function addTable(&$table_obj)
|
|
{
|
|
// Check that $table_obj is a DB_Table object
|
|
// Identify subclass name, if any
|
|
if (is_subclass_of($table_obj, 'DB_Table')) {
|
|
$subclass = get_class($table_obj);
|
|
} elseif (is_a($table_obj, 'DB_Table')) {
|
|
$subclass = null;
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT);
|
|
}
|
|
|
|
// Identify table name and table object (sub)class name
|
|
$table = $table_obj->table;
|
|
|
|
// Set $this->_primary_key[$table]
|
|
$this->_primary_key[$table] = null;
|
|
foreach ($table_obj->idx as $idx_name => $idx_def) {
|
|
if ($idx_def['type'] == 'primary') {
|
|
if (is_null($this->_primary_key[$table])) {
|
|
$this->_primary_key[$table] = $idx_def['cols'];
|
|
} else {
|
|
// More than one primary key defined in the table
|
|
unset($this->_primary_key[$table]);
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_MULT_PKEY, $table);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add references between $this parent and child table object
|
|
$this->_table[$table] =& $table_obj;
|
|
$table_obj->setDatabaseInstance($this);
|
|
|
|
// Add subclass name (if any) to $this->_table_subclass
|
|
$this->_table_subclass[$table] = $subclass;
|
|
|
|
// Set shared properties
|
|
$table_obj->db =& $this->db;
|
|
$table_obj->backend = $this->backend;
|
|
$table_obj->fetchmode = $this->fetchmode;
|
|
|
|
// Add all columns to $_col property
|
|
foreach ($table_obj->col as $key => $def) {
|
|
if (!isset($this->_col[$key])) {
|
|
$this->_col[$key] = array();
|
|
}
|
|
$this->_col[$key][] = $table;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function deleteTable($table)
|
|
|
|
/**
|
|
* Deletes a table from $this database object.
|
|
*
|
|
* Removes all dependencies on $table from the database model. The table
|
|
* is removed from $_table and $_primary_key properties. Its columns are
|
|
* removed from the $_col and $_foreign_col properties. References to
|
|
* and from the table are removed from the $_ref, $_ref_to, and $_link
|
|
* properties. Referencing columns are removed from $_foreign_col.
|
|
*
|
|
* @param string $table name of table to be deleted
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function deleteTable($table)
|
|
{
|
|
if (isset($this->_table[$table])) {
|
|
$table_obj =& $this->_table[$table];
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Remove reference to database from table object
|
|
$null_instance = null;
|
|
$table_obj->setDatabaseInstance($null_instance);
|
|
|
|
// Remove columns from $_col and $_foreign_col property arrays
|
|
foreach ($table_obj->col as $column => $def) {
|
|
$key = array_search($table, $this->_col[$column]);
|
|
if (is_integer($key)) {
|
|
unset($this->_col[$column][$key]);
|
|
if (count($this->_col[$column]) == 0) {
|
|
unset($this->_col[$column]);
|
|
} else {
|
|
$new = array_values($this->_col[$column]);
|
|
$this->_col[$column] = $new;
|
|
}
|
|
}
|
|
if (isset($this->_foreign_col[$column])) {
|
|
$key = array_search($table, $this->_foreign_col[$column]);
|
|
if (is_integer($key)) {
|
|
unset($this->_foreign_col[$column][$key]);
|
|
if (count($this->_foreign_col[$column]) == 0) {
|
|
unset($this->_foreign_col[$column]);
|
|
} else {
|
|
$new = array_values($this->_foreign_col[$column]);
|
|
$this->_foreign_col[$column] = $new;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove all references involving the deleted table.
|
|
// Corresponding links are removed from $this->_link by deleteRef
|
|
// Referencing columns are removed from $this->_foreign_col by deleteRef
|
|
foreach ($this->_ref as $ftable => $referenced) {
|
|
foreach ($referenced as $rtable => $ref) {
|
|
if ($ftable == $table || $rtable == $table) {
|
|
$this->deleteRef($ftable, $rtable);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove table from $this->_table and $this->_primary_key
|
|
unset($this->_table[$table]);
|
|
unset($this->_primary_key[$table]);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function addRef($ftable, $fkey, $rtable, [$rkey], [$on_delete], [$on_update])
|
|
|
|
/**
|
|
* Adds a foreign key reference to the database.
|
|
*
|
|
* Adds a reference from foreign key $fkey of table $ftable to
|
|
* referenced key $rkey of table named $rtable to the $this->_ref
|
|
* property. The values of $fkey and $rkey (if not null) may either
|
|
* both be column name strings (for single column keys) or they
|
|
* may both be numerically indexed arrays of corresponding column
|
|
* names (for multi-column keys). If $rkey is null (the default),
|
|
* the referenced key taken to be the primary key of $rtable, if
|
|
* any.
|
|
*
|
|
* The $on_delete and $on_update parameters may be either be null,
|
|
* or may have string values 'restrict', 'cascade', 'set null', or
|
|
* 'set default' that indicate referentially triggered actions to be
|
|
* taken deletion or updating of referenced row in $rtable. Each of
|
|
* these actions corresponds to a standard SQL action (e.g., cascading
|
|
* delete) that may be taken upon referencing rows of table $ftable
|
|
* when a referenced row of $rtable is deleted or updated. A PHP
|
|
* null value for either parameter (the default) signifies that no
|
|
* such action will be taken upon deletion or updating.
|
|
*
|
|
* There may no more than one reference from a table to another, though
|
|
* reference may contain multiple columns.
|
|
*
|
|
* Returns true on success, and PEAR error on failure. Returns the
|
|
* following DB_TABLE_DATABASE_ERR_* error codes if:
|
|
* - $ftable does not exist ( _NO_FTABLE )
|
|
* - $rtable does not exist ( _NO_RTABLE )
|
|
* - $rkey is null and $rtable has no primary key ( _NO_PKEY )
|
|
* - $fkey is neither a string nor an array ( _FKEY )
|
|
* - $rkey is not a string, $fkey is a string ( _RKEY_NOT_STRING )
|
|
* - $rkey is not an array, $fkey is an array ( _RKEY_NOT_ARRAY )
|
|
* - A column of $fkey does not exist ( _NO_FCOL )
|
|
* - A column of $rkey does not exist ( _NO_RCOL )
|
|
* - A column of $fkey and $rkey have different types ( _REF_TYPE )
|
|
* - A reference from $ftable to $rtable already exists ( _MULT_REF )
|
|
*
|
|
* @param string $ftable name of foreign/referencing table
|
|
* @param mixed $fkey foreign key in referencing table
|
|
* @param string $rtable name of referenced table
|
|
* @param mixed $rkey referenced key in referenced table
|
|
* @param string $on_delete action upon delete of a referenced row.
|
|
* @param string $on_update action upon update of a referenced row.
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function addRef($ftable, $fkey, $rtable, $rkey = null,
|
|
$on_delete = null, $on_update = null)
|
|
{
|
|
// Check existence of $ftable is a key in $this->_table.
|
|
if (isset($this->_table[$ftable])) {
|
|
$ftable_obj =& $this->_table[$ftable];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_FTABLE,
|
|
"$ftable => $rtable");
|
|
}
|
|
|
|
// Check existence of referenced table
|
|
if (isset($this->_table[$rtable])) {
|
|
$rtable_obj =& $this->_table[$rtable];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_RTABLE,
|
|
"$ftable => $rtable");
|
|
}
|
|
|
|
// If referenced key is null, set it to the primary key
|
|
if (!$rkey) {
|
|
if (isset($this->_primary_key[$rtable])) {
|
|
$rkey = $this->_primary_key[$rtable];
|
|
} else {
|
|
// Error: null referenced key and no primary key
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_PKEY,
|
|
"$ftable => $rtable");
|
|
}
|
|
}
|
|
|
|
// Check $fkey and $rkey types and compatibility
|
|
if (is_string($fkey)) {
|
|
if (!is_string($rkey)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING,
|
|
"$ftable => $rtable");
|
|
}
|
|
if (!isset($ftable_obj->col[$fkey])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_FCOL,
|
|
"$ftable.$fkey => $rtable.$rkey");
|
|
}
|
|
if (!isset($rtable_obj->col[$rkey])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_RCOL,
|
|
"$ftable.$fkey => $rtable.$rkey");
|
|
}
|
|
$ftype = $ftable_obj->col[$fkey]['type'];
|
|
$rtype = $rtable_obj->col[$rkey]['type'];
|
|
if (!($rtype == $ftype)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_REF_TYPE,
|
|
"$ftable.$fkey => $rtable.$rkey");
|
|
}
|
|
} elseif (is_array($fkey)) {
|
|
if (!is_array($rkey)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY,
|
|
"$ftable => $rtable");
|
|
}
|
|
if (!(count($fkey) == count($rkey))) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER,
|
|
"$ftable => $rtable");
|
|
}
|
|
for ($i=0 ; $i < count($rkey) ; $i++) {
|
|
$fcol = $fkey[$i];
|
|
$rcol = $rkey[$i];
|
|
if (!isset($ftable_obj->col[$fcol])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_FCOL,
|
|
"$ftable.$fcol => $rtable.$rcol");
|
|
}
|
|
if (!isset($rtable_obj->col[$rcol])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_RCOL,
|
|
"$ftable.$fcol => $rtable.$rcol");
|
|
}
|
|
$ftype = $ftable_obj->col[$fcol]['type'];
|
|
$rtype = $rtable_obj->col[$rcol]['type'];
|
|
if (!($rtype == $ftype)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_REF_TYPE,
|
|
"$ftable.$fcol => $rtable.$rcol");
|
|
}
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_FKEY,
|
|
"$ftable => $rtable");
|
|
}
|
|
|
|
// Check validity of on_delete and on_update actions
|
|
$valid_actions =
|
|
array(null, 'cascade', 'set null', 'set default', 'restrict');
|
|
if (!in_array($on_delete, $valid_actions)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION,
|
|
"$ftable => $rtable");
|
|
}
|
|
if (!in_array($on_update, $valid_actions)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION,
|
|
"$ftable => $rtable");
|
|
}
|
|
|
|
// Add reference to $this->_ref;
|
|
$ref = array(
|
|
'fkey' => $fkey,
|
|
'rkey' => $rkey,
|
|
'on_delete' => $on_delete,
|
|
'on_update' => $on_update);
|
|
if (!isset($this->_ref[$ftable])) {
|
|
$this->_ref[$ftable] = array();
|
|
} else {
|
|
if (isset($this->_ref[$ftable][$rtable])) {
|
|
// Multiple references from $ftable to $rtable
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_MULT_REF,
|
|
"$ftable => $rtable");
|
|
}
|
|
}
|
|
$this->_ref[$ftable][$rtable] = $ref;
|
|
|
|
// Add referencing table $ftable to $ref_to property
|
|
if (!isset($this->_ref_to[$rtable])) {
|
|
$this->_ref_to[$rtable] = array();
|
|
}
|
|
$this->_ref_to[$rtable][] = $ftable;
|
|
|
|
// Add foreign key columns to $this->_foreign_col
|
|
if (is_string($fkey)) {
|
|
if (!isset($this->_foreign_col[$fkey])) {
|
|
$this->_foreign_col[$fkey] = array();
|
|
}
|
|
$this->_foreign_col[$fkey][] = $ftable;
|
|
} elseif (is_array($fkey)) {
|
|
foreach ($fkey as $fcol) {
|
|
if (!isset($this->_foreign_col[$fcol])) {
|
|
$this->_foreign_col[$fcol] = array();
|
|
}
|
|
$this->_foreign_col[$fcol][] = $ftable;
|
|
}
|
|
}
|
|
|
|
// Normal completion
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function deleteRef($ftable, $rtable)
|
|
|
|
/**
|
|
* Deletes one reference from database model
|
|
*
|
|
* Removes reference from referencing (foreign key) table named
|
|
* $ftable to referenced table named $rtable. Unsets relevant elements
|
|
* of the $ref, $_ref_to, and $_link property arrays, and removes the
|
|
* foreign key columns of $ftable from the $_foreign_col property.
|
|
*
|
|
* Does nothing, silently, if no such reference exists, i.e., if
|
|
* $this->_ref[$ftable][$rtable] is not set.
|
|
*
|
|
* @param $ftable name of referencing (foreign key) table
|
|
* @param $rtable name of referenced table
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function deleteRef($ftable, $rtable)
|
|
{
|
|
// Delete from $_ref property
|
|
if (isset($this->_ref[$ftable])) {
|
|
if (isset($this->_ref[$ftable][$rtable])) {
|
|
$fkey = $this->_ref[$ftable][$rtable]['fkey'];
|
|
unset($this->_ref[$ftable][$rtable]);
|
|
} else {
|
|
// No such reference, abort silently
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Remove foreign key columns from $foreign_col property
|
|
if (isset($fkey)) {
|
|
if (is_string($fkey)) {
|
|
$fkey = array($fkey);
|
|
}
|
|
foreach ($fkey as $column) {
|
|
if (isset($this->_foreign_col[$column])) {
|
|
$key = array_search($ftable,
|
|
$this->_foreign_col[$column]);
|
|
if (is_integer($key)) {
|
|
unset($this->_foreign_col[$column][$key]);
|
|
if (count($this->_foreign_col[$column]) == 0) {
|
|
unset($this->_foreign_col[$column]);
|
|
} else {
|
|
$new = array_values($this->_foreign_col[$column]);
|
|
$this->_foreign_col[$column] = $new;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete from $_ref_to property
|
|
if (isset($this->_ref_to[$rtable])) {
|
|
$key = array_search($ftable, $this->_ref_to[$rtable]);
|
|
// Unset element
|
|
unset($this->_ref_to[$rtable][$key]);
|
|
if (count($this->_ref_to[$rtable]) == 0) {
|
|
unset($this->_ref_to[$rtable]);
|
|
} else {
|
|
// Redefine numerical keys of remaining elements
|
|
$ref_to = array_values($this->_ref_to[$rtable]);
|
|
$this->_ref_to[$rtable] = $ref_to;
|
|
}
|
|
}
|
|
|
|
// Delete all relevant links from $_link property
|
|
if (isset($this->_link[$rtable])) {
|
|
foreach ($this->_link[$rtable] as $table2 => $links) {
|
|
if (in_array($ftable, $links)) {
|
|
$this->deleteLink($rtable, $table2, $ftable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setOnDelete($ftable, $rtable, $action)
|
|
|
|
/**
|
|
* Modifies the on delete action for one foreign key reference.
|
|
*
|
|
* Modifies the value of the on_delete action associated with a reference
|
|
* from $ftable to $rtable. The parameter action may be one of the action
|
|
* strings 'cascade', 'restrict', 'set null', or 'set default', or it may
|
|
* be php null. A null value of $action indicates that no action should be
|
|
* taken upon deletion of a referenced row.
|
|
*
|
|
* Returns true on success, and PEAR error on failure. Returns the error
|
|
* code DB_TABLE_DATABASE_ERR_REF_TRIG_ACTION if $action is a neither a
|
|
* valid action string nor null. Returns true, and does nothing, if
|
|
* $this->_ref[$ftable][$rtable] is not set.
|
|
*
|
|
* @param string $ftable name of referencing (foreign key) table
|
|
* @param string $rtable name of referenced table
|
|
* @param string $action on delete action (action string or null)
|
|
* @return boolean true on normal completion (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function setOnDelete($ftable, $rtable, $action)
|
|
{
|
|
$valid_actions =
|
|
array(null, 'cascade', 'set null', 'set default', 'restrict');
|
|
|
|
if (isset($this->_ref[$ftable])) {
|
|
if (isset($this->_ref[$ftable][$rtable])) {
|
|
if (!in_array($action, $valid_actions)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_REF_ON_DELETE_ACTION,
|
|
"$ftable => $rtable");
|
|
}
|
|
$this->_ref[$ftable][$rtable]['on_delete'] = $action;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function setOnUpdate($ftable, $rtable, $action)
|
|
|
|
/**
|
|
* Modifies on update action for one foreign key reference.
|
|
*
|
|
* Similar to setOnDelete. See setOnDelete for further details.
|
|
*
|
|
* @param string $ftable name of referencing (foreign key) table
|
|
* @param string $rtable name of referenced table
|
|
* @param array $action on update action (action string or null)
|
|
* @return boolean true on normal completion (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function setOnUpdate($ftable, $rtable, $action)
|
|
{
|
|
$valid_actions =
|
|
array(null, 'cascade', 'set null', 'set default', 'restrict');
|
|
|
|
if (isset($this->_ref[$ftable])) {
|
|
if (isset($this->_ref[$ftable][$rtable])) {
|
|
if (!in_array($action, $valid_actions)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_REF_ON_UPDATE_ACTION,
|
|
"$ftable => $rtable");
|
|
}
|
|
$this->_ref[$ftable][$rtable]['on_update'] = $action;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function addLink($table1, $table2, $link)
|
|
|
|
/**
|
|
* Identifies a linking/association table that links two others
|
|
*
|
|
* Adds table name $link to $this->_link[$table1][$table2] and
|
|
* to $this->_link[$table2][$table1].
|
|
*
|
|
* Returns true on success, and PEAR error on failure. Returns the
|
|
* following DB_TABLE_DATABASE_ERR_* error codes if:
|
|
* - $ftable does not exist ( _NO_FTABLE )
|
|
* - $rtable does not exist ( _NO_RTABLE )
|
|
*
|
|
* @param string $table1 name of 1st linked table
|
|
* @param string $table2 name of 2nd linked table
|
|
* @param string $link name of linking/association table.
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function addLink($table1, $table2, $link)
|
|
{
|
|
|
|
// Check for existence of all three tables
|
|
if (is_string($table1)) {
|
|
if (!isset($this->_table[$table1])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"addLink, $table1");
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"addLink, $table1");
|
|
}
|
|
if (!isset($this->_table[$table2])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"addLink, $table2");
|
|
}
|
|
if (!isset($this->_table[$link])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"addLink, $link");
|
|
}
|
|
if (!isset($this->_ref[$link])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_REF_LINK,
|
|
"$link => $table1, $table2");
|
|
} else {
|
|
if (!isset($this->_ref[$link][$table1])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_REF_LINK,
|
|
"$link => $table1");
|
|
}
|
|
if (!isset($this->_ref[$link][$table2])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_REF_LINK,
|
|
"$link => $table2");
|
|
}
|
|
}
|
|
|
|
// Add $this_link[$table1][$table2]
|
|
if (!key_exists($table1, $this->_link)) {
|
|
$this->_link[$table1] = array();
|
|
}
|
|
if (!key_exists($table2, $this->_link[$table1])) {
|
|
$this->_link[$table1][$table2] = array();
|
|
}
|
|
$this->_link[$table1][$table2][] = $link;
|
|
|
|
// Add $this_link[$table2][$table1]
|
|
if (!key_exists($table2, $this->_link)) {
|
|
$this->_link[$table2] = array();
|
|
}
|
|
if (!key_exists($table1, $this->_link[$table2])) {
|
|
$this->_link[$table2][$table1] = array();
|
|
}
|
|
$this->_link[$table2][$table1][] = $link;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function addAllLink()
|
|
|
|
/**
|
|
* Adds all possible linking tables to the $_link property array
|
|
*
|
|
* Identifies all potential linking tables in the datbase, and adds
|
|
* them all to the $_link property. Table $link is taken to be a
|
|
* link between tables $table1 and $table2 if it contains foreign
|
|
* key references to both $table1 and $table2.
|
|
*
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function addAllLinks()
|
|
{
|
|
foreach ($this->_table as $link => $link_obj) {
|
|
if (isset($this->_ref[$link])) {
|
|
$ref = $this->_ref[$link];
|
|
$n = count($ref);
|
|
$names = array_keys($ref);
|
|
if ($n > 1) {
|
|
$is_link = true;
|
|
} else {
|
|
$is_link = false;
|
|
}
|
|
if ($is_link) {
|
|
if ($n == 2) {
|
|
$table1 = $names[0];
|
|
$table2 = $names[1];
|
|
$this->addLink($table1, $table2, $link);
|
|
} elseif ($n > 2) {
|
|
for ($i=1 ; $i < $n; $i++) {
|
|
for ($j=0 ; $j < $i; $j++) {
|
|
$table1 = $names[$j];
|
|
$table2 = $names[$i];
|
|
$this->addLink($table1, $table2, $link);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function deleteLink($table1, $table2, $link = null)
|
|
|
|
/**
|
|
* Removes a link between two tables from the $_link property
|
|
*
|
|
* If $link is not null, remove table $link from the list of links
|
|
* between $table1 and $table2, if present. If $link is null, delete
|
|
* all links between $table1 and $table2.
|
|
*
|
|
* @param string $table1 name of 1st linked table
|
|
* @param string $table2 name of 2nd linked table
|
|
* @param string $link name of linking table
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function deleteLink($table1, $table2, $link = null)
|
|
{
|
|
if (isset($this->_link[$table1])) {
|
|
if (isset($this->_link[$table1][$table2])) {
|
|
if ($link) {
|
|
// Find numerical key of $link in _link[$table1][$table2]
|
|
$key = array_search($link, $this->_link[$table1][$table2]);
|
|
if (is_integer($key)) {
|
|
unset($this->_link[$table1][$table2][$key]);
|
|
if (count($this->_link[$table1][$table2]) == 0) {
|
|
unset($this->_link[$table1][$table2]);
|
|
unset($this->_link[$table2][$table1]);
|
|
if (count($this->_link[$table1]) == 0) {
|
|
unset($this->_link[$table1]);
|
|
}
|
|
if (count($this->_link[$table2]) == 0) {
|
|
unset($this->_link[$table2]);
|
|
}
|
|
} else {
|
|
// Reset remaining indices sequentially from zero
|
|
$new = array_values($this->_link[$table1][$table2]);
|
|
$this->_link[$table1][$table2] = $new;
|
|
$this->_link[$table2][$table1] = $new;
|
|
}
|
|
}
|
|
} else {
|
|
unset($this->_link[$table1][$table2]);
|
|
unset($this->_link[$table2][$table1]);
|
|
if (count($this->_link[$table1]) == 0) {
|
|
unset($this->_link[$table1]);
|
|
}
|
|
if (count($this->_link[$table2]) == 0) {
|
|
unset($this->_link[$table2]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function validCol($col, $from = null)
|
|
|
|
/**
|
|
* Validates and (if necessary) disambiguates a column name.
|
|
*
|
|
* The parameter $col is a string may be either a column name or
|
|
* a column name qualified by a table name, using the SQL syntax
|
|
* "$table.$column". If $col contains a table name, and is valid,
|
|
* an array($table, $column) is returned. If $col is not qualified
|
|
* by a column name, an array array($table, $column) is returned,
|
|
* in which $table is either the name of one table, or an array
|
|
* containing the names of two or more tables containing a column
|
|
* named $col.
|
|
*
|
|
* The $from parameter, if present, is a numerical array of
|
|
* names of tables with which $col should be associated, if no
|
|
* explicit table name is provided, and if possible. If one
|
|
* or more of the tables in $from contains a column $col, the
|
|
* returned table or set of tables is restricted to those in
|
|
* array $from.
|
|
*
|
|
* If the table name remains ambiguous after testing for tables in
|
|
* the $from set, and $col is not a foreign key in one or more of
|
|
* the remaining tables, the returned table or set of tables is
|
|
* restricted to those in which $col is not a foreign key.
|
|
*
|
|
* Returns a PEAR_Error with the following DB_TABLE_DATABASE_ERR_* error
|
|
* codes if:
|
|
* - column $col does not exist in the database ( _NO_COL_DB )
|
|
* - column $col does not exist in the specified table ( _NO_COL_TBL )
|
|
*
|
|
* @param string $col column name, optionally qualified by a table name
|
|
* @param array $from array of tables from which $col should be chosen,
|
|
* if possible.
|
|
* @return array array($table, $column), or PEAR_Error on failure
|
|
* $column is a string, $table is a string or array
|
|
* @access public
|
|
*/
|
|
function validCol($col, $from = null)
|
|
{
|
|
$col = explode('.',trim($col));
|
|
if (count($col) == 1) {
|
|
// Parameter $col is a column name with no table name
|
|
$column = $col[0];
|
|
// Does $column exist in database ?
|
|
if (!isset($this->_col[$column])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_COL_DB,
|
|
"$column");
|
|
}
|
|
$table = $this->_col[$column];
|
|
// If $table is not unique, try restricting to arrays in $from
|
|
if (count($table) > 1 && $from) {
|
|
$ptable = array_intersect($table, $from);
|
|
if (count($ptable) > 0) {
|
|
$table = array_values($ptable);
|
|
}
|
|
}
|
|
// If count($table)>1, try excluding foreign key columns
|
|
if (count($table) > 1 && isset($this->_foreign_col[$column])) {
|
|
$ptable = array_diff($table, $this->_foreign_col[$column]);
|
|
if (count($ptable) > 0) {
|
|
$table = array_values($ptable);
|
|
}
|
|
}
|
|
// If only one table remains, set $table = table name string
|
|
if (count($table) == 1) {
|
|
$table = $table[0];
|
|
}
|
|
} elseif (count($col) == 2) {
|
|
// parameter $col is qualified by a table name
|
|
$table = $col[0];
|
|
$column = $col[1];
|
|
if (isset($this->_table[$table])) {
|
|
$table_obj =& $this->_table[$table];
|
|
$col_array = $table_obj->col;
|
|
if (!isset($col_array[$column])) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_COL_TBL,
|
|
"$table.$column");
|
|
}
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL, "validCol, $table");
|
|
}
|
|
}
|
|
return array($table, $column);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function createTables($flag = 'safe')
|
|
|
|
/**
|
|
* Creates all the tables in a database in a RDBMS
|
|
*
|
|
* Note: this method creates all the tables in a database, but does
|
|
* NOT create the parent database or set it to the current or default
|
|
* database -- the database must exist before the method is called.
|
|
*
|
|
* If creation of any table fails, the method immediately returns the
|
|
* PEAR error returned by DB_Table::create($flag).
|
|
*
|
|
* @param mixed $flag The automatic database creation mode, which is
|
|
* applied to each table in the database. It can have
|
|
* values:
|
|
* - 'safe' to create a table only if it does not exist
|
|
* - 'drop' to drop and recreate any existing table
|
|
with the same name
|
|
*
|
|
* @return boolean true on sucess (PEAR_Error on failure of any table)
|
|
* @access public
|
|
*
|
|
* @see DB_Table::create()
|
|
*/
|
|
function createTables($flag = 'safe')
|
|
{
|
|
foreach ($this->_table as $name => $table) {
|
|
$result = $table->create($flag);
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function validForeignKeys($table_name, $data)
|
|
|
|
/**
|
|
* Check validity of any foreign key values in associative array $data
|
|
* containing values to be inserted or updated in table $table_name.
|
|
*
|
|
* Returns true if each foreign key in $data matches a row in the
|
|
* referenced table, or if there are no foreign key columns in $data.
|
|
* Returns a PEAR_Error if any foreign key column in associative array
|
|
* $data (which may contain a full or partial row of $table_name), does
|
|
* not match the the value of the referenced column in any row of the
|
|
* referenced table.
|
|
*
|
|
* @param $table_name name of the referencing table containing $data
|
|
* @param @data associative array containing all or part of a row
|
|
* of data of $table_name, with column name keys.
|
|
* @return bool true if all foreign keys are valid, returns PEAR_Error
|
|
* if foreign keys are invalid or if an error is thrown
|
|
* by a required query
|
|
*
|
|
* @throws PEAR error if:
|
|
* - Error thrown by _buildFKeyFilter method (bubbles up)
|
|
* - Error thrown by select method for required query (bubbles up)
|
|
*
|
|
* @access public
|
|
*/
|
|
function validForeignKeys($table_name, $data)
|
|
{
|
|
if (isset($this->_ref[$table_name])) {
|
|
foreach ($this->_ref[$table_name] as $rtable_name => $ref) {
|
|
$fkey = $ref['fkey'];
|
|
$rkey = $ref['rkey'];
|
|
$rtable_obj =& $this->_table[$rtable_name];
|
|
|
|
// Construct select where clause for referenced rows,
|
|
// $filter = '' if $data contains no foreign key columns,
|
|
$filter = $this->_buildFKeyFilter($data, $fkey, $rkey);
|
|
if (PEAR::isError($filter)) {
|
|
return $filter;
|
|
}
|
|
|
|
// If inserted data contain FK columns referenced by rtable,
|
|
// select referenced row of rtable, return error if none is
|
|
// found
|
|
if ($filter) {
|
|
$sql = array('select'=> '*',
|
|
'from' => $rtable_name,
|
|
'where' => $filter);
|
|
$referenced_rows = $this->select($sql);
|
|
// Check for failed query
|
|
if (PEAR::isError($referenced_rows)) {
|
|
return $referenced_rows;
|
|
}
|
|
// Check for failed foreign key constraint
|
|
if (count($referenced_rows) == 0) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function insert($table_name, $data)
|
|
|
|
/**
|
|
* Inserts a single table row
|
|
*
|
|
* Wrapper for insert method of the corresponding DB_Table object.
|
|
*
|
|
* Data will be validated before insertion using validForeignKey(),
|
|
* if foreign key validation in enabled.
|
|
*
|
|
* @param string $table_name Name of table into which to insert data
|
|
* @param array $data Associative array, in which each key is a column
|
|
* name and each value is that column's value.
|
|
* This is the data that will be inserted into
|
|
* the table. Data is checked against the column
|
|
* names and data types for validity.
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function insert($table_name, $data)
|
|
{
|
|
// Dereference table object
|
|
if (isset($this->_table[$table_name])) {
|
|
$table_obj =& $this->_table[$table_name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"insert, $table_name");
|
|
}
|
|
|
|
// Insert into $table_obj
|
|
$result = $table_obj->insert($data);
|
|
|
|
// Return value: true or PEAR_Error
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
} else {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function autoValidInsert($flag = true)
|
|
|
|
/**
|
|
* Turns on or off automatic validation of inserted data for all tables
|
|
*
|
|
* @param bool $flag true to turn on auto-validation, false to turn off.
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function autoValidInsert($flag = true)
|
|
{
|
|
foreach ($this->_table as $table_obj) {
|
|
$table_obj->autoValidInsert($flag);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function update($table_name, $data, $where)
|
|
|
|
/**
|
|
* Updates all row(s) of table that match a custom where clause.
|
|
*
|
|
* Wrapper for insert method of the corresponding DB_Table object.
|
|
*
|
|
* Data will be validated before insertion using validForeignKey(),
|
|
* if foreign key validation in enabled.
|
|
*
|
|
* Implements any required ON UPDATE actions on tables that
|
|
* reference updated columns, if on update actions are enabled.
|
|
*
|
|
* @param string $table_name name of table to update
|
|
* @param array $data associative array in which keys are names of
|
|
* columns to be updated values are new values.
|
|
* @param string $where SQL WHERE clause that limits the set of
|
|
* records to update.
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function update($table_name, $data, $where)
|
|
{
|
|
// Dereference table object
|
|
if (isset($this->_table[$table_name])) {
|
|
$table_obj =& $this->_table[$table_name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"update, $table_name");
|
|
}
|
|
|
|
// Apply update
|
|
$result = $table_obj->update($data, $where);
|
|
|
|
// Return value: true or PEAR_Error
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
} else {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function autoValidUpdate($flag = true)
|
|
|
|
/**
|
|
* Turns on (or off) automatic validation of updated data for all tables.
|
|
*
|
|
* @param bool $flag true to turn on auto-validation, false to turn off
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function autoValidUpdate($flag = true)
|
|
{
|
|
foreach ($this->_table as $table_obj) {
|
|
$table_obj->autoValidUpdate($flag);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function onUpdateAction(&$table_obj, $data, $where)
|
|
|
|
/**
|
|
* Implements any ON UPDATE actions triggered by updating of rows of
|
|
* $table_obj that match logical condition $where.
|
|
*
|
|
* This method is called by the DB_Table::update() method if the table
|
|
* has a parent DB_Table_Database object, and if ON UPDATE actions are
|
|
* enabled in the database object. It is called indirectly by the
|
|
* DB_Table_Database::delete() method, which is simply a wrapper for
|
|
* the DB_Table method.
|
|
*
|
|
* @param object &$table_obj Reference to a DB_Table object
|
|
* @param array $data Data to updated, column name keys, data values
|
|
* @param string $where SQL logical condition for updated rows
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function onUpdateAction(&$table_obj, $data, $where)
|
|
{
|
|
$table_name = $table_obj->table;
|
|
if ($this->_act_on_update and isset($this->_ref_to[$table_name])) {
|
|
$update_rows = null;
|
|
foreach ($this->_ref_to[$table_name] as $ftable_name) {
|
|
$ref = $this->_ref[$ftable_name][$table_name];
|
|
$action = isset($ref['on_update']) ? $ref['on_update'] : null;
|
|
if (is_null($action)) {
|
|
continue;
|
|
}
|
|
$rtable_obj =& $this->_table[$table_name];
|
|
$ftable_obj =& $this->_table[$ftable_name];
|
|
$fkey = $ref['fkey'];
|
|
$rkey = $ref['rkey'];
|
|
|
|
// Check if any column(s) of referenced $rkey are updated
|
|
$rkey_updated = false;
|
|
foreach ($data as $key => $value) {
|
|
if (is_string($rkey)){
|
|
if ($key == $rkey) {
|
|
$rkey_updated = true;
|
|
break;
|
|
}
|
|
} else {
|
|
if (in_array($key, $rkey)) {
|
|
$rkey_updated = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If $rkey is not updated, continue to next referencing table
|
|
if (!$rkey_updated) {
|
|
continue;
|
|
}
|
|
|
|
// Select rows to be updated, if not done previously
|
|
if ($update_rows === null) {
|
|
if ($this->backend == 'mdb2') {
|
|
$fetchmode_assoc = MDB2_FETCHMODE_ASSOC;
|
|
} else {
|
|
$fetchmode_assoc = DB_FETCHMODE_ASSOC;
|
|
}
|
|
$sql = array('select' => '*',
|
|
'from' => $table_name,
|
|
'where' => $where,
|
|
'fetchmode' => $fetchmode_assoc);
|
|
$update_rows = $this->select($sql);
|
|
if (PEAR::isError($update_rows)) {
|
|
return $update_rows;
|
|
}
|
|
}
|
|
|
|
// Construct $fdata array if cascade, set null, or set default
|
|
$fdata = null;
|
|
if ($action == 'cascade') {
|
|
if (is_string($rkey)) {
|
|
if (array_key_exists($rkey, $data)) {
|
|
$fdata = array($fkey => $data[$rkey]);
|
|
}
|
|
} else {
|
|
$fdata = array();
|
|
for ($i=0; $i < count($rkey); $i++) {
|
|
$rcol = $rkey[$i];
|
|
$fcol = $fkey[$i];
|
|
if (array_key_exists($rcol, $data)) {
|
|
$fdata[$fcol] = $data[$rcol];
|
|
}
|
|
}
|
|
if (count($fdata) == 0) {
|
|
$fdata = null;
|
|
}
|
|
}
|
|
} elseif ($action == 'set null' or $action == 'set default') {
|
|
if (is_string($fkey)) {
|
|
if ($action == 'set default') {
|
|
$value = isset($ftable_obj->col[$fkey]['default'])
|
|
? $ftable_obj->col[$fkey]['default'] : null;
|
|
} else {
|
|
$value = null;
|
|
}
|
|
$fdata = array($fkey => $value);
|
|
} else {
|
|
$fdata = array();
|
|
foreach ($fkey as $fcol) {
|
|
if ($action == 'set default') {
|
|
$value = isset($ftable_obj->col[$fcol]['default'])
|
|
? $ftable_obj->col[$fcol]['default'] : null;
|
|
} else {
|
|
$value = null;
|
|
}
|
|
$fdata[$fcol] = $value;
|
|
}
|
|
if (count($fdata) == 0) {
|
|
$fdata = null;
|
|
}
|
|
}
|
|
} elseif ($action == 'restrict') {
|
|
$fdata = true;
|
|
} elseif ($action == null) {
|
|
$fdata = null;
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION,
|
|
"$ftable_name => $table_name");
|
|
}
|
|
|
|
if (!is_null($fdata)) {
|
|
|
|
// Loop over rows to be updated from $table
|
|
foreach ($update_rows as $update_row) {
|
|
|
|
// If necessary, restore case of column names
|
|
if ($this->_fix_case) {
|
|
$cols = array_keys($table_obj->col);
|
|
$update_row = $this->_replaceKeys($update_row, $cols);
|
|
}
|
|
|
|
// Construct filter for rows that reference $update_row
|
|
$filter = $this->_buildFKeyFilter($update_row,
|
|
$rkey, $fkey);
|
|
|
|
// Apply action to foreign/referencing rows
|
|
if ($action == 'restrict') {
|
|
$sql = array('select'=>'*',
|
|
'from' => $ftable_name,
|
|
'where' => $filter);
|
|
$frows = $this->select($sql);
|
|
if (PEAR::isError($frows)) {
|
|
return $frows;
|
|
}
|
|
if (count($frows) > 0) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE,
|
|
$table_name);
|
|
}
|
|
} else {
|
|
// If 'cascade', 'set null', or 'set default',
|
|
// then update the referencing foreign key.
|
|
// Note: Turn off foreign key validity check
|
|
// during update, then restore original value
|
|
$check_fkey = $this->_check_fkey;
|
|
$this->_check_fkey = false;
|
|
$result = $this->update($ftable_name, $fdata,
|
|
$filter);
|
|
$this->_check_fkey = $check_fkey;
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
}
|
|
}
|
|
} // foreach ($update_row)
|
|
} // if (!is_null($fdata))
|
|
|
|
} // foreach loop over referencing tables
|
|
} // end if
|
|
|
|
// Normal completion
|
|
return true;
|
|
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function autoRecast($flag = true)
|
|
|
|
/**
|
|
* Turns on (or off) automatic recasting of insert and update data
|
|
* for all tables
|
|
*
|
|
* @param bool $flag True to automatically recast insert and update
|
|
* data, in all tables, false to not do so.
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function autoRecast($flag = true)
|
|
{
|
|
foreach ($this->_table as $table_obj) {
|
|
$table_obj->autoRecast($flag);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function autoInc($flag = true)
|
|
|
|
/**
|
|
* Turns on (or off) php implementation of auto-incrementing on insertion
|
|
* for all tables
|
|
*
|
|
* @param bool $flag True to turn on auto-incrementing, false to turn off
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function autoInc($flag = true)
|
|
{
|
|
foreach ($this->_table as $table_obj) {
|
|
$table_obj->auto_inc = $flag;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function delete($table_name, $where)
|
|
|
|
/**
|
|
* Deletes all row(s) of table that match a custom where clause.
|
|
*
|
|
* Wrapper for insert method of the corresponding DB_Table object.
|
|
*
|
|
* Implements any required ON DELETE action on tables that reference
|
|
* deleted rows, if on delete actions are enabled.
|
|
*
|
|
* @param string $table_name name of table from which to delete
|
|
* @param string $where SQL WHERE clause that limits the set
|
|
* of records to delete
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function delete($table_name, $where)
|
|
{
|
|
// Dereference table object
|
|
if (isset($this->_table[$table_name])) {
|
|
$table_obj =& $this->_table[$table_name];
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_TBL,
|
|
"delete, $table_name");
|
|
}
|
|
|
|
// Delete from $table_obj
|
|
$result = $table_obj->delete($where);
|
|
|
|
// Return value: true or PEAR_Error
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
} else {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function onDeleteAction(&$table_obj, $where)
|
|
|
|
/**
|
|
* Implements ON DELETE actions triggered by deletion of rows of
|
|
* $table_obj that match logical condition $where.
|
|
*
|
|
* This method is called by the DB_Table::delete() method if the table
|
|
* has a parent DB_Table_Database object, and if ON DELETE actions are
|
|
* enabled in the database object. It is called indirectly by the
|
|
* DB_Table_Database::delete() method, which is simply a wrapper for
|
|
* the DB_Table method.
|
|
*
|
|
* @param object &$table_obj Reference to a DB_Table object
|
|
* @param string $where SQL logical condition for deleted rows
|
|
* @return boolean true on success (PEAR_Error on failure)
|
|
* @access public
|
|
*/
|
|
function onDeleteAction(&$table_obj, $where)
|
|
{
|
|
$table_name = $table_obj->table;
|
|
if ($this->_act_on_delete and isset($this->_ref_to[$table_name])) {
|
|
$delete_rows = null;
|
|
foreach ($this->_ref_to[$table_name] as $ftable_name) {
|
|
$ref = $this->_ref[$ftable_name][$table_name];
|
|
$action = $ref['on_delete'];
|
|
if (is_null($action)) {
|
|
continue;
|
|
}
|
|
$ftable_obj =& $this->_table[$ftable_name];
|
|
$rtable_obj =& $this->_table[$table_name];
|
|
$fkey = $ref['fkey'];
|
|
$rkey = $ref['rkey'];
|
|
|
|
// Select rows to be deleted, if not done previously
|
|
if ($delete_rows === null) {
|
|
if ($this->backend == 'mdb2') {
|
|
$fetchmode_assoc = MDB2_FETCHMODE_ASSOC;
|
|
} else {
|
|
$fetchmode_assoc = DB_FETCHMODE_ASSOC;
|
|
}
|
|
$sql = array('select' => '*',
|
|
'from' => $table_name,
|
|
'where' => $where,
|
|
'fetchmode' => $fetchmode_assoc);
|
|
$delete_rows = $this->select($sql);
|
|
if (PEAR::isError($delete_rows)) {
|
|
return $delete_rows;
|
|
}
|
|
}
|
|
|
|
// If set null or set default, construct update $fdata
|
|
// $fdata contains data for updating referencing rows
|
|
if ($action == 'set null' or $action == 'set default') {
|
|
if (is_string($fkey)) {
|
|
if ($action == 'set default') {
|
|
$value = isset($ftable_obj->col[$fkey]['default'])
|
|
? $ftable_obj->col[$fkey]['default'] : null;
|
|
} else {
|
|
$value = null;
|
|
}
|
|
$fdata = array($fkey => $value);
|
|
} else {
|
|
$fdata = array();
|
|
foreach ($fkey as $fcol) {
|
|
if ($action == 'set default') {
|
|
$value = isset($ftable_obj->col[$fcol]['default'])
|
|
? $ftable_obj->col[$fcol]['default'] : null;
|
|
} else {
|
|
$value = null;
|
|
}
|
|
$fdata[$fcol] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop over rows to be deleted from $table_name
|
|
foreach ($delete_rows as $delete_row) {
|
|
|
|
// If necessary, restore case of $delete_row column names
|
|
if ($this->_fix_case) {
|
|
$cols = array_keys($table_obj->col);
|
|
$delete_row = $this->_replaceKeys($delete_row, $cols);
|
|
}
|
|
|
|
// Construct filter for referencing rows in $ftable_name
|
|
$filter = $this->_buildFKeyFilter($delete_row,
|
|
$rkey, $fkey);
|
|
|
|
// Apply action for one deleted row
|
|
if ($action == 'restrict') {
|
|
// Select for referencing rows throw error if found
|
|
$sql = array('select'=>'*',
|
|
'from' => $ftable_name,
|
|
'where' => $filter);
|
|
$frows = $this->select($sql);
|
|
if (PEAR::isError($frows)) {
|
|
return $frows;
|
|
}
|
|
if (count($frows) > 0) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_RESTRICT_DELETE,
|
|
$table_name);
|
|
}
|
|
} elseif ($action == 'cascade') {
|
|
// Delete referencing rows
|
|
// Note: Recursion on delete
|
|
$result = $this->delete($ftable_name, $filter);
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
}
|
|
} elseif ($action == 'set null' OR $action == 'set default') {
|
|
// Update referencing rows, using $fdata
|
|
// Note: Turn off foreign key validity check during
|
|
// update of referencing key to null or default, then
|
|
// restore $this->_check_fkey to original value
|
|
$check_fkey = $this->_check_fkey;
|
|
$this->_check_fkey = false;
|
|
$result = $this->update($ftable_name, $fdata, $filter);
|
|
$this->_check_fkey = $check_fkey;
|
|
#$result = $ftable_obj->update($fdata, $filter);
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
}
|
|
} else {
|
|
// Invalid $action name, throw Error
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION,
|
|
"$ftable_name => $table_name");
|
|
}
|
|
} // end foreach ($delete_rows)
|
|
|
|
} // end foreach ($this->_ref_to[...] as $ftable_name)
|
|
} // end if
|
|
|
|
// Normal completion
|
|
return true;
|
|
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function _replaceKeys($data, $keys)
|
|
|
|
/**
|
|
* Returns array in which keys of associative array $data are replaced
|
|
* by values of sequential array $keys.
|
|
*
|
|
* This function is used by the onDeleteAction() and onUpdateAction()
|
|
* methods to restore the case of column names in associative arrays
|
|
* that are returned from an automatically generated query "SELECT *
|
|
* FROM $table WHERE ...", when these column name keys are returned
|
|
* with a fixed case. In this usage, $keys is a sequential array of
|
|
* the names of all columns in $table.
|
|
*
|
|
* @param array $data associative array
|
|
* @param array $key numerical array of replacement key names
|
|
* @return array associative array in which keys of $data have been
|
|
* replaced by the values of array $keys.
|
|
* @access private
|
|
*/
|
|
function _replaceKeys($data, $keys)
|
|
{
|
|
$new_data = array();
|
|
$i = 0;
|
|
foreach ($data as $old_key => $value) {
|
|
$new_key = $keys[$i];
|
|
$new_data[$new_key] = $value;
|
|
$i = $i + 1;
|
|
}
|
|
return $new_data;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function autoJoin($cols = null, $tables = null, $filter = null)
|
|
|
|
/**
|
|
* Builds a select command involving joined tables from
|
|
* a list of column names and/or a list of table names.
|
|
*
|
|
* Returns an query array of the form used in $this->buildSQL,
|
|
* constructed on the basis of a sequential array $cols of
|
|
* column names and/or a sequential array $tables of table
|
|
* names. The 'FROM' clause in the resulting SQL contains
|
|
* all the table listed in the $tables parameter and all
|
|
* those containing the columns listed in the $cols array,
|
|
* as well as any linking tables required to establish
|
|
* many to many relationships between these tables. The
|
|
* 'WHERE' clause is constructed so as to create an inner
|
|
* join of these tables.
|
|
*
|
|
* The $cols parameter is a sequential array in which the
|
|
* values are column names. Column names may be qualified
|
|
* by a table name, using the SQL table.column syntax, but
|
|
* need not be qualified if they are unambiguous. The
|
|
* values in $cols can only be column names, and may not
|
|
* be functions or more complicated SQL expressions. If
|
|
* cols is null, the resulting SQL command will start with
|
|
* 'SELECT * FROM ...' .
|
|
*
|
|
* The $tables parameter is a sequential array in which the
|
|
* values are table names. If $tables is null, the FROM
|
|
* clause is constructed from the tables containing the
|
|
* columns in the $cols.
|
|
*
|
|
* The $params array is an associative array can have
|
|
* 'filter', and 'order' keys, which are both optional.
|
|
* A value $params['filter'] is an condition string to
|
|
* add (i.e., AND) to the automatically constructed set
|
|
* of join conditions. A value $params['order'] is an
|
|
* SQL 'ORDER BY' clause, with no 'ORDER BY' prefix.
|
|
*
|
|
* The function returns an associative array with keys
|
|
* ('select', 'from', 'where', ['order']), for which the
|
|
* associated values are strings containing the SELECT,
|
|
* FROM, WHERE and (optionally) ORDER BY clauses of the
|
|
* select statement. The entire SELECT command string
|
|
* can be obtained by passing the resulting array to
|
|
* the buildSQL method.
|
|
*
|
|
* @param array $cols sequential array of column names
|
|
* @param array $tables sequential array of table names
|
|
* @param array $filter SQL logical expression to be added
|
|
* (ANDed) to the where clause
|
|
* @return array sql query array for select statement
|
|
* @access public
|
|
*/
|
|
function autoJoin($cols = null, $tables = null, $filter = null)
|
|
{
|
|
// initialize array containing clauses of select statement
|
|
$query = array();
|
|
|
|
if (is_null($tables)) {
|
|
if (is_null($cols)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL);
|
|
}
|
|
$tables = array();
|
|
}
|
|
|
|
if (!$cols) {
|
|
// If no columns specified, SELECT * FROM ...
|
|
$query['select'] = '*';
|
|
} else {
|
|
|
|
// Qualify unqualified columns with table names
|
|
$all_tables = $tables;
|
|
foreach ($cols as $key => $col) {
|
|
$col_array = $this->validCol($col, $tables);
|
|
if (PEAR::isError($col_array)) {
|
|
return $col_array;
|
|
}
|
|
$table = $col_array[0];
|
|
$column = $col_array[1];
|
|
if (is_array($table)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE, $col);
|
|
}
|
|
$cols[$key] = "$table.$column";
|
|
if (!in_array($table, $all_tables)) {
|
|
$all_tables[] = $table;
|
|
}
|
|
}
|
|
$tables = $all_tables;
|
|
|
|
// Construct select clause
|
|
$query['select'] = implode(', ', $cols);
|
|
|
|
}
|
|
|
|
// Construct array $joins of join conditions
|
|
$n_tables = count($tables);
|
|
if ($n_tables == 1) {
|
|
$query['from'] = $tables[0];
|
|
} else {
|
|
$join_tables = array($tables[0]); // list of joined tables
|
|
$link_tables = array(); // list of required linking tables
|
|
$joins = array(); // list of join conditions
|
|
// Initialize linked list of unjoined tables
|
|
$next = array();
|
|
for ( $i=1 ; $i < $n_tables-1 ; $i++) {
|
|
$next[$tables[$i]] = $tables[$i+1];
|
|
$prev[$tables[$i+1]] = $tables[$i];
|
|
}
|
|
$next[$tables[$n_tables-1]] = $tables[1];
|
|
$prev[$tables[1]] = $tables[$n_tables-1];
|
|
$n_remain = $n_tables - 1;
|
|
$head = $tables[1];
|
|
$table1 = $tables[1];
|
|
$joined = false;
|
|
$direct = true;
|
|
while ($n_remain > 0) {
|
|
|
|
if ($direct) {
|
|
|
|
// Search for references from table1 to joined tables
|
|
if (isset($this->_ref[$table1])) {
|
|
$list = $this->_ref[$table1];
|
|
foreach ($list as $table2 => $def) {
|
|
if (in_array($table2, $join_tables)) {
|
|
if ($joined) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
|
|
$table1);
|
|
}
|
|
$fkey = $def['fkey'];
|
|
$rkey = $def['rkey'];
|
|
if (is_string($fkey)) {
|
|
$joins[] = "$table1.$fkey = $table2.$rkey";
|
|
} else {
|
|
for ($i=0; $i < count($fkey); $i++ ) {
|
|
$fcol = $fkey[$i];
|
|
$rcol = $rkey[$i];
|
|
$joins[] =
|
|
"$table1.$fcol = $table2.$rcol";
|
|
}
|
|
}
|
|
$joined = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search for references to table1 from joined tables
|
|
if (isset($this->_ref_to[$table1])) {
|
|
$list = $this->_ref_to[$table1];
|
|
foreach ($list as $table2) {
|
|
if (in_array($table2, $join_tables)) {
|
|
if ($joined) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
|
|
$table1);
|
|
}
|
|
$def = $this->_ref[$table2][$table1];
|
|
$fkey = $def['fkey'];
|
|
$rkey = $def['rkey'];
|
|
if (is_string($fkey)) {
|
|
$joins[] = "$table2.$fkey = $table1.$rkey";
|
|
} else {
|
|
for ($i=0; $i < count($fkey); $i++ ) {
|
|
$fcol = $fkey[$i];
|
|
$rcol = $rkey[$i];
|
|
$joins[] =
|
|
"$table2.$fcol = $table1.$rcol";
|
|
}
|
|
}
|
|
$joined = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// Search for indirect linking table to table1
|
|
if (isset($this->_link[$table1])) {
|
|
foreach ($this->_link[$table1] as $table2 => $links) {
|
|
if (in_array($table2, $join_tables)) {
|
|
$n_link = count($links);
|
|
if ($n_link > 1) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
|
|
$table1);
|
|
}
|
|
if ($joined and $n_link > 0) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
|
|
$table1);
|
|
}
|
|
$link = $links[0];
|
|
$def1 = $this->_ref[$link][$table1];
|
|
$fkey1 = $def1['fkey'];
|
|
$rkey1 = $def1['rkey'];
|
|
if (is_string($fkey1)) {
|
|
$joins[] = "$link.$fkey1 = $table1.$rkey1";
|
|
} else {
|
|
for ($i=0; $i < count($fkey1); $i++ ) {
|
|
$fcol1 = $fkey1[$i];
|
|
$rcol1 = $rkey1[$i];
|
|
$joins[] =
|
|
"$link.$fcol1 = $table1.$rcol1";
|
|
}
|
|
}
|
|
$def2 = $this->_ref[$link][$table2];
|
|
$fkey2 = $def2['fkey'];
|
|
$rkey2 = $def2['rkey'];
|
|
if (is_string($fkey2)) {
|
|
$joins[] = "$link.$fkey2 = $table2.$rkey2";
|
|
} else {
|
|
for ($i=0; $i < count($fkey2); $i++ ) {
|
|
$fcol2 = $fkey2[$i];
|
|
$rcol2 = $rkey2[$i];
|
|
$joins[] =
|
|
"$link.$fcol2 = $table2.$rcol2";
|
|
}
|
|
}
|
|
$link_tables[] = $link;
|
|
$joined = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if ($joined) {
|
|
$join_tables[] = $table1;
|
|
$n_remain = $n_remain - 1;
|
|
if ($n_remain > 0) {
|
|
$head = $next[$table1];
|
|
$tail = $prev[$table1];
|
|
$prev[$head] = $tail;
|
|
$next[$tail] = $head;
|
|
$table1 = $head;
|
|
$joined = false;
|
|
$direct = true;
|
|
}
|
|
} else {
|
|
$table1 = $next[$table1];
|
|
if ($table1 == $head) {
|
|
if ($direct) {
|
|
$direct = false;
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_FAIL_JOIN,$table1);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Add any required linking tables to $tables array
|
|
if ($link_tables) {
|
|
foreach ($link_tables as $link) {
|
|
if (!in_array($link, $tables)) {
|
|
$tables[] = $link;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct from clause from $tables array
|
|
$query['from'] = implode(', ', $tables);
|
|
|
|
// Construct where clause from $joins array
|
|
$query['where'] = implode("\n AND ", $joins);
|
|
|
|
}
|
|
|
|
// Add $filter condition, if present
|
|
if ($filter) {
|
|
if (isset($query['where'])) {
|
|
$query['where'] = '( ' . $query['where'] . " )\n" .
|
|
' AND ( ' . $filter . ')';
|
|
} else {
|
|
$query['where'] = $filter;
|
|
}
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function _buildFKeyFilter($data, $data_key = null, $filt_key = null, $match = 'simple')
|
|
|
|
/**
|
|
* Returns WHERE clause equating values of $data array to database column
|
|
* values
|
|
*
|
|
* Usage: The function is designed to return an SQL logical
|
|
* expression that equates the values of a set of foreign key columns in
|
|
* associative array $data, which is a row to be inserted or updated in
|
|
* one table, to the values of the corresponding columns of a referenced
|
|
* table. In this usage, $data_key is the foreign key (a column name or
|
|
* numerical array of column names), and $filt_key is the corresponding
|
|
* referenced key.
|
|
*
|
|
* Parameters: Parameter $data is an associative array containing data to
|
|
* be inserted into or used to update one row of a database table, in which
|
|
* array keys are column names. When present, $data_key contains either
|
|
* the name of a single array key of interest, or a numerical array of such
|
|
* keys. These are usually the names of the columns of a foreign key in
|
|
* that table. When, $data_key is null or absent, it is taken to be equal
|
|
* to an array containing all of the keys of $data. When present, $filt_key
|
|
* contains either a string or a numerical array of strings that are
|
|
* aliases for the keys in $data_key. These are usually the names of the
|
|
* corresponding columns in the referenced table. When $filt_key is null
|
|
* or absent, it is equated with $data_key internally. The function
|
|
* returns an SQL logical expression that equates the values in $data
|
|
* whose keys are specified by $data_key, to the values of database
|
|
* columns whose names are specified in $filt_key.
|
|
*
|
|
* General case: _buildFKeyFilter returns a SQL logical expression that
|
|
* equates the values of $data whose keys are given in $data_key with the
|
|
* values of database columns with names given in $filt_key. For example,
|
|
* if
|
|
* <code>
|
|
* $data = array( 'k1' => $v1, 'k2' => $v2, ... , 'k10' => $v10 );
|
|
* $data_key = array('k2', 'k5', 'k7');
|
|
* $filt_key = array('c2', 'c5', 'c7');
|
|
* </code>
|
|
* then buildFilter($data, $data_key, $filt_key) returns a string
|
|
* <code>
|
|
* "c2 = $v2 AND c5 = $v5 AND c7 = $v7"
|
|
* </code>
|
|
* in which the values $v2, $v5, $v7 are replaced by properly quoted
|
|
* SQL literal values. If, in the above example, $data_key = 'k5'
|
|
* and $filt_key = 'c5', then the function will return
|
|
* <code>
|
|
* "c5 = $v5"
|
|
* </code>
|
|
* where (again) $v5 is replaced by an SQL literal.
|
|
*
|
|
* Simple case: If parameters $data_key and $filt_key are null, the
|
|
* behavior is the same as that of the DB_Table_Base::buildFilter() method.
|
|
* For example, if
|
|
* <code>
|
|
* $data = array( 'c1' => $v1, 'c2' => $v2, 'c3' => $v3)
|
|
* </code>
|
|
* then _buildFKeyFilter($data) returns a string
|
|
* <code>
|
|
* "c1 => $val1 AND c2 => $val2 AND c3 = $v3"
|
|
* </code>
|
|
* in which the values $v1, $v2, $v3 are replaced by SQL literal values,
|
|
* quoted and escaped as appropriate for each data type and the backend.
|
|
*
|
|
* Quoting is done by the DB_Table_Database::quote() method, based on
|
|
* the php type of the values in $array. The treatment of null values
|
|
* in $data depends upon the value of the $match parameter.
|
|
*
|
|
* Null values: The treatment to null values in $data depends upon
|
|
* the value of the $match parameter . If $match == 'simple', an empty
|
|
* string is returned if any $value of $data with a key in $data_key
|
|
* is null. If $match == 'partial', the returned SQL expression
|
|
* equates only the relevant non-null values of $data to the values of
|
|
* corresponding database columns. If $match == 'full', the function
|
|
* returns an empty string if all of the relevant values of data are
|
|
* null, and returns a PEAR_Error if some of the selected values are
|
|
* null and others are not null.
|
|
*
|
|
* @param array $data associative array, keys are column names
|
|
* @param mixed $data_key string or numerical array of strings, in which
|
|
* values are a set of keys of interest in $data
|
|
* @param mixed $data_key string or numerical array of strings, in which
|
|
* values are names of a corresponding set of
|
|
* database column names.
|
|
* @return string SQL expression equating values in $data, for which keys
|
|
* also appear in $data_key, to values of corresponding
|
|
* database columns named in $filt_key.
|
|
* @access private
|
|
*/
|
|
function _buildFKeyFilter($data, $data_key = null, $filt_key = null,
|
|
$match = 'simple')
|
|
{
|
|
// Check $match type value
|
|
if (!in_array($match, array('simple', 'partial', 'full'))) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_MATCH_TYPE);
|
|
}
|
|
|
|
// Simple case: Build filter from $data array alone
|
|
if (is_null($data_key) && is_null($filt_key)) {
|
|
return $this->buildFilter($data, $match);
|
|
}
|
|
|
|
// Defaults for $data_key and $filt_key:
|
|
if (is_null($data_key)) {
|
|
$data_key = array_keys($data);
|
|
}
|
|
if (is_null($filt_key)) {
|
|
$filt_key = $data_key;
|
|
}
|
|
|
|
// General case: $data_key and/or $filt_key not null
|
|
if (is_string($data_key)) {
|
|
if (!is_string($filt_key)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_FILT_KEY);
|
|
}
|
|
if (array_key_exists($data_key, $data)) {
|
|
$value = $data[$data_key];
|
|
if (!is_null($value)) {
|
|
$value = (string) $this->quote($data[$data_key]);
|
|
return "$filt_key = $value";
|
|
} else {
|
|
return '';
|
|
}
|
|
} else {
|
|
return '';
|
|
}
|
|
} elseif (is_array($data_key)) {
|
|
if (!is_array($filt_key)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_FILT_KEY);
|
|
}
|
|
$filter = array();
|
|
for ($i=0; $i < count($data_key); $i++) {
|
|
$data_col = $data_key[$i];
|
|
$filt_col = $filt_key[$i];
|
|
if (array_key_exists($data_col, $data)) {
|
|
$value = $data[$data_col];
|
|
if (!is_null($value)) {
|
|
if ($match == 'full' && isset($found_null)) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_FULL_KEY);
|
|
}
|
|
$value = $this->quote($value);
|
|
$filter[] = "$filt_col = $value";
|
|
} else {
|
|
$found_null = true;
|
|
}
|
|
}
|
|
}
|
|
if ($match == 'simple' && isset($found_null)) {
|
|
return '';
|
|
}
|
|
if (count($filter) == 0) {
|
|
return '';
|
|
}
|
|
return implode(' AND ', $filter);
|
|
} else {
|
|
// Invalid data key
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_DATA_KEY);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function quote($value)
|
|
|
|
/**
|
|
* Returns SQL literal string representation of a php value
|
|
*
|
|
* Calls MDB2::quote() or DB_Common::quoteSmart() to enquote and
|
|
* escape string values. If $value is:
|
|
* - a string, return the string enquoted and escaped
|
|
* - a number, return cast of number to string, without quotes
|
|
* - a boolean, return '1' for true and '0' for false
|
|
* - null, return the string 'NULL'
|
|
*
|
|
* @param mixed $value
|
|
* @return string Representation of value as an SQL literal
|
|
*
|
|
* @access public
|
|
*
|
|
* @see DB_Common::quoteSmart()
|
|
* @see MDB2::quote()
|
|
*/
|
|
function quote($value)
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value ? '1' : '0';
|
|
}
|
|
if ($this->backend == 'mdb2') {
|
|
$value = $this->db->quote($value);
|
|
} else {
|
|
$value = $this->db->quoteSmart($value);
|
|
}
|
|
return (string) $value;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function __sleep()
|
|
|
|
/**
|
|
* Serializes all table references and sets $db = null, $backend = null
|
|
*
|
|
* @return array names of all properties
|
|
* @access public
|
|
*/
|
|
function __sleep()
|
|
{
|
|
$this->db = null;
|
|
$this->backend = null;
|
|
// needed in setDatabaseInstance, where null is passed by reference
|
|
$null_variable = null;
|
|
foreach ($this->_table as $name => $table_obj) {
|
|
$table_obj->db = null;
|
|
$table_obj->setDatabaseInstance($null_variable);
|
|
$this->_table[$name] = serialize($table_obj);
|
|
}
|
|
return array_keys(get_object_vars($this));
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function __wakeup()
|
|
|
|
/**
|
|
* Unserializes DB_Table_Database object and all child DB_Table objects
|
|
*
|
|
* Immediately after unserialization, a DB_Table_Database object
|
|
* has null $db and $backend properties, as do all of its child
|
|
* DB_Table objects. The DB_Table_Database::setDB method should
|
|
* be called immediately after unserialization to re-establish
|
|
* the database connection, like so:
|
|
* <code>
|
|
* $db_object = unserialize($serialized_db);
|
|
* $db_object->setDB($conn);
|
|
* </code>
|
|
* where $conn is a DB/MDB2 object. This establishes a DB/MDB2
|
|
* connection for both the parent database and all child tables.
|
|
*
|
|
* This method unserializes all of the child DB_Table objects of
|
|
* a DB_Table_Database object. It must thus have access to the
|
|
* definitions of the associated DB_Table subclasses. These are
|
|
* listed in the $_table_subclass property. If a required subclass
|
|
* named $subclass is not defined, the __wakeup() method attempts
|
|
* to autoload a file "$subclass.php" in the directory specified
|
|
* by $this->table_subclass_path.
|
|
*
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
function __wakeup()
|
|
{
|
|
foreach ($this->_table as $name => $table_string) {
|
|
|
|
// Check for subclass definition, and autoload if necessary.
|
|
$subclass = $this->_table_subclass[$name];
|
|
if (!is_null($subclass)) {
|
|
if (!class_exists($subclass)) {
|
|
$dir = $this->_table_subclass_path;
|
|
require_once $dir . '/' . $subclass . '.php';
|
|
}
|
|
}
|
|
// Unserialize table
|
|
$table_obj = unserialize($table_string);
|
|
// Reset references between database and table objects
|
|
$table_obj->setDatabaseInstance($this);
|
|
$this->_table[$name] = $table_obj;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function toXML()
|
|
|
|
/**
|
|
* Returns XML string representation of database declaration
|
|
*
|
|
* @param string $indent string of whitespace, prefix to each line
|
|
* @return string XML string representation
|
|
* @access public
|
|
*/
|
|
function toXML($indent = '') {
|
|
require_once 'DB/Table/XML.php';
|
|
$s = array();
|
|
$s[] = DB_Table_XML::openTag('database', $indent);
|
|
foreach ($this->_table as $name => $table_obj) {
|
|
$s[] = $table_obj->toXML($indent);
|
|
}
|
|
$s[] = DB_Table_XML::closeTag('database', $indent);
|
|
return implode("\n", $s);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ function fromXML($xml_string, $conn)
|
|
|
|
/**
|
|
* Returns a DB_Table_Database object constructed from an XML string
|
|
*
|
|
* Uses the MDB2 XML schema for a database element, including a new
|
|
* syntax for foreign key indices.
|
|
*
|
|
* NOTE: This function requires PHP 5. It throws an error if used
|
|
* with PHP 4.
|
|
*
|
|
* @param string XML string representation
|
|
* @return object DB_Table_Database object on success (PEAR_Error on failure)
|
|
*
|
|
* @throws PEAR_Error if:
|
|
* - PHP version is not >= 5.0.0 (...DATABASE_ERR_PHP_VERSION )
|
|
* - Parsing by simpleXML fails (...DATABASE_ERR_XML_PARSE )
|
|
*
|
|
* @access public
|
|
*/
|
|
function fromXML($xml_string, $conn)
|
|
{
|
|
// Check PHP version. Throw error if not >= PHP 5.0.0
|
|
$version = phpversion();
|
|
if (version_compare($version, '5.0.0', "<")) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_PHP_VERSION,
|
|
$version);
|
|
}
|
|
|
|
$xml = simplexml_load_string($xml_string);
|
|
if ($xml == false) {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_XML_PARSE);
|
|
}
|
|
|
|
// Instantiate database object
|
|
$database_name = (string) $xml->name;
|
|
$database_obj = new DB_Table_Database($conn, $database_name);
|
|
|
|
// Create array of foreign key references
|
|
$ref = array();
|
|
|
|
// Loop over tables
|
|
foreach ($xml->table as $table) {
|
|
$table_name = (string) $table->name;
|
|
|
|
// Instantiate table object
|
|
$table_obj = new DB_Table($conn, $table_name);
|
|
|
|
// Add columns to table object
|
|
$declaration = $table->declaration;
|
|
foreach ($declaration->field as $field) {
|
|
$col_name = (string) $field->name;
|
|
$type = (string) $field->type;
|
|
$def = array('type' => $type);
|
|
if (isset($field->length)) {
|
|
$def['size'] = (integer) $field->length;
|
|
}
|
|
if (isset($field->notnull)) {
|
|
if ($field->notnull) {
|
|
$def['require'] = true;
|
|
} else {
|
|
$def['require'] = false;
|
|
}
|
|
}
|
|
if (isset($field->default)) {
|
|
$def['default'] = $field->default;
|
|
}
|
|
if (isset($field->autoincrement)) {
|
|
if (is_null($table_obj->auto_inc_col)) {
|
|
$table_obj->auto_inc_col = $col_name;
|
|
} else {
|
|
return $this->throwError(
|
|
DB_TABLE_DATABASE_ERR_XML_MULT_AUTO_INC);
|
|
}
|
|
}
|
|
$table_obj->col[$col_name] = $def;
|
|
}
|
|
|
|
// Add indices
|
|
foreach ($declaration->index as $index) {
|
|
if (isset($index->name)) {
|
|
$name = (string) $index->name;
|
|
} else {
|
|
$name = null;
|
|
}
|
|
$def = array();
|
|
if (isset($index->primary)) {
|
|
$def['type'] = 'primary';
|
|
} elseif (isset($index->unique)) {
|
|
$def['type'] = 'unique';
|
|
} else {
|
|
$def['type'] = 'normal';
|
|
}
|
|
foreach ($index->field as $field) {
|
|
$def['cols'][] = (string) $field;
|
|
}
|
|
if ($name) {
|
|
$table_obj->idx[$name] = $def;
|
|
} else {
|
|
$table_obj->idx[] = $def;
|
|
}
|
|
}
|
|
|
|
// Add table object to database object
|
|
$database_obj->addTable($table_obj);
|
|
|
|
// Foreign key references
|
|
foreach ($declaration->foreign as $foreign) {
|
|
if (isset($foreign->name)) {
|
|
$name = (string) $foreign->name;
|
|
} else {
|
|
$name = null;
|
|
}
|
|
$fkey = array();
|
|
foreach ($foreign->field as $field) {
|
|
$fkey[] = (string) $field;
|
|
}
|
|
if (count($fkey) == 1) {
|
|
$fkey = $fkey[0];
|
|
}
|
|
$rtable = (string) $foreign->references->table;
|
|
if (isset($foreign->references->field)) {
|
|
$rkey = array();
|
|
foreach ($foreign->references->field as $field) {
|
|
$rkey[] = (string) $field;
|
|
}
|
|
if (count($rkey)==1) {
|
|
$rkey = $rkey[0];
|
|
}
|
|
} else {
|
|
$rkey = null;
|
|
}
|
|
if (isset($foreign->ondelete)) {
|
|
$on_delete = (string) $foreign->ondelete;
|
|
} else {
|
|
$on_delete = null;
|
|
}
|
|
if (isset($foreign->onupdate)) {
|
|
$on_update = (string) $foreign->onupdate;
|
|
} else {
|
|
$on_update = null;
|
|
}
|
|
|
|
// Add reference definition to $ref array
|
|
$def = array();
|
|
$def['fkey'] = $fkey;
|
|
$def['rkey'] = $rkey;
|
|
$def['on_delete'] = $on_delete;
|
|
$def['on_update'] = $on_update;
|
|
if (!isset($ref[$table_name])) {
|
|
$ref[$table_name] = array();
|
|
}
|
|
$ref[$table_name][$rtable] = $def;
|
|
|
|
}
|
|
|
|
// Release variable $table_obj to refer to another table
|
|
unset($table_obj);
|
|
}
|
|
|
|
// Add all references to database object
|
|
foreach ($ref as $ftable => $list) {
|
|
foreach ($list as $rtable => $def) {
|
|
$fkey = $def['fkey'];
|
|
$rkey = $def['rkey'];
|
|
$on_delete = $def['on_delete'];
|
|
$on_update = $def['on_update'];
|
|
$database_obj->addRef($ftable, $fkey, $rtable, $rkey,
|
|
$on_delete, $on_update);
|
|
}
|
|
}
|
|
|
|
return $database_obj;
|
|
}
|
|
|
|
// }}}
|
|
|
|
// }}}
|
|
}
|
|
|
|
// }}}
|
|
|
|
/* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* c-hanging-comment-ender-p: nil
|
|
* End:
|
|
*/
|
|
|
|
?>
|