Files
portal-legacy/lib/symfony/vendor/propel/util/Criteria.php
Chris Sewell cf140a2e97 initial commit
2012-11-28 03:55:08 -05:00

1731 lines
42 KiB
PHP
Executable File

<?php
/*
* $Id: Criteria.php 561 2007-02-01 02:09:52Z hans $
*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information please see
* <http://propel.phpdb.org>.
*/
/**
* This is a utility class for holding criteria information for a query.
*
* BasePeer constructs SQL statements based on the values in this class.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @author Kaspars Jaudzems <kaspars.jaudzems@inbox.lv> (Propel)
* @author Frank Y. Kim <frank.kim@clearink.com> (Torque)
* @author John D. McNally <jmcnally@collab.net> (Torque)
* @author Brett McLaughlin <bmclaugh@algx.net> (Torque)
* @author Eric Dobbs <eric@dobbse.net> (Torque)
* @author Henning P. Schmiedehausen <hps@intermeta.de> (Torque)
* @author Sam Joseph <sam@neurogrid.com> (Torque)
* @version $Revision: 561 $
* @package propel.util
*/
class Criteria implements IteratorAggregate {
/** Comparison type. */
const EQUAL = "=";
/** Comparison type. */
const NOT_EQUAL = "<>";
/** Comparison type. */
const ALT_NOT_EQUAL = "!=";
/** Comparison type. */
const GREATER_THAN = ">";
/** Comparison type. */
const LESS_THAN = "<";
/** Comparison type. */
const GREATER_EQUAL = ">=";
/** Comparison type. */
const LESS_EQUAL = "<=";
/** Comparison type. */
const LIKE = " LIKE ";
/** Comparison type. */
const NOT_LIKE = " NOT LIKE ";
/** PostgreSQL comparison type */
const ILIKE = " ILIKE ";
/** PostgreSQL comparison type */
const NOT_ILIKE = " NOT ILIKE ";
/** Comparison type. */
const CUSTOM = "CUSTOM";
/** Comparison type. */
const DISTINCT = "DISTINCT ";
/** Comparison type. */
const IN = " IN ";
/** Comparison type. */
const NOT_IN = " NOT IN ";
/** Comparison type. */
const ALL = "ALL ";
/** Comparison type. */
const JOIN = "JOIN";
/** Binary math operator: AND */
const BINARY_AND = "&";
/** Binary math operator: OR */
const BINARY_OR = "|";
/** "Order by" qualifier - ascending */
const ASC = "ASC";
/** "Order by" qualifier - descending */
const DESC = "DESC";
/** "IS NULL" null comparison */
const ISNULL = " IS NULL ";
/** "IS NOT NULL" null comparison */
const ISNOTNULL = " IS NOT NULL ";
/** "CURRENT_DATE" ANSI SQL function */
const CURRENT_DATE = "CURRENT_DATE";
/** "CURRENT_TIME" ANSI SQL function */
const CURRENT_TIME = "CURRENT_TIME";
/** "CURRENT_TIMESTAMP" ANSI SQL function */
const CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP";
/** "LEFT JOIN" SQL statement */
const LEFT_JOIN = "LEFT JOIN";
/** "RIGHT JOIN" SQL statement */
const RIGHT_JOIN = "RIGHT JOIN";
/** "INNER JOIN" SQL statement */
const INNER_JOIN = "INNER JOIN";
private $ignoreCase = false;
private $singleRecord = false;
private $selectModifiers = array();
private $selectColumns = array();
private $orderByColumns = array();
private $groupByColumns = array();
private $having = null;
private $asColumns = array();
private $joins = array();
/** The name of the database. */
private $dbName;
/** The name of the database as given in the contructor. */
private $originalDbName;
/**
* To limit the number of rows to return. <code>0</code> means return all
* rows.
*/
private $limit = 0;
/** To start the results at a row other than the first one. */
private $offset = 0;
// flag to note that the criteria involves a blob.
private $blobFlag = null;
private $aliases = array();
private $useTransaction = false;
/**
* Primary storage of criteria data.
* @var array
*/
private $map = array();
/**
* Creates a new instance with the default capacity which corresponds to
* the specified database.
*
* @param dbName The dabase name.
*/
public function __construct($dbName = null)
{
$this->setDbName($dbName);
$this->originalDbName = $dbName;
}
/**
* Implementing SPL IteratorAggregate interface. This allows
* you to foreach() over a Criteria object.
*/
public function getIterator()
{
return new CriterionIterator($this);
}
/**
* Get the criteria map.
* @return array
*/
public function getMap()
{
return $this->map;
}
/**
* Brings this criteria back to its initial state, so that it
* can be reused as if it was new. Except if the criteria has grown in
* capacity, it is left at the current capacity.
* @return void
*/
public function clear()
{
$this->map = array();
$this->ignoreCase = false;
$this->singleRecord = false;
$this->selectModifiers = array();
$this->selectColumns = array();
$this->orderByColumns = array();
$this->groupByColumns = array();
$this->having = null;
$this->asColumns = array();
$this->joins = array();
$this->dbName = $this->originalDbName;
$this->offset = 0;
$this->limit = -1;
$this->blobFlag = null;
$this->aliases = array();
$this->useTransaction = false;
}
/**
* Add an AS clause to the select columns. Usage:
*
* <code>
* Criteria myCrit = new Criteria();
* myCrit->addAsColumn("alias", "ALIAS(".MyPeer::ID.")");
* </code>
*
* @param string $name Wanted Name of the column (alias).
* @param string $clause SQL clause to select from the table
*
* If the name already exists, it is replaced by the new clause.
*
* @return Criteria A modified Criteria object.
*/
public function addAsColumn($name, $clause)
{
$this->asColumns[$name] = $clause;
return $this;
}
/**
* Get the column aliases.
*
* @return array An assoc array which map the column alias names
* to the alias clauses.
*/
public function getAsColumns()
{
return $this->asColumns;
}
/**
* Returns the column name associated with an alias (AS-column).
*
* @param string $alias
* @return string $string
*/
public function getColumnForAs($as)
{
if (isset($this->asColumns[$as])) {
return $this->asColumns[$as];
}
}
/**
* Allows one to specify an alias for a table that can
* be used in various parts of the SQL.
*
* @param string $alias
* @param string $table
* @return void
*/
public function addAlias($alias, $table)
{
$this->aliases[$alias] = $table;
}
/**
* Returns the table name associated with an alias.
*
* @param string $alias
* @return string $string
*/
public function getTableForAlias($alias)
{
if (isset($this->aliases[$alias])) {
return $this->aliases[$alias];
}
}
/**
* Get the keys for the criteria map.
* @return array
*/
public function keys()
{
return array_keys($this->map);
}
/**
* Does this Criteria object contain the specified key?
*
* @param string $column [table.]column
* @return boolean True if this Criteria object contain the specified key.
*/
public function containsKey($column)
{
// must use array_key_exists() because the key could
// exist but have a NULL value (that'd be valid).
return array_key_exists($column, $this->map);
}
/**
* Will force the sql represented by this criteria to be executed within
* a transaction. This is here primarily to support the oid type in
* postgresql. Though it can be used to require any single sql statement
* to use a transaction.
* @return void
*/
public function setUseTransaction($v)
{
$this->useTransaction = (boolean) $v;
}
/**
* called by BasePeer to determine whether the sql command specified by
* this criteria must be wrapped in a transaction.
*
* @return a <code>boolean</code> value
*/
public function isUseTransaction()
{
return $this->useTransaction;
}
/**
* Method to return criteria related to columns in a table.
*
* @param string $column Column name.
* @return A Criterion or null if $column is invalid.
*/
public function getCriterion($column)
{
if (isset($this->map[$column])) {
return $this->map[$column];
}
}
/**
* Method to return criterion that is not added automatically
* to this Criteria. This can be used to chain the
* Criterions to form a more complex where clause.
*
* @param column String full name of column (for example TABLE.COLUMN).
* @param mixed $value
* @param string $comparison
* @return A Criterion.
*/
public function getNewCriterion($column, $value, $comparison = null)
{
return new Criterion($this, $column, $value, $comparison);
}
/**
* Method to return a String table name.
*
* @param name A String with the name of the key.
* @return A String with the value of the object at key.
*/
public function getColumnName($name)
{
if ( isset ( $this->map[$name] ) ) {
return $this->map[$name]->getColumn();
}
return null;
}
/**
* Shortcut method to get an array of columns indexed by table.
* @return array array(table => array(table.column1, table.column2))
*/
public function getTablesColumns()
{
$tables = array();
foreach ( array_keys ( $this->map ) as $key) {
$t = substr ( $key, 0, strpos ( $key, '.' ) );
if ( ! isset ( $tables[$t] ) ) {
$tables[$t] = array( $key );
} else {
$tables[$t][] = $key;
}
}
return $tables;
}
/**
* Method to return a comparison String.
*
* @param string $key String name of the key.
* @return string A String with the value of the object at key.
*/
public function getComparison($key)
{
if ( isset ( $this->map[$key] ) ) {
return $this->map[$key]->getComparison();
}
return null;
}
/**
* Get the Database(Map) name.
*
* @return string A String with the Database(Map) name.
*/
public function getDbName()
{
return $this->dbName;
}
/**
* Set the DatabaseMap name. If <code>null</code> is supplied, uses value
* provided by <code>Propel::getDefaultDB()</code>.
*
* @param $dbName A String with the Database(Map) name.
* @return void
*/
public function setDbName($dbName = null)
{
$this->dbName = ($dbName === null ? Propel::getDefaultDB() : $dbName);
}
/**
* Method to return a String table name.
*
* @param $name A String with the name of the key.
* @return string A String with the value of table for criterion at key.
*/
public function getTableName($name)
{
if ( isset ( $this->map[$name] ) ) {
return $this->map[$name]->getTable();
}
return null;
}
/**
* Method to return the value that was added to Criteria.
*
* @param string $name A String with the name of the key.
* @return mixed The value of object at key.
*/
public function getValue($name)
{
if ( isset ( $this->map[$name] ) ) {
return $this->map[$name]->getValue();
}
return null;
}
/**
* An alias to getValue() -- exposing a Hashtable-like interface.
*
* @param string $key An Object.
* @return mixed The value within the Criterion (not the Criterion object).
*/
public function get($key)
{
return $this->getValue($key);
}
/**
* Overrides Hashtable put, so that this object is returned
* instead of the value previously in the Criteria object.
* The reason is so that it more closely matches the behavior
* of the add() methods. If you want to get the previous value
* then you should first Criteria.get() it yourself. Note, if
* you attempt to pass in an Object that is not a String, it will
* throw a NPE. The reason for this is that none of the add()
* methods support adding anything other than a String as a key.
*
* @param string $key
* @param mixed $value
* @return Instance of self.
*/
public function put($key, $value)
{
return $this->add($key, $value);
}
/**
* Copies all of the mappings from the specified Map to this Criteria
* These mappings will replace any mappings that this Criteria had for any
* of the keys currently in the specified Map.
*
* if the map was another Criteria, its attributes are copied to this
* Criteria, overwriting previous settings.
*
* @param mixed $t Mappings to be stored in this map.
*/
public function putAll($t)
{
if (is_array($t)) {
foreach ($t as $key=>$value) {
if ($value instanceof Criterion) {
$this->map[$key] = $value;
} else {
$this->put($key, $value);
}
}
} elseif ($t instanceof Criteria) {
$this->joins = $t->joins;
}
}
/**
* This method adds a new criterion to the list of criterias.
* If a criterion for the requested column already exists, it is
* replaced. If is used as follow:
*
* <p>
* <code>
* $crit = new Criteria();
* $crit->add(&quot;column&quot;,
* &quot;value&quot;
* &quot;Criteria::GREATER_THAN&quot;);
* </code>
*
* Any comparison can be used.
*
* The name of the table must be used implicitly in the column name,
* so the Column name must be something like 'TABLE.id'. If you
* don't like this, you can use the add(table, column, value) method.
*
* @param string $critOrColumn The column to run the comparison on, or Criterion object.
* @param mixed $value
* @param string $comparison A String.
*
* @return A modified Criteria object.
*/
public function add($p1, $value = null, $comparison = null)
{
if ($p1 instanceof Criterion) {
$c = $p1;
$this->map[$c->getTable() . '.' . $c->getColumn()] = $c;
} else {
$column = $p1;
$this->map[$column] = new Criterion($this, $column, $value, $comparison);
}
return $this;
}
/**
* This is the way that you should add a straight (inner) join of two tables. For
* example:
*
* <p>
* AND PROJECT.PROJECT_ID=FOO.PROJECT_ID
* <p>
*
* left = PROJECT.PROJECT_ID
* right = FOO.PROJECT_ID
*
* @param string $left A String with the left side of the join.
* @param string $right A String with the right side of the join.
* @param string $operator A String with the join operator e.g. LEFT JOIN, ...
* @return Criteria A modified Criteria object.
*/
public function addJoin($left, $right, $operator = null)
{
$this->joins [] = new Join($left, $right, $operator);
return $this;
}
/**
* Get the array of Joins. This method is meant to
* be called by BasePeer.
* @return an array which contains objects of type Join,
* or an empty array if the criteria does not contains any joins
*/
function & getJoins()
{
return $this->joins;
}
/**
* get one side of the set of possible joins. This method is meant to
* be called by BasePeer.
* @return array
* @deprecated This method is no longer used by BasePeer.
*/
public function getJoinL()
{
throw new PropelException("getJoinL() in Criteria is no longer supported!");
}
/**
* get one side of the set of possible joins. This method is meant to
* be called by BasePeer.
* @return array
* @deprecated This method is no longer used by BasePeer.
*/
public function getJoinR()
{
throw new PropelException("getJoinR() in Criteria is no longer supported!");
}
/**
* Adds "ALL " to the SQL statement.
* @return void
*/
public function setAll()
{
$this->selectModifiers[] = self::ALL;
}
/**
* Adds "DISTINCT " to the SQL statement.
* @return void
*/
public function setDistinct()
{
$this->selectModifiers[] = self::DISTINCT;
}
/**
* Sets ignore case.
*
* @param boolean $b True if case should be ignored.
* @return A modified Criteria object.
*/
public function setIgnoreCase($b)
{
$this->ignoreCase = (boolean) $b;
return $this;
}
/**
* Is ignore case on or off?
*
* @return boolean True if case is ignored.
*/
public function isIgnoreCase()
{
return $this->ignoreCase;
}
/**
* Set single record? Set this to <code>true</code> if you expect the query
* to result in only a single result record (the default behaviour is to
* throw a PropelException if multiple records are returned when the query
* is executed). This should be used in situations where returning multiple
* rows would indicate an error of some sort. If your query might return
* multiple records but you are only interested in the first one then you
* should be using setLimit(1).
*
* @param b set to <code>true</code> if you expect the query to select just
* one record.
* @return A modified Criteria object.
*/
public function setSingleRecord($b)
{
$this->singleRecord = (boolean) $b;
return $this;
}
/**
* Is single record?
*
* @return boolean True if a single record is being returned.
*/
public function isSingleRecord()
{
return $this->singleRecord;
}
/**
* Set limit.
*
* @param limit An int with the value for limit.
* @return A modified Criteria object.
*/
public function setLimit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Get limit.
*
* @return int An int with the value for limit.
*/
public function getLimit()
{
return $this->limit;
}
/**
* Set offset.
*
* @param int $offset An int with the value for offset.
* @return A modified Criteria object.
*/
public function setOffset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* Get offset.
*
* @return An int with the value for offset.
*/
public function getOffset()
{
return $this->offset;
}
/**
* Add select column.
*
* @param name A String with the name of the select column.
* @return A modified Criteria object.
*/
public function addSelectColumn($name)
{
$this->selectColumns[] = $name;
return $this;
}
/**
* Get select columns.
*
* @return array An array with the name of the select
* columns.
*/
public function getSelectColumns()
{
return $this->selectColumns;
}
/**
* Clears current select columns.
*
* @return Criteria A modified Criteria object.
*/
public function clearSelectColumns() {
$this->selectColumns = array();
$this->asColumns = array();
return $this;
}
/**
* Get select modifiers.
*
* @return An array with the select modifiers.
*/
public function getSelectModifiers()
{
return $this->selectModifiers;
}
/**
* Add group by column name.
*
* @param string $groupBy The name of the column to group by.
* @return A modified Criteria object.
*/
public function addGroupByColumn($groupBy)
{
$this->groupByColumns[] = $groupBy;
return $this;
}
/**
* Add order by column name, explicitly specifying ascending.
*
* @param name The name of the column to order by.
* @return A modified Criteria object.
*/
public function addAscendingOrderByColumn($name)
{
$this->orderByColumns[] = $name . ' ' . self::ASC;
return $this;
}
/**
* Add order by column name, explicitly specifying descending.
*
* @param string $name The name of the column to order by.
* @return Criteria The modified Criteria object.
*/
public function addDescendingOrderByColumn($name)
{
$this->orderByColumns[] = $name . ' ' . self::DESC;
return $this;
}
/**
* Get order by columns.
*
* @return array An array with the name of the order columns.
*/
public function getOrderByColumns()
{
return $this->orderByColumns;
}
/**
* Clear the order-by columns.
*
* @return Criteria
*/
public function clearOrderByColumns()
{
$this->orderByColumns = array();
return $this;
}
/**
* Clear the group-by columns.
*
* @return Criteria
*/
public function clearGroupByColumns()
{
$this->groupByColumns = array();
return $this;
}
/**
* Get group by columns.
*
* @return array
*/
public function getGroupByColumns()
{
return $this->groupByColumns;
}
/**
* Get Having Criterion.
*
* @return Criterion A Criterion object that is the having clause.
*/
public function getHaving()
{
return $this->having;
}
/**
* Remove an object from the criteria.
*
* @param string $key A string with the key to be removed.
* @return mixed The removed value.
*/
public function remove($key)
{
$c = isset($this->map[$key]) ? $this->map[$key] : null;
unset($this->map[$key]);
if ($c instanceof Criterion) {
return $c->getValue();
}
return $c;
}
/**
* Build a string representation of the Criteria.
*
* @return string A String with the representation of the Criteria.
*/
public function toString()
{
$sb = "Criteria:: ";
try {
$sb .= "\nCurrent Query SQL (may not be complete or applicable): "
. BasePeer::createSelectSql($this, $params=array());
$sb .= "\nParameters to replace: " . var_export($params, true);
} catch (Exception $exc) {
$sb .= "(Error: " . $exc->getMessage() . ")";
}
return $sb;
}
/**
* Returns the size (count) of this criteria.
* @return int
*/
public function size()
{
return count($this->map);
}
/**
* This method checks another Criteria to see if they contain
* the same attributes and hashtable entries.
* @return boolean
*/
public function equals($crit)
{
$isEquiv = false;
if ($crit === null || !($crit instanceof Criteria)) {
$isEquiv = false;
} elseif ($this === $crit) {
$isEquiv = true;
} elseif ($this->size() === $crit->size()) {
// Important: nested criterion objects are checked
$criteria = $crit; // alias
if ($this->offset === $criteria->getOffset()
&& $this->limit === $criteria->getLimit()
&& $this->ignoreCase === $criteria->isIgnoreCase()
&& $this->singleRecord === $criteria->isSingleRecord()
&& $this->dbName === $criteria->getDbName()
&& $this->selectModifiers === $criteria->getSelectModifiers()
&& $this->selectColumns === $criteria->getSelectColumns()
&& $this->orderByColumns === $criteria->getOrderByColumns()
)
{
$isEquiv = true;
foreach($criteria->keys() as $key) {
if ($this->containsKey($key)) {
$a = $this->getCriterion($key);
$b = $criteria->getCriterion($key);
if (!$a->equals($b)) {
$isEquiv = false;
break;
}
} else {
$isEquiv = false;
break;
}
}
}
}
return $isEquiv;
}
/**
* This method adds a prepared Criterion object to the Criteria as a having clause.
* You can get a new, empty Criterion object with the
* getNewCriterion() method.
*
* <p>
* <code>
* $crit = new Criteria();
* $c = $crit->getNewCriterion(BasePeer::ID, 5, Criteria::LESS_THAN);
* $crit->addHaving($c);
* </code>
*
* @param having A Criterion object
*
* @return A modified Criteria object.
*/
public function addHaving(Criterion $having)
{
$this->having = $having;
return $this;
}
/**
* This method adds a new criterion to the list of criterias.
* If a criterion for the requested column already exists, it is
* "AND"ed to the existing criterion.
*
* addAnd(column, value, comparison)
* <code>
* $crit = $orig_crit->addAnd(&quot;column&quot;,
* &quot;value&quot;
* &quot;Criterion::GREATER_THAN&quot;);
* </code>
*
* addAnd(column, value)
* <code>
* $crit = $orig_crit->addAnd(&quot;column&quot;, &quot;value&quot;);
* </code>
*
* addAnd(Criterion)
* <code>
* $crit = new Criteria();
* $c = $crit->getNewCriterion(BasePeer::ID, 5, Criteria::LESS_THAN);
* $crit->addAnd($c);
* </code>
*
* Any comparison can be used, of course.
*
*
* @return Criteria A modified Criteria object.
*/
public function addAnd($p1, $p2 = null, $p3 = null)
{
if ($p3 !== null) {
// addAnd(column, value, comparison)
$oc = $this->getCriterion($p1);
$nc = new Criterion($this, $p1, $p2, $p3);
if ($oc === null) {
$this->map[$p1] = $nc;
} else {
$oc->addAnd($nc);
}
} elseif ($p2 !== null) {
// addAnd(column, value)
$this->addAnd($p1, $p2, self::EQUAL);
} elseif ($p1 instanceof Criterion) {
// addAnd(Criterion)
$c = $p1;
$oc = $this->getCriterion($c->getTable() . '.' . $c->getColumn());
if ($oc === null) {
$this->add($c);
} else {
$oc->addAnd($c);
}
} elseif ($p2 === null && $p3 === null) {
// client has not specified $p3 (comparison)
// which means Criteria::EQUAL but has also specified $p2 == null
// which is a valid combination we should handle by creating "IS NULL"
$this->addAnd($p1, $p2, self::EQUAL);
}
return $this;
}
/**
* This method adds a new criterion to the list of criterias.
* If a criterion for the requested column already exists, it is
* "OR"ed to the existing criterion.
*
* Any comparison can be used.
*
* Supports a number of different signatures:
*
* addOr(column, value, comparison)
* <code>
* $crit = $orig_crit->addOr(&quot;column&quot;,
* &quot;value&quot;
* &quot;Criterion::GREATER_THAN&quot;);
* </code>
*
* addOr(column, value)
* <code>
* $crit = $orig_crit->addOr(&quot;column&quot;, &quot;value&quot;);
* </code>
*
* addOr(Criterion)
*
* @return Criteria A modified Criteria object.
*/
public function addOr($p1, $p2 = null, $p3 = null)
{
if ($p3 !== null) {
// addOr(column, value, comparison)
$oc = $this->getCriterion($p1);
$nc = new Criterion($this, $p1, $p2, $p3);
if ($oc === null) {
$this->map[$p1] = $nc;
} else {
$oc->addOr($nc);
}
} elseif ($p2 !== null) {
// addOr(column, value)
$this->addOr($p1, $p2, self::EQUAL);
} elseif ($p1 instanceof Criterion) {
// addOr(Criterion)
$c = $p1;
$oc = $this->getCriterion($c->getTable() . '.' . $c->getColumn());
if ($oc === null) {
$this->add($c);
} else {
$oc->addOr($c);
}
} elseif ($p2 === null && $p3 === null) {
// client has not specified $p3 (comparison)
// which means Criteria::EQUAL but has also specified $p2 == null
// which is a valid combination we should handle by creating "IS NULL"
$this->addOr($p1, $p2, self::EQUAL);
}
return $this;
}
}
// --------------------------------------------------------------------
// Criterion Iterator class -- allows foreach($criteria as $criterion)
// --------------------------------------------------------------------
/**
* Class that implements SPL Iterator interface. This allows foreach() to
* be used w/ Criteria objects. Probably there is no performance advantage
* to doing it this way, but it makes sense -- and simpler code.
*
* @author Hans Lellelid <hans@xmpl.org>
* @package propel.util
*/
class CriterionIterator implements Iterator {
private $idx = 0;
private $criteria;
private $criteriaKeys;
private $criteriaSize;
public function __construct($criteria) {
$this->criteria = $criteria;
$this->criteriaKeys = $criteria->keys();
$this->criteriaSize = count($this->criteriaKeys);
}
public function rewind() {
$this->idx = 0;
}
public function valid() {
return $this->idx < $this->criteriaSize;
}
public function key() {
return $this->criteriaKeys[$this->idx];
}
public function current() {
return $this->criteria->getCriterion($this->criteriaKeys[$this->idx]);
}
public function next() {
$this->idx++;
}
}
// --------------------------------------------------------------------
// Criterion "inner" class
// --------------------------------------------------------------------
/**
* This is an "inner" class that describes an object in the criteria.
*
* In Torque this is an inner class of the Criteria class.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @package propel.util
*/
class Criterion {
const UND = " AND ";
const ODER = " OR ";
/** Value of the CO. */
private $value;
/** Comparison value.
* @var SqlEnum
*/
private $comparison;
/** Table name. */
private $table;
/** Real table name */
private $realtable;
/** Column name. */
private $column;
/** flag to ignore case in comparision */
private $ignoreStringCase = false;
/**
* The DBAdaptor which might be used to get db specific
* variations of sql.
*/
private $db;
/**
* other connected criteria and their conjunctions.
*/
private $clauses = array();
private $conjunctions = array();
/** "Parent" Criteria class */
private $parent;
/**
* Create a new instance.
*
* @param Criteria $parent The outer class (this is an "inner" class).
* @param string $column TABLE.COLUMN format.
* @param mixed $value
* @param string $comparison
*/
public function __construct(Criteria $outer, $column, $value, $comparison = null)
{
list($this->table, $this->column) = explode('.', $column);
$this->value = $value;
$this->comparison = ($comparison === null ? Criteria::EQUAL : $comparison);
$this->init($outer);
}
/**
* Init some properties with the help of outer class
* @param Criteria $criteria The outer class
*/
public function init(Criteria $criteria)
{
//init $this->db
try {
$db = Propel::getDB($criteria->getDbName());
$this->setDB($db);
} catch (Exception $e) {
// we are only doing this to allow easier debugging, so
// no need to throw up the exception, just make note of it.
Propel::log("Could not get a DBAdapter, generated sql may be wrong", Propel::LOG_ERR);
}
//init $this->realtable
$realtable = $criteria->getTableForAlias($this->table);
if(!$realtable) $realtable = $this->table;
$this->realtable = $realtable;
}
/**
* Get the column name.
*
* @return string A String with the column name.
*/
public function getColumn()
{
return $this->column;
}
/**
* Set the table name.
*
* @param name A String with the table name.
* @return void
*/
public function setTable($name)
{
$this->table = $name;
}
/**
* Get the table name.
*
* @return string A String with the table name.
*/
public function getTable()
{
return $this->table;
}
/**
* Get the comparison.
*
* @return string A String with the comparison.
*/
public function getComparison()
{
return $this->comparison;
}
/**
* Get the value.
*
* @return mixed An Object with the value.
*/
public function getValue()
{
return $this->value;
}
/**
* Get the value of db.
* The DBAdapter which might be used to get db specific
* variations of sql.
* @return DBAdapter value of db.
*/
public function getDB()
{
return $this->db;
}
/**
* Set the value of db.
* The DBAdapter might be used to get db specific variations of sql.
* @param DBAdapter $v Value to assign to db.
* @return void
*/
public function setDB(DBAdapter $v)
{
$this->db = $v;
for($i=0, $_i=count($this->clauses); $i < $_i; $i++) {
$this->clauses[$i]->setDB($v);
}
}
/**
* Sets ignore case.
*
* @param boolean $b True if case should be ignored.
* @return Criterion A modified Criterion object.
*/
public function setIgnoreCase($b)
{
$this->ignoreStringCase = $b;
return $this;
}
/**
* Is ignore case on or off?
*
* @return boolean True if case is ignored.
*/
public function isIgnoreCase()
{
return $this->ignoreStringCase;
}
/**
* Get the list of clauses in this Criterion.
* @return array
*/
private function getClauses()
{
return $this->clauses;
}
/**
* Get the list of conjunctions in this Criterion
* @return array
*/
public function getConjunctions()
{
return $this->conjunctions;
}
/**
* Append an AND Criterion onto this Criterion's list.
*/
public function addAnd(Criterion $criterion)
{
$this->clauses[] = $criterion;
$this->conjunctions[] = self::UND;
return $this;
}
/**
* Append an OR Criterion onto this Criterion's list.
* @return Criterion
*/
public function addOr(Criterion $criterion)
{
$this->clauses[] = $criterion;
$this->conjunctions[] = self::ODER;
return $this;
}
/**
* Appends a Prepared Statement representation of the Criterion
* onto the buffer.
*
* @param string &$sb The stringbuffer that will receive the Prepared Statement
* @param array $params A list to which Prepared Statement parameters
* will be appended
* @return void
* @throws PropelException - if the expression builder cannot figure out how to turn a specified
* expression into proper SQL.
*/
public function appendPsTo(&$sb, &$params)
{
if ($this->column === null) {
return;
}
$db = $this->getDb();
$sb .= str_repeat ( '(', count($this->clauses) );
if (Criteria::CUSTOM === $this->comparison) {
if ($this->value !== "") {
$sb .= (string) $this->value;
}
} else {
if ($this->table === null) {
$field = $this->column;
} else {
$field = $this->table . '.' . $this->column;
}
// Check to see if table is an alias & store real name, if so
// (real table name is needed for the returned $params array)
$realtable = $this->realtable;
// There are several different types of expressions that need individual handling:
// IN/NOT IN, LIKE/NOT LIKE, and traditional expressions.
// OPTION 1: table.column IN (?, ?) or table.column NOT IN (?, ?)
if ($this->comparison === Criteria::IN || $this->comparison === Criteria::NOT_IN) {
$valuesLength = 0;
foreach ( (array) $this->value as $value ) {
$valuesLength++;
$params[] = array('table' => $realtable, 'column' => $this->column, 'value' => $value);
}
if ( $valuesLength !== 0 ) {
$sb .= $field . $this->comparison . '(' . substr(str_repeat("?,", $valuesLength), 0, -1) . ')';
} else {
$sb .= ($this->comparison === Criteria::IN) ? "1<>1" : "1=1";
}
unset ( $value, $valuesLength );
// OPTION 2: table.column LIKE ? or table.column NOT LIKE ? (or ILIKE for Postgres)
} elseif ($this->comparison === Criteria::LIKE || $this->comparison === Criteria::NOT_LIKE
|| $this->comparison === Criteria::ILIKE || $this->comparison === Criteria::NOT_ILIKE) {
// Handle LIKE, NOT LIKE (and related ILIKE, NOT ILIKE for Postgres)
// If selection is case insensitive use ILIKE for PostgreSQL or SQL
// UPPER() function on column name for other databases.
if ($this->ignoreStringCase) {
include_once 'propel/adapter/DBPostgres.php';
if ($db instanceof DBPostgres) {
if ($this->comparison === Criteria::LIKE) {
$this->comparison = Criteria::ILIKE;
} elseif ($this->comparison === Criteria::NOT_LIKE) {
$this->comparison = Criteria::NOT_ILIKE;
}
} else {
$field = $db->ignoreCase($field);
}
}
$sb .= $field . $this->comparison;
// If selection is case insensitive use SQL UPPER() function
// on criteria or, if Postgres we are using ILIKE, so not necessary.
if ($this->ignoreStringCase && !($db instanceof DBPostgres)) {
$sb .= $db->ignoreCase('?');
} else {
$sb .= '?';
}
$params[] = array('table' => $realtable, 'column' => $this->column, 'value' => $this->value);
// OPTION 3: table.column = ? or table.column >= ? etc. (traditional expressions, the default)
} else {
// NULL VALUES need special treatment because the SQL syntax is different
// i.e. table.column IS NULL rather than table.column = null
if ($this->value !== null) {
// ANSI SQL functions get inserted right into SQL (not escaped, etc.)
if ($this->value === Criteria::CURRENT_DATE || $this->value === Criteria::CURRENT_TIME || $this->value === Criteria::CURRENT_TIMESTAMP) {
$sb .= $field . $this->comparison . $this->value;
} else {
// default case, it is a normal col = value expression; value
// will be replaced w/ '?' and will be inserted later using native Creole functions
if ($this->ignoreStringCase) {
$sb .= $db->ignoreCase($field) . $this->comparison . $db->ignoreCase("?");
} else {
$sb .= $field . $this->comparison . "?";
}
// need to track the field in params, because
// we'll need it to determine the correct setter
// method later on (e.g. field 'review.DATE' => setDate());
$params[] = array('table' => $realtable, 'column' => $this->column, 'value' => $this->value);
}
} else {
// value is null, which means it was either not specified or specifically
// set to null.
if ($this->comparison === Criteria::EQUAL || $this->comparison === Criteria::ISNULL) {
$sb .= $field . Criteria::ISNULL;
} elseif ($this->comparison === Criteria::NOT_EQUAL || $this->comparison === Criteria::ISNOTNULL) {
$sb .= $field . Criteria::ISNOTNULL;
} else {
// for now throw an exception, because not sure how to interpret this
throw new PropelException("Could not build SQL for expression: $field " . $this->comparison . " NULL");
}
}
}
}
foreach ( $this->clauses as $key=>$clause ) {
$sb .= $this->conjunctions[$key];
$clause->appendPsTo($sb, $params);
$sb .= ')';
}
}
/**
* This method checks another Criteria to see if they contain
* the same attributes and hashtable entries.
* @return boolean
*/
public function equals($obj)
{
if ($this === $obj) {
return true;
}
if (($obj === null) || !($obj instanceof Criterion)) {
return false;
}
$crit = $obj;
$isEquiv = ( ( ($this->table === null && $crit->getTable() === null)
|| ( $this->table !== null && $this->table === $crit->getTable() )
)
&& $this->column === $crit->getColumn()
&& $this->comparison === $crit->getComparison());
// check chained criterion
$clausesLength = count($this->clauses);
$isEquiv &= (count($crit->getClauses()) == $clausesLength);
$critConjunctions = $crit->getConjunctions();
$critClauses = $crit->getClauses();
for ($i=0; $i < $clausesLength && $isEquiv; $i++) {
$isEquiv &= ($this->conjunctions[$i] === $critConjunctions[$i]);
$isEquiv &= ($this->clauses[$i] === $critClauses[$i]);
}
if ($isEquiv) {
$isEquiv &= $this->value === $crit->getValue();
}
return $isEquiv;
}
/**
* Returns a hash code value for the object.
*/
public function hashCode()
{
$h = crc32(serialize($this->value)) ^ crc32($this->comparison);
if ($this->table !== null) {
$h ^= crc32($this->table);
}
if ($this->column !== null) {
$h ^= crc32($this->column);
}
foreach ( $this->clauses as $clause ) {
// TODO: i KNOW there is a php incompatibility with the following line
// but i dont remember what it is, someone care to look it up and
// replace it if it doesnt bother us?
// $clause->appendPsTo($sb='',$params=array());
$sb = '';
$params = array();
$clause->appendPsTo($sb,$params);
$h ^= crc32(serialize(array($sb,$params)));
unset ( $sb, $params );
}
return $h;
}
/**
* Get all tables from nested criterion objects
* @return array
*/
public function getAllTables()
{
$tables = array();
$this->addCriterionTable($this, $tables);
return $tables;
}
/**
* method supporting recursion through all criterions to give
* us a string array of tables from each criterion
* @return void
*/
private function addCriterionTable(Criterion $c, &$s)
{
$s[] = $c->getTable();
foreach ( $c->getClauses() as $clause ) {
$this->addCriterionTable($clause, $s);
}
}
/**
* get an array of all criterion attached to this
* recursing through all sub criterion
* @return array Criterion[]
*/
public function getAttachedCriterion()
{
$crits = array();
$this->traverseCriterion($this, $crits);
return $crits;
}
/**
* method supporting recursion through all criterions to give
* us an array of them
* @param Criterion $c
* @param array &$a
* @return void
*/
private function traverseCriterion(Criterion $c, &$a)
{
$a[] = $c;
foreach ( $c->getClauses() as $clause ) {
$this->traverseCriterion($clause, $a);
}
}
}
/**
* Data object to describe a join between two tables, for example
* <pre>
* table_a LEFT JOIN table_b ON table_a.id = table_b.a_id
* </pre>
*/
class Join
{
/** the left column of the join condition */
private $leftColumn = null;
/** the right column of the join condition */
private $rightColumn = null;
/** the type of the join (LEFT JOIN, ...), or null */
private $joinType = null;
/**
* Constructor
* @param leftColumn the left column of the join condition;
* might contain an alias name
* @param rightColumn the right column of the join condition
* might contain an alias name
* @param joinType the type of the join. Valid join types are
* null (adding the join condition to the where clause),
* Criteria::LEFT_JOIN(), Criteria::RIGHT_JOIN(), and Criteria::INNER_JOIN()
*/
public function __construct($leftColumn, $rightColumn, $joinType = null)
{
$this->leftColumn = $leftColumn;
$this->rightColumn = $rightColumn;
$this->joinType = $joinType;
}
/**
* @return the type of the join, i.e. Criteria::LEFT_JOIN(), ...,
* or null for adding the join condition to the where Clause
*/
public function getJoinType()
{
return $this->joinType;
}
/**
* @return the left column of the join condition
*/
public function getLeftColumn()
{
return $this->leftColumn;
}
public function getLeftColumnName()
{
return substr($this->leftColumn, strpos($this->leftColumn, '.') + 1);
}
public function getLeftTableName()
{
return substr($this->leftColumn, 0, strpos($this->leftColumn, '.'));
}
/**
* @return the right column of the join condition
*/
public function getRightColumn()
{
return $this->rightColumn;
}
public function getRightColumnName()
{
return substr($this->rightColumn, strpos($this->rightColumn, '.') + 1);
}
public function getRightTableName()
{
return substr($this->rightColumn, 0, strpos($this->rightColumn, '.'));
}
/**
* returns a String representation of the class,
* mainly for debugging purposes
* @return a String representation of the class
*/
public function toString()
{
$result = "";
if ($this->joinType != null)
{
$result .= $this->joinType . " : ";
}
$result .= $this->leftColumn . "=" . $this->rightColumn . " (ignoreCase not considered)";
return $result;
}
}