sintonia/library/propel/runtime/lib/util/BasePeer.php

1100 lines
35 KiB
PHP

<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
/**
* This is a utility class for all generated Peer classes in the system.
*
* Peer classes are responsible for isolating all of the database access
* for a specific business object. They execute all of the SQL
* against the database. Over time this class has grown to include
* utility methods which ease execution of cross-database queries and
* the implementation of concrete Peers.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @author Kaspars Jaudzems <kaspars.jaudzems@inbox.lv> (Propel)
* @author Heltem <heltem@o2php.com> (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 Stephen Haberman <stephenh@chase3000.com> (Torque)
* @version $Revision: 1772 $
* @package propel.runtime.util
*/
class BasePeer
{
/** Array (hash) that contains the cached mapBuilders. */
private static $mapBuilders = array();
/** Array (hash) that contains cached validators */
private static $validatorMap = array();
/**
* phpname type
* e.g. 'AuthorId'
*/
const TYPE_PHPNAME = 'phpName';
/**
* studlyphpname type
* e.g. 'authorId'
*/
const TYPE_STUDLYPHPNAME = 'studlyPhpName';
/**
* column (peer) name type
* e.g. 'book.AUTHOR_ID'
*/
const TYPE_COLNAME = 'colName';
/**
* column part of the column peer name
* e.g. 'AUTHOR_ID'
*/
const TYPE_RAW_COLNAME = 'rawColName';
/**
* column fieldname type
* e.g. 'author_id'
*/
const TYPE_FIELDNAME = 'fieldName';
/**
* num type
* simply the numerical array index, e.g. 4
*/
const TYPE_NUM = 'num';
static public function getFieldnames ($classname, $type = self::TYPE_PHPNAME) {
// TODO we should take care of including the peer class here
$peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true?
$callable = array($peerclass, 'getFieldnames');
return call_user_func($callable, $type);
}
static public function translateFieldname($classname, $fieldname, $fromType, $toType) {
// TODO we should take care of including the peer class here
$peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true?
$callable = array($peerclass, 'translateFieldname');
$args = array($fieldname, $fromType, $toType);
return call_user_func_array($callable, $args);
}
/**
* Method to perform deletes based on values and keys in a
* Criteria.
*
* @param Criteria $criteria The criteria to use.
* @param PropelPDO $con A PropelPDO connection object.
* @return int The number of rows affected by last statement execution. For most
* uses there is only one delete statement executed, so this number
* will correspond to the number of rows affected by the call to this
* method. Note that the return value does require that this information
* is returned (supported) by the PDO driver.
* @throws PropelException
*/
public static function doDelete(Criteria $criteria, PropelPDO $con)
{
$db = Propel::getDB($criteria->getDbName());
$dbMap = Propel::getDatabaseMap($criteria->getDbName());
// Set up a list of required tables (one DELETE statement will
// be executed per table)
$tables = $criteria->getTablesColumns();
if (empty($tables)) {
throw new PropelException("Cannot delete from an empty Criteria");
}
$affectedRows = 0; // initialize this in case the next loop has no iterations.
foreach ($tables as $tableName => $columns) {
$whereClause = array();
$params = array();
$stmt = null;
try {
$sql = 'DELETE ';
if ($queryComment = $criteria->getComment()) {
$sql .= '/* ' . $queryComment . ' */ ';
}
if ($realTableName = $criteria->getTableForAlias($tableName)) {
if ($db->useQuoteIdentifier()) {
$realTableName = $db->quoteIdentifierTable($realTableName);
}
$sql .= $tableName . ' FROM ' . $realTableName . ' AS ' . $tableName;
} else {
if ($db->useQuoteIdentifier()) {
$tableName = $db->quoteIdentifierTable($tableName);
}
$sql .= 'FROM ' . $tableName;
}
foreach ($columns as $colName) {
$sb = "";
$criteria->getCriterion($colName)->appendPsTo($sb, $params);
$whereClause[] = $sb;
}
$sql .= " WHERE " . implode(" AND ", $whereClause);
$stmt = $con->prepare($sql);
self::populateStmtValues($stmt, $params, $dbMap, $db);
$stmt->execute();
$affectedRows = $stmt->rowCount();
} catch (Exception $e) {
Propel::log($e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute DELETE statement [%s]', $sql), $e);
}
} // for each table
return $affectedRows;
}
/**
* Method to deletes all contents of specified table.
*
* This method is invoked from generated Peer classes like this:
* <code>
* public static function doDeleteAll($con = null)
* {
* if ($con === null) $con = Propel::getConnection(self::DATABASE_NAME);
* BasePeer::doDeleteAll(self::TABLE_NAME, $con, self::DATABASE_NAME);
* }
* </code>
*
* @param string $tableName The name of the table to empty.
* @param PropelPDO $con A PropelPDO connection object.
* @param string $databaseName the name of the database.
* @return int The number of rows affected by the statement. Note
* that the return value does require that this information
* is returned (supported) by the Propel db driver.
* @throws PropelException - wrapping SQLException caught from statement execution.
*/
public static function doDeleteAll($tableName, PropelPDO $con, $databaseName = null)
{
try {
$db = Propel::getDB($databaseName);
if ($db->useQuoteIdentifier()) {
$tableName = $db->quoteIdentifierTable($tableName);
}
$sql = "DELETE FROM " . $tableName;
$stmt = $con->prepare($sql);
$stmt->execute();
return $stmt->rowCount();
} catch (Exception $e) {
Propel::log($e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute DELETE ALL statement [%s]', $sql), $e);
}
}
/**
* Method to perform inserts based on values and keys in a
* Criteria.
* <p>
* If the primary key is auto incremented the data in Criteria
* will be inserted and the auto increment value will be returned.
* <p>
* If the primary key is included in Criteria then that value will
* be used to insert the row.
* <p>
* If no primary key is included in Criteria then we will try to
* figure out the primary key from the database map and insert the
* row with the next available id using util.db.IDBroker.
* <p>
* If no primary key is defined for the table the values will be
* inserted as specified in Criteria and null will be returned.
*
* @param Criteria $criteria Object containing values to insert.
* @param PropelPDO $con A PropelPDO connection.
* @return mixed The primary key for the new row if (and only if!) the primary key
* is auto-generated. Otherwise will return <code>null</code>.
* @throws PropelException
*/
public static function doInsert(Criteria $criteria, PropelPDO $con) {
// the primary key
$id = null;
$db = Propel::getDB($criteria->getDbName());
// Get the table name and method for determining the primary
// key value.
$keys = $criteria->keys();
if (!empty($keys)) {
$tableName = $criteria->getTableName( $keys[0] );
} else {
throw new PropelException("Database insert attempted without anything specified to insert");
}
$dbMap = Propel::getDatabaseMap($criteria->getDbName());
$tableMap = $dbMap->getTable($tableName);
$keyInfo = $tableMap->getPrimaryKeyMethodInfo();
$useIdGen = $tableMap->isUseIdGenerator();
//$keyGen = $con->getIdGenerator();
$pk = self::getPrimaryKey($criteria);
// only get a new key value if you need to
// the reason is that a primary key might be defined
// but you are still going to set its value. for example:
// a join table where both keys are primary and you are
// setting both columns with your own values
// pk will be null if there is no primary key defined for the table
// we're inserting into.
if ($pk !== null && $useIdGen && !$criteria->keyContainsValue($pk->getFullyQualifiedName()) && $db->isGetIdBeforeInsert()) {
try {
$id = $db->getId($con, $keyInfo);
} catch (Exception $e) {
throw new PropelException("Unable to get sequence id.", $e);
}
$criteria->add($pk->getFullyQualifiedName(), $id);
}
try {
$adapter = Propel::getDB($criteria->getDBName());
$qualifiedCols = $criteria->keys(); // we need table.column cols when populating values
$columns = array(); // but just 'column' cols for the SQL
foreach ($qualifiedCols as $qualifiedCol) {
$columns[] = substr($qualifiedCol, strrpos($qualifiedCol, '.') + 1);
}
// add identifiers
if ($adapter->useQuoteIdentifier()) {
$columns = array_map(array($adapter, 'quoteIdentifier'), $columns);
$tableName = $adapter->quoteIdentifierTable($tableName);
}
$sql = 'INSERT INTO ' . $tableName
. ' (' . implode(',', $columns) . ')'
. ' VALUES (';
// . substr(str_repeat("?,", count($columns)), 0, -1) .
for($p=1, $cnt=count($columns); $p <= $cnt; $p++) {
$sql .= ':p'.$p;
if ($p !== $cnt) $sql .= ',';
}
$sql .= ')';
$stmt = $con->prepare($sql);
self::populateStmtValues($stmt, self::buildParams($qualifiedCols, $criteria), $dbMap, $db);
$stmt->execute();
} catch (Exception $e) {
Propel::log($e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute INSERT statement [%s]', $sql), $e);
}
// If the primary key column is auto-incremented, get the id now.
if ($pk !== null && $useIdGen && $db->isGetIdAfterInsert()) {
try {
$id = $db->getId($con, $keyInfo);
} catch (Exception $e) {
throw new PropelException("Unable to get autoincrement id.", $e);
}
}
return $id;
}
/**
* Method used to update rows in the DB. Rows are selected based
* on selectCriteria and updated using values in updateValues.
* <p>
* Use this method for performing an update of the kind:
* <p>
* WHERE some_column = some value AND could_have_another_column =
* another value AND so on.
*
* @param $selectCriteria A Criteria object containing values used in where
* clause.
* @param $updateValues A Criteria object containing values used in set
* clause.
* @param PropelPDO $con The PropelPDO connection object to use.
* @return int The number of rows affected by last update statement. For most
* uses there is only one update statement executed, so this number
* will correspond to the number of rows affected by the call to this
* method. Note that the return value does require that this information
* is returned (supported) by the Propel db driver.
* @throws PropelException
*/
public static function doUpdate(Criteria $selectCriteria, Criteria $updateValues, PropelPDO $con) {
$db = Propel::getDB($selectCriteria->getDbName());
$dbMap = Propel::getDatabaseMap($selectCriteria->getDbName());
// Get list of required tables, containing all columns
$tablesColumns = $selectCriteria->getTablesColumns();
if (empty($tablesColumns)) {
$tablesColumns = array($selectCriteria->getPrimaryTableName() => array());
}
// we also need the columns for the update SQL
$updateTablesColumns = $updateValues->getTablesColumns();
$affectedRows = 0; // initialize this in case the next loop has no iterations.
foreach ($tablesColumns as $tableName => $columns) {
$whereClause = array();
$params = array();
$stmt = null;
try {
$sql = 'UPDATE ';
if ($queryComment = $selectCriteria->getComment()) {
$sql .= '/* ' . $queryComment . ' */ ';
}
// is it a table alias?
if ($tableName2 = $selectCriteria->getTableForAlias($tableName)) {
$udpateTable = $tableName2 . ' ' . $tableName;
$tableName = $tableName2;
} else {
$udpateTable = $tableName;
}
if ($db->useQuoteIdentifier()) {
$sql .= $db->quoteIdentifierTable($udpateTable);
} else {
$sql .= $udpateTable;
}
$sql .= " SET ";
$p = 1;
foreach ($updateTablesColumns[$tableName] as $col) {
$updateColumnName = substr($col, strrpos($col, '.') + 1);
// add identifiers for the actual database?
if ($db->useQuoteIdentifier()) {
$updateColumnName = $db->quoteIdentifier($updateColumnName);
}
if ($updateValues->getComparison($col) != Criteria::CUSTOM_EQUAL) {
$sql .= $updateColumnName . '=:p'.$p++.', ';
} else {
$param = $updateValues->get($col);
$sql .= $updateColumnName . ' = ';
if (is_array($param)) {
if (isset($param['raw'])) {
$raw = $param['raw'];
$rawcvt = '';
// parse the $params['raw'] for ? chars
for($r=0,$len=strlen($raw); $r < $len; $r++) {
if ($raw{$r} == '?') {
$rawcvt .= ':p'.$p++;
} else {
$rawcvt .= $raw{$r};
}
}
$sql .= $rawcvt . ', ';
} else {
$sql .= ':p'.$p++.', ';
}
if (isset($param['value'])) {
$updateValues->put($col, $param['value']);
}
} else {
$updateValues->remove($col);
$sql .= $param . ', ';
}
}
}
$params = self::buildParams($updateTablesColumns[$tableName], $updateValues);
$sql = substr($sql, 0, -2);
if (!empty($columns)) {
foreach ($columns as $colName) {
$sb = "";
$selectCriteria->getCriterion($colName)->appendPsTo($sb, $params);
$whereClause[] = $sb;
}
$sql .= " WHERE " . implode(" AND ", $whereClause);
}
$stmt = $con->prepare($sql);
// Replace ':p?' with the actual values
self::populateStmtValues($stmt, $params, $dbMap, $db);
$stmt->execute();
$affectedRows = $stmt->rowCount();
$stmt = null; // close
} catch (Exception $e) {
if ($stmt) $stmt = null; // close
Propel::log($e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute UPDATE statement [%s]', $sql), $e);
}
} // foreach table in the criteria
return $affectedRows;
}
/**
* Executes query build by createSelectSql() and returns the resultset statement.
*
* @param Criteria $criteria A Criteria.
* @param PropelPDO $con A PropelPDO connection to use.
* @return PDOStatement The resultset.
* @throws PropelException
* @see createSelectSql()
*/
public static function doSelect(Criteria $criteria, PropelPDO $con = null)
{
$dbMap = Propel::getDatabaseMap($criteria->getDbName());
$db = Propel::getDB($criteria->getDbName());
$stmt = null;
if ($con === null) {
$con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ);
}
if ($criteria->isUseTransaction()) {
$con->beginTransaction();
}
try {
$params = array();
$sql = self::createSelectSql($criteria, $params);
$stmt = $con->prepare($sql);
self::populateStmtValues($stmt, $params, $dbMap, $db);
$stmt->execute();
if ($criteria->isUseTransaction()) {
$con->commit();
}
} catch (Exception $e) {
if ($stmt) {
$stmt = null; // close
}
if ($criteria->isUseTransaction()) {
$con->rollBack();
}
Propel::log($e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute SELECT statement [%s]', $sql), $e);
}
return $stmt;
}
/**
* Executes a COUNT query using either a simple SQL rewrite or, for more complex queries, a
* sub-select of the SQL created by createSelectSql() and returns the statement.
*
* @param Criteria $criteria A Criteria.
* @param PropelPDO $con A PropelPDO connection to use.
* @return PDOStatement The resultset statement.
* @throws PropelException
* @see createSelectSql()
*/
public static function doCount(Criteria $criteria, PropelPDO $con = null)
{
$dbMap = Propel::getDatabaseMap($criteria->getDbName());
$db = Propel::getDB($criteria->getDbName());
if ($con === null) {
$con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ);
}
$stmt = null;
if ($criteria->isUseTransaction()) {
$con->beginTransaction();
}
$needsComplexCount = $criteria->getGroupByColumns()
|| $criteria->getOffset()
|| $criteria->getLimit()
|| $criteria->getHaving()
|| in_array(Criteria::DISTINCT, $criteria->getSelectModifiers());
try {
$params = array();
if ($needsComplexCount) {
if (self::needsSelectAliases($criteria)) {
if ($criteria->getHaving()) {
throw new PropelException('Propel cannot create a COUNT query when using HAVING and duplicate column names in the SELECT part');
}
self::turnSelectColumnsToAliases($criteria);
}
$selectSql = self::createSelectSql($criteria, $params);
$sql = 'SELECT COUNT(*) FROM (' . $selectSql . ') propelmatch4cnt';
} else {
// Replace SELECT columns with COUNT(*)
$criteria->clearSelectColumns()->addSelectColumn('COUNT(*)');
$sql = self::createSelectSql($criteria, $params);
}
$stmt = $con->prepare($sql);
self::populateStmtValues($stmt, $params, $dbMap, $db);
$stmt->execute();
if ($criteria->isUseTransaction()) {
$con->commit();
}
} catch (Exception $e) {
if ($stmt !== null) {
$stmt = null;
}
if ($criteria->isUseTransaction()) {
$con->rollBack();
}
Propel::log($e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute COUNT statement [%s]', $sql), $e);
}
return $stmt;
}
/**
* Populates values in a prepared statement.
*
* This method is designed to work with the createSelectSql() method, which creates
* both the SELECT SQL statement and populates a passed-in array of parameter
* values that should be substituted.
*
* <code>
* $params = array();
* $sql = BasePeer::createSelectSql($criteria, $params);
* BasePeer::populateStmtValues($stmt, $params, Propel::getDatabaseMap($critera->getDbName()), Propel::getDB($criteria->getDbName()));
* </code>
*
* @param PDOStatement $stmt
* @param array $params array('column' => ..., 'table' => ..., 'value' => ...)
* @param DatabaseMap $dbMap
* @return int The number of params replaced.
* @see createSelectSql()
* @see doSelect()
*/
public static function populateStmtValues(PDOStatement $stmt, array $params, DatabaseMap $dbMap, DBAdapter $db)
{
$i = 1;
foreach ($params as $param) {
$tableName = $param['table'];
$columnName = $param['column'];
$value = $param['value'];
if (null === $value) {
$stmt->bindValue(':p'.$i++, null, PDO::PARAM_NULL);
} elseif (null !== $tableName) {
$cMap = $dbMap->getTable($tableName)->getColumn($columnName);
$type = $cMap->getType();
$pdoType = $cMap->getPdoType();
// FIXME - This is a temporary hack to get around apparent bugs w/ PDO+MYSQL
// See http://pecl.php.net/bugs/bug.php?id=9919
if ($pdoType == PDO::PARAM_BOOL && $db instanceof DBMySQL) {
$value = (int) $value;
$pdoType = PDO::PARAM_INT;
} elseif (is_numeric($value) && $cMap->isEpochTemporal()) { // it's a timestamp that needs to be formatted
if ($type == PropelColumnTypes::TIMESTAMP) {
$value = date($db->getTimestampFormatter(), $value);
} else if ($type == PropelColumnTypes::DATE) {
$value = date($db->getDateFormatter(), $value);
} else if ($type == PropelColumnTypes::TIME) {
$value = date($db->getTimeFormatter(), $value);
}
} elseif ($value instanceof DateTime && $cMap->isTemporal()) { // it's a timestamp that needs to be formatted
if ($type == PropelColumnTypes::TIMESTAMP || $type == PropelColumnTypes::BU_TIMESTAMP) {
$value = $value->format($db->getTimestampFormatter());
} else if ($type == PropelColumnTypes::DATE || $type == PropelColumnTypes::BU_DATE) {
$value = $value->format($db->getDateFormatter());
} else if ($type == PropelColumnTypes::TIME) {
$value = $value->format($db->getTimeFormatter());
}
} elseif (is_resource($value) && $cMap->isLob()) {
// we always need to make sure that the stream is rewound, otherwise nothing will
// get written to database.
rewind($value);
}
$stmt->bindValue(':p'.$i++, $value, $pdoType);
} else {
$stmt->bindValue(':p'.$i++, $value);
}
} // foreach
}
/**
* Applies any validators that were defined in the schema to the specified columns.
*
* @param string $dbName The name of the database
* @param string $tableName The name of the table
* @param array $columns Array of column names as key and column values as value.
*/
public static function doValidate($dbName, $tableName, $columns)
{
$dbMap = Propel::getDatabaseMap($dbName);
$tableMap = $dbMap->getTable($tableName);
$failureMap = array(); // map of ValidationFailed objects
foreach ($columns as $colName => $colValue) {
if ($tableMap->containsColumn($colName)) {
$col = $tableMap->getColumn($colName);
foreach ($col->getValidators() as $validatorMap) {
$validator = BasePeer::getValidator($validatorMap->getClass());
if ($validator && ($col->isNotNull() || $colValue !== null) && $validator->isValid($validatorMap, $colValue) === false) {
if (!isset($failureMap[$colName])) { // for now we do one ValidationFailed per column, not per rule
$failureMap[$colName] = new ValidationFailed($colName, $validatorMap->getMessage(), $validator);
}
}
}
}
}
return (!empty($failureMap) ? $failureMap : true);
}
/**
* Helper method which returns the primary key contained
* in the given Criteria object.
*
* @param Criteria $criteria A Criteria.
* @return ColumnMap If the Criteria object contains a primary
* key, or null if it doesn't.
* @throws PropelException
*/
private static function getPrimaryKey(Criteria $criteria)
{
// Assume all the keys are for the same table.
$keys = $criteria->keys();
$key = $keys[0];
$table = $criteria->getTableName($key);
$pk = null;
if (!empty($table)) {
$dbMap = Propel::getDatabaseMap($criteria->getDbName());
$pks = $dbMap->getTable($table)->getPrimaryKeys();
if (!empty($pks)) {
$pk = array_shift($pks);
}
}
return $pk;
}
/**
* Checks whether the Criteria needs to use column aliasing
* This is implemented in a service class rather than in Criteria itself
* in order to avoid doing the tests when it's not necessary (e.g. for SELECTs)
*/
public static function needsSelectAliases(Criteria $criteria)
{
$columnNames = array();
foreach ($criteria->getSelectColumns() as $fullyQualifiedColumnName) {
if ($pos = strrpos($fullyQualifiedColumnName, '.')) {
$columnName = substr($fullyQualifiedColumnName, $pos);
if (isset($columnNames[$columnName])) {
// more than one column with the same name, so aliasing is required
return true;
}
$columnNames[$columnName] = true;
}
}
return false;
}
/**
* Ensures uniqueness of select column names by turning them all into aliases
* This is necessary for queries on more than one table when the tables share a column name
* @see http://propel.phpdb.org/trac/ticket/795
*
* @param Criteria $criteria
*
* @return Criteria The input, with Select columns replaced by aliases
*/
public static function turnSelectColumnsToAliases(Criteria $criteria)
{
$selectColumns = $criteria->getSelectColumns();
// clearSelectColumns also clears the aliases, so get them too
$asColumns = $criteria->getAsColumns();
$criteria->clearSelectColumns();
$columnAliases = $asColumns;
// add the select columns back
foreach ($selectColumns as $clause) {
// Generate a unique alias
$baseAlias = preg_replace('/\W/', '_', $clause);
$alias = $baseAlias;
// If it already exists, add a unique suffix
$i = 0;
while (isset($columnAliases[$alias])) {
$i++;
$alias = $baseAlias . '_' . $i;
}
// Add it as an alias
$criteria->addAsColumn($alias, $clause);
$columnAliases[$alias] = $clause;
}
// Add the aliases back, don't modify them
foreach ($asColumns as $name => $clause) {
$criteria->addAsColumn($name, $clause);
}
return $criteria;
}
/**
* Method to create an SQL query based on values in a Criteria.
*
* This method creates only prepared statement SQL (using ? where values
* will go). The second parameter ($params) stores the values that need
* to be set before the statement is executed. The reason we do it this way
* is to let the PDO layer handle all escaping & value formatting.
*
* @param Criteria $criteria Criteria for the SELECT query.
* @param array &$params Parameters that are to be replaced in prepared statement.
* @return string
* @throws PropelException Trouble creating the query string.
*/
public static function createSelectSql(Criteria $criteria, &$params)
{
$db = Propel::getDB($criteria->getDbName());
$dbMap = Propel::getDatabaseMap($criteria->getDbName());
$fromClause = array();
$joinClause = array();
$joinTables = array();
$whereClause = array();
$orderByClause = array();
$orderBy = $criteria->getOrderByColumns();
$groupBy = $criteria->getGroupByColumns();
$ignoreCase = $criteria->isIgnoreCase();
// get the first part of the SQL statement, the SELECT part
$selectSql = self::createSelectSqlPart($criteria, $fromClause);
// add the criteria to WHERE clause
// this will also add the table names to the FROM clause if they are not already
// included via a LEFT JOIN
foreach ($criteria->keys() as $key) {
$criterion = $criteria->getCriterion($key);
$table = null;
foreach ($criterion->getAttachedCriterion() as $attachedCriterion) {
$tableName = $attachedCriterion->getTable();
$table = $criteria->getTableForAlias($tableName);
if ($table !== null) {
$fromClause[] = $table . ' ' . $tableName;
} else {
$fromClause[] = $tableName;
$table = $tableName;
}
if (($criteria->isIgnoreCase() || $attachedCriterion->isIgnoreCase())
&& $dbMap->getTable($table)->getColumn($attachedCriterion->getColumn())->isText()) {
$attachedCriterion->setIgnoreCase(true);
}
}
$criterion->setDB($db);
$sb = '';
$criterion->appendPsTo($sb, $params);
$whereClause[] = $sb;
}
// Handle joins
// joins with a null join type will be added to the FROM clause and the condition added to the WHERE clause.
// joins of a specified type: the LEFT side will be added to the fromClause and the RIGHT to the joinClause
foreach ($criteria->getJoins() as $join) {
// The join might have been established using an alias name
$leftTable = $join->getLeftTableName();
if ($realTable = $criteria->getTableForAlias($leftTable)) {
$leftTableForFrom = $realTable . ' ' . $leftTable;
$leftTable = $realTable;
} else {
$leftTableForFrom = $leftTable;
}
$rightTable = $join->getRightTableName();
if ($realTable = $criteria->getTableForAlias($rightTable)) {
$rightTableForFrom = $realTable . ' ' . $rightTable;
$rightTable = $realTable;
} else {
$rightTableForFrom = $rightTable;
}
// determine if casing is relevant.
if ($ignoreCase = $criteria->isIgnoreCase()) {
$leftColType = $dbMap->getTable($leftTable)->getColumn($join->getLeftColumnName())->getType();
$rightColType = $dbMap->getTable($rightTable)->getColumn($join->getRightColumnName())->getType();
$ignoreCase = ($leftColType == 'string' || $rightColType == 'string');
}
// build the condition
$condition = '';
foreach ($join->getConditions() as $index => $conditionDesc) {
if ($ignoreCase) {
$condition .= $db->ignoreCase($conditionDesc['left']) . $conditionDesc['operator'] . $db->ignoreCase($conditionDesc['right']);
} else {
$condition .= implode($conditionDesc);
}
if ($index + 1 < $join->countConditions()) {
$condition .= ' AND ';
}
}
// add 'em to the queues..
if ($joinType = $join->getJoinType()) {
// real join
if (!$fromClause) {
$fromClause[] = $leftTableForFrom;
}
$joinTables[] = $rightTableForFrom;
$joinClause[] = $join->getJoinType() . ' ' . $rightTableForFrom . " ON ($condition)";
} else {
// implicit join, translates to a where
$fromClause[] = $leftTableForFrom;
$fromClause[] = $rightTableForFrom;
$whereClause[] = $condition;
}
}
// Unique from clause elements
$fromClause = array_unique($fromClause);
$fromClause = array_diff($fromClause, array(''));
// tables should not exist in both the from and join clauses
if ($joinTables && $fromClause) {
foreach ($fromClause as $fi => $ftable) {
if (in_array($ftable, $joinTables)) {
unset($fromClause[$fi]);
}
}
}
// Add the GROUP BY columns
$groupByClause = $groupBy;
$having = $criteria->getHaving();
$havingString = null;
if ($having !== null) {
$sb = '';
$having->appendPsTo($sb, $params);
$havingString = $sb;
}
if (!empty($orderBy)) {
foreach ($orderBy as $orderByColumn) {
// Add function expression as-is.
if (strpos($orderByColumn, '(') !== false) {
$orderByClause[] = $orderByColumn;
continue;
}
// Split orderByColumn (i.e. "table.column DESC")
$dotPos = strrpos($orderByColumn, '.');
if ($dotPos !== false) {
$tableName = substr($orderByColumn, 0, $dotPos);
$columnName = substr($orderByColumn, $dotPos + 1);
} else {
$tableName = '';
$columnName = $orderByColumn;
}
$spacePos = strpos($columnName, ' ');
if ($spacePos !== false) {
$direction = substr($columnName, $spacePos);
$columnName = substr($columnName, 0, $spacePos);
} else {
$direction = '';
}
$tableAlias = $tableName;
if ($aliasTableName = $criteria->getTableForAlias($tableName)) {
$tableName = $aliasTableName;
}
$columnAlias = $columnName;
if ($asColumnName = $criteria->getColumnForAs($columnName)) {
$columnName = $asColumnName;
}
$column = $tableName ? $dbMap->getTable($tableName)->getColumn($columnName) : null;
if ($criteria->isIgnoreCase() && $column && $column->isText()) {
$ignoreCaseColumn = $db->ignoreCaseInOrderBy("$tableAlias.$columnAlias");
$orderByClause[] = $ignoreCaseColumn . $direction;
$selectSql .= ', ' . $ignoreCaseColumn;
} else {
$orderByClause[] = $orderByColumn;
}
}
}
if (empty($fromClause) && $criteria->getPrimaryTableName()) {
$fromClause[] = $criteria->getPrimaryTableName();
}
// from / join tables quoted if it is necessary
if ($db->useQuoteIdentifier()) {
$fromClause = array_map(array($db, 'quoteIdentifierTable'), $fromClause);
$joinClause = $joinClause ? $joinClause : array_map(array($db, 'quoteIdentifierTable'), $joinClause);
}
// build from-clause
$from = '';
if (!empty($joinClause) && count($fromClause) > 1) {
$from .= implode(" CROSS JOIN ", $fromClause);
} else {
$from .= implode(", ", $fromClause);
}
$from .= $joinClause ? ' ' . implode(' ', $joinClause) : '';
// Build the SQL from the arrays we compiled
$sql = $selectSql
." FROM " . $from
.($whereClause ? " WHERE ".implode(" AND ", $whereClause) : "")
.($groupByClause ? " GROUP BY ".implode(",", $groupByClause) : "")
.($havingString ? " HAVING ".$havingString : "")
.($orderByClause ? " ORDER BY ".implode(",", $orderByClause) : "");
// APPLY OFFSET & LIMIT to the query.
if ($criteria->getLimit() || $criteria->getOffset()) {
$db->applyLimit($sql, $criteria->getOffset(), $criteria->getLimit(), $criteria);
}
return $sql;
}
/**
* Builds the SELECT part of a SQL statement based on a Criteria
* taking into account select columns and 'as' columns (i.e. columns aliases)
*/
public static function createSelectSqlPart(Criteria $criteria, &$fromClause, $aliasAll = false)
{
$selectClause = array();
if ($aliasAll) {
self::turnSelectColumnsToAliases($criteria);
// no select columns after that, they are all aliases
} else {
foreach ($criteria->getSelectColumns() as $columnName) {
// expect every column to be of "table.column" formation
// it could be a function: e.g. MAX(books.price)
$tableName = null;
$selectClause[] = $columnName; // the full column name: e.g. MAX(books.price)
$parenPos = strrpos($columnName, '(');
$dotPos = strrpos($columnName, '.', ($parenPos !== false ? $parenPos : 0));
if ($dotPos !== false) {
if ($parenPos === false) { // table.column
$tableName = substr($columnName, 0, $dotPos);
} else { // FUNC(table.column)
// functions may contain qualifiers so only take the last
// word as the table name.
// COUNT(DISTINCT books.price)
$lastSpace = strpos($tableName, ' ');
if ($lastSpace !== false) { // COUNT(DISTINCT books.price)
$tableName = substr($tableName, $lastSpace + 1);
} else {
$tableName = substr($columnName, $parenPos + 1, $dotPos - ($parenPos + 1));
}
}
// is it a table alias?
$tableName2 = $criteria->getTableForAlias($tableName);
if ($tableName2 !== null) {
$fromClause[] = $tableName2 . ' ' . $tableName;
} else {
$fromClause[] = $tableName;
}
} // if $dotPost !== false
}
}
// set the aliases
foreach ($criteria->getAsColumns() as $alias => $col) {
$selectClause[] = $col . ' AS ' . $alias;
}
$selectModifiers = $criteria->getSelectModifiers();
$queryComment = $criteria->getComment();
// Build the SQL from the arrays we compiled
$sql = "SELECT "
. ($queryComment ? '/* ' . $queryComment . ' */ ' : '')
. ($selectModifiers ? (implode(' ', $selectModifiers) . ' ') : '')
. implode(", ", $selectClause);
return $sql;
}
/**
* Builds a params array, like the kind populated by Criterion::appendPsTo().
* This is useful for building an array even when it is not using the appendPsTo() method.
* @param array $columns
* @param Criteria $values
* @return array params array('column' => ..., 'table' => ..., 'value' => ...)
*/
private static function buildParams($columns, Criteria $values)
{
$params = array();
foreach ($columns as $key) {
if ($values->containsKey($key)) {
$crit = $values->getCriterion($key);
$params[] = array('column' => $crit->getColumn(), 'table' => $crit->getTable(), 'value' => $crit->getValue());
}
}
return $params;
}
/**
* This function searches for the given validator $name under propel/validator/$name.php,
* imports and caches it.
*
* @param string $classname The dot-path name of class (e.g. myapp.propel.MyValidator)
* @return Validator object or null if not able to instantiate validator class (and error will be logged in this case)
*/
public static function getValidator($classname)
{
try {
$v = isset(self::$validatorMap[$classname]) ? self::$validatorMap[$classname] : null;
if ($v === null) {
$cls = Propel::importClass($classname);
$v = new $cls();
self::$validatorMap[$classname] = $v;
}
return $v;
} catch (Exception $e) {
Propel::log("BasePeer::getValidator(): failed trying to instantiate " . $classname . ": ".$e->getMessage(), Propel::LOG_ERR);
}
}
}