BasePeer.php 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  1. <?php
  2. /**
  3. * This file is part of the Propel package.
  4. * For the full copyright and license information, please view the LICENSE
  5. * file that was distributed with this source code.
  6. *
  7. * @license MIT License
  8. */
  9. /**
  10. * This is a utility class for all generated Peer classes in the system.
  11. *
  12. * Peer classes are responsible for isolating all of the database access
  13. * for a specific business object. They execute all of the SQL
  14. * against the database. Over time this class has grown to include
  15. * utility methods which ease execution of cross-database queries and
  16. * the implementation of concrete Peers.
  17. *
  18. * @author Hans Lellelid <hans@xmpl.org> (Propel)
  19. * @author Kaspars Jaudzems <kaspars.jaudzems@inbox.lv> (Propel)
  20. * @author Heltem <heltem@o2php.com> (Propel)
  21. * @author Frank Y. Kim <frank.kim@clearink.com> (Torque)
  22. * @author John D. McNally <jmcnally@collab.net> (Torque)
  23. * @author Brett McLaughlin <bmclaugh@algx.net> (Torque)
  24. * @author Stephen Haberman <stephenh@chase3000.com> (Torque)
  25. * @version $Revision: 1772 $
  26. * @package propel.runtime.util
  27. */
  28. class BasePeer
  29. {
  30. /** Array (hash) that contains the cached mapBuilders. */
  31. private static $mapBuilders = array();
  32. /** Array (hash) that contains cached validators */
  33. private static $validatorMap = array();
  34. /**
  35. * phpname type
  36. * e.g. 'AuthorId'
  37. */
  38. const TYPE_PHPNAME = 'phpName';
  39. /**
  40. * studlyphpname type
  41. * e.g. 'authorId'
  42. */
  43. const TYPE_STUDLYPHPNAME = 'studlyPhpName';
  44. /**
  45. * column (peer) name type
  46. * e.g. 'book.AUTHOR_ID'
  47. */
  48. const TYPE_COLNAME = 'colName';
  49. /**
  50. * column part of the column peer name
  51. * e.g. 'AUTHOR_ID'
  52. */
  53. const TYPE_RAW_COLNAME = 'rawColName';
  54. /**
  55. * column fieldname type
  56. * e.g. 'author_id'
  57. */
  58. const TYPE_FIELDNAME = 'fieldName';
  59. /**
  60. * num type
  61. * simply the numerical array index, e.g. 4
  62. */
  63. const TYPE_NUM = 'num';
  64. static public function getFieldnames ($classname, $type = self::TYPE_PHPNAME) {
  65. // TODO we should take care of including the peer class here
  66. $peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true?
  67. $callable = array($peerclass, 'getFieldnames');
  68. return call_user_func($callable, $type);
  69. }
  70. static public function translateFieldname($classname, $fieldname, $fromType, $toType) {
  71. // TODO we should take care of including the peer class here
  72. $peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true?
  73. $callable = array($peerclass, 'translateFieldname');
  74. $args = array($fieldname, $fromType, $toType);
  75. return call_user_func_array($callable, $args);
  76. }
  77. /**
  78. * Method to perform deletes based on values and keys in a
  79. * Criteria.
  80. *
  81. * @param Criteria $criteria The criteria to use.
  82. * @param PropelPDO $con A PropelPDO connection object.
  83. * @return int The number of rows affected by last statement execution. For most
  84. * uses there is only one delete statement executed, so this number
  85. * will correspond to the number of rows affected by the call to this
  86. * method. Note that the return value does require that this information
  87. * is returned (supported) by the PDO driver.
  88. * @throws PropelException
  89. */
  90. public static function doDelete(Criteria $criteria, PropelPDO $con)
  91. {
  92. $db = Propel::getDB($criteria->getDbName());
  93. $dbMap = Propel::getDatabaseMap($criteria->getDbName());
  94. // Set up a list of required tables (one DELETE statement will
  95. // be executed per table)
  96. $tables = $criteria->getTablesColumns();
  97. if (empty($tables)) {
  98. throw new PropelException("Cannot delete from an empty Criteria");
  99. }
  100. $affectedRows = 0; // initialize this in case the next loop has no iterations.
  101. foreach ($tables as $tableName => $columns) {
  102. $whereClause = array();
  103. $params = array();
  104. $stmt = null;
  105. try {
  106. $sql = 'DELETE ';
  107. if ($queryComment = $criteria->getComment()) {
  108. $sql .= '/* ' . $queryComment . ' */ ';
  109. }
  110. if ($realTableName = $criteria->getTableForAlias($tableName)) {
  111. if ($db->useQuoteIdentifier()) {
  112. $realTableName = $db->quoteIdentifierTable($realTableName);
  113. }
  114. $sql .= $tableName . ' FROM ' . $realTableName . ' AS ' . $tableName;
  115. } else {
  116. if ($db->useQuoteIdentifier()) {
  117. $tableName = $db->quoteIdentifierTable($tableName);
  118. }
  119. $sql .= 'FROM ' . $tableName;
  120. }
  121. foreach ($columns as $colName) {
  122. $sb = "";
  123. $criteria->getCriterion($colName)->appendPsTo($sb, $params);
  124. $whereClause[] = $sb;
  125. }
  126. $sql .= " WHERE " . implode(" AND ", $whereClause);
  127. $stmt = $con->prepare($sql);
  128. self::populateStmtValues($stmt, $params, $dbMap, $db);
  129. $stmt->execute();
  130. $affectedRows = $stmt->rowCount();
  131. } catch (Exception $e) {
  132. Propel::log($e->getMessage(), Propel::LOG_ERR);
  133. throw new PropelException(sprintf('Unable to execute DELETE statement [%s]', $sql), $e);
  134. }
  135. } // for each table
  136. return $affectedRows;
  137. }
  138. /**
  139. * Method to deletes all contents of specified table.
  140. *
  141. * This method is invoked from generated Peer classes like this:
  142. * <code>
  143. * public static function doDeleteAll($con = null)
  144. * {
  145. * if ($con === null) $con = Propel::getConnection(self::DATABASE_NAME);
  146. * BasePeer::doDeleteAll(self::TABLE_NAME, $con, self::DATABASE_NAME);
  147. * }
  148. * </code>
  149. *
  150. * @param string $tableName The name of the table to empty.
  151. * @param PropelPDO $con A PropelPDO connection object.
  152. * @param string $databaseName the name of the database.
  153. * @return int The number of rows affected by the statement. Note
  154. * that the return value does require that this information
  155. * is returned (supported) by the Propel db driver.
  156. * @throws PropelException - wrapping SQLException caught from statement execution.
  157. */
  158. public static function doDeleteAll($tableName, PropelPDO $con, $databaseName = null)
  159. {
  160. try {
  161. $db = Propel::getDB($databaseName);
  162. if ($db->useQuoteIdentifier()) {
  163. $tableName = $db->quoteIdentifierTable($tableName);
  164. }
  165. $sql = "DELETE FROM " . $tableName;
  166. $stmt = $con->prepare($sql);
  167. $stmt->execute();
  168. return $stmt->rowCount();
  169. } catch (Exception $e) {
  170. Propel::log($e->getMessage(), Propel::LOG_ERR);
  171. throw new PropelException(sprintf('Unable to execute DELETE ALL statement [%s]', $sql), $e);
  172. }
  173. }
  174. /**
  175. * Method to perform inserts based on values and keys in a
  176. * Criteria.
  177. * <p>
  178. * If the primary key is auto incremented the data in Criteria
  179. * will be inserted and the auto increment value will be returned.
  180. * <p>
  181. * If the primary key is included in Criteria then that value will
  182. * be used to insert the row.
  183. * <p>
  184. * If no primary key is included in Criteria then we will try to
  185. * figure out the primary key from the database map and insert the
  186. * row with the next available id using util.db.IDBroker.
  187. * <p>
  188. * If no primary key is defined for the table the values will be
  189. * inserted as specified in Criteria and null will be returned.
  190. *
  191. * @param Criteria $criteria Object containing values to insert.
  192. * @param PropelPDO $con A PropelPDO connection.
  193. * @return mixed The primary key for the new row if (and only if!) the primary key
  194. * is auto-generated. Otherwise will return <code>null</code>.
  195. * @throws PropelException
  196. */
  197. public static function doInsert(Criteria $criteria, PropelPDO $con) {
  198. // the primary key
  199. $id = null;
  200. $db = Propel::getDB($criteria->getDbName());
  201. // Get the table name and method for determining the primary
  202. // key value.
  203. $keys = $criteria->keys();
  204. if (!empty($keys)) {
  205. $tableName = $criteria->getTableName( $keys[0] );
  206. } else {
  207. throw new PropelException("Database insert attempted without anything specified to insert");
  208. }
  209. $dbMap = Propel::getDatabaseMap($criteria->getDbName());
  210. $tableMap = $dbMap->getTable($tableName);
  211. $keyInfo = $tableMap->getPrimaryKeyMethodInfo();
  212. $useIdGen = $tableMap->isUseIdGenerator();
  213. //$keyGen = $con->getIdGenerator();
  214. $pk = self::getPrimaryKey($criteria);
  215. // only get a new key value if you need to
  216. // the reason is that a primary key might be defined
  217. // but you are still going to set its value. for example:
  218. // a join table where both keys are primary and you are
  219. // setting both columns with your own values
  220. // pk will be null if there is no primary key defined for the table
  221. // we're inserting into.
  222. if ($pk !== null && $useIdGen && !$criteria->keyContainsValue($pk->getFullyQualifiedName()) && $db->isGetIdBeforeInsert()) {
  223. try {
  224. $id = $db->getId($con, $keyInfo);
  225. } catch (Exception $e) {
  226. throw new PropelException("Unable to get sequence id.", $e);
  227. }
  228. $criteria->add($pk->getFullyQualifiedName(), $id);
  229. }
  230. try {
  231. $adapter = Propel::getDB($criteria->getDBName());
  232. $qualifiedCols = $criteria->keys(); // we need table.column cols when populating values
  233. $columns = array(); // but just 'column' cols for the SQL
  234. foreach ($qualifiedCols as $qualifiedCol) {
  235. $columns[] = substr($qualifiedCol, strrpos($qualifiedCol, '.') + 1);
  236. }
  237. // add identifiers
  238. if ($adapter->useQuoteIdentifier()) {
  239. $columns = array_map(array($adapter, 'quoteIdentifier'), $columns);
  240. $tableName = $adapter->quoteIdentifierTable($tableName);
  241. }
  242. $sql = 'INSERT INTO ' . $tableName
  243. . ' (' . implode(',', $columns) . ')'
  244. . ' VALUES (';
  245. // . substr(str_repeat("?,", count($columns)), 0, -1) .
  246. for($p=1, $cnt=count($columns); $p <= $cnt; $p++) {
  247. $sql .= ':p'.$p;
  248. if ($p !== $cnt) $sql .= ',';
  249. }
  250. $sql .= ')';
  251. $stmt = $con->prepare($sql);
  252. self::populateStmtValues($stmt, self::buildParams($qualifiedCols, $criteria), $dbMap, $db);
  253. $stmt->execute();
  254. } catch (Exception $e) {
  255. Propel::log($e->getMessage(), Propel::LOG_ERR);
  256. throw new PropelException(sprintf('Unable to execute INSERT statement [%s]', $sql), $e);
  257. }
  258. // If the primary key column is auto-incremented, get the id now.
  259. if ($pk !== null && $useIdGen && $db->isGetIdAfterInsert()) {
  260. try {
  261. $id = $db->getId($con, $keyInfo);
  262. } catch (Exception $e) {
  263. throw new PropelException("Unable to get autoincrement id.", $e);
  264. }
  265. }
  266. return $id;
  267. }
  268. /**
  269. * Method used to update rows in the DB. Rows are selected based
  270. * on selectCriteria and updated using values in updateValues.
  271. * <p>
  272. * Use this method for performing an update of the kind:
  273. * <p>
  274. * WHERE some_column = some value AND could_have_another_column =
  275. * another value AND so on.
  276. *
  277. * @param $selectCriteria A Criteria object containing values used in where
  278. * clause.
  279. * @param $updateValues A Criteria object containing values used in set
  280. * clause.
  281. * @param PropelPDO $con The PropelPDO connection object to use.
  282. * @return int The number of rows affected by last update statement. For most
  283. * uses there is only one update statement executed, so this number
  284. * will correspond to the number of rows affected by the call to this
  285. * method. Note that the return value does require that this information
  286. * is returned (supported) by the Propel db driver.
  287. * @throws PropelException
  288. */
  289. public static function doUpdate(Criteria $selectCriteria, Criteria $updateValues, PropelPDO $con) {
  290. $db = Propel::getDB($selectCriteria->getDbName());
  291. $dbMap = Propel::getDatabaseMap($selectCriteria->getDbName());
  292. // Get list of required tables, containing all columns
  293. $tablesColumns = $selectCriteria->getTablesColumns();
  294. if (empty($tablesColumns)) {
  295. $tablesColumns = array($selectCriteria->getPrimaryTableName() => array());
  296. }
  297. // we also need the columns for the update SQL
  298. $updateTablesColumns = $updateValues->getTablesColumns();
  299. $affectedRows = 0; // initialize this in case the next loop has no iterations.
  300. foreach ($tablesColumns as $tableName => $columns) {
  301. $whereClause = array();
  302. $params = array();
  303. $stmt = null;
  304. try {
  305. $sql = 'UPDATE ';
  306. if ($queryComment = $selectCriteria->getComment()) {
  307. $sql .= '/* ' . $queryComment . ' */ ';
  308. }
  309. // is it a table alias?
  310. if ($tableName2 = $selectCriteria->getTableForAlias($tableName)) {
  311. $udpateTable = $tableName2 . ' ' . $tableName;
  312. $tableName = $tableName2;
  313. } else {
  314. $udpateTable = $tableName;
  315. }
  316. if ($db->useQuoteIdentifier()) {
  317. $sql .= $db->quoteIdentifierTable($udpateTable);
  318. } else {
  319. $sql .= $udpateTable;
  320. }
  321. $sql .= " SET ";
  322. $p = 1;
  323. foreach ($updateTablesColumns[$tableName] as $col) {
  324. $updateColumnName = substr($col, strrpos($col, '.') + 1);
  325. // add identifiers for the actual database?
  326. if ($db->useQuoteIdentifier()) {
  327. $updateColumnName = $db->quoteIdentifier($updateColumnName);
  328. }
  329. if ($updateValues->getComparison($col) != Criteria::CUSTOM_EQUAL) {
  330. $sql .= $updateColumnName . '=:p'.$p++.', ';
  331. } else {
  332. $param = $updateValues->get($col);
  333. $sql .= $updateColumnName . ' = ';
  334. if (is_array($param)) {
  335. if (isset($param['raw'])) {
  336. $raw = $param['raw'];
  337. $rawcvt = '';
  338. // parse the $params['raw'] for ? chars
  339. for($r=0,$len=strlen($raw); $r < $len; $r++) {
  340. if ($raw{$r} == '?') {
  341. $rawcvt .= ':p'.$p++;
  342. } else {
  343. $rawcvt .= $raw{$r};
  344. }
  345. }
  346. $sql .= $rawcvt . ', ';
  347. } else {
  348. $sql .= ':p'.$p++.', ';
  349. }
  350. if (isset($param['value'])) {
  351. $updateValues->put($col, $param['value']);
  352. }
  353. } else {
  354. $updateValues->remove($col);
  355. $sql .= $param . ', ';
  356. }
  357. }
  358. }
  359. $params = self::buildParams($updateTablesColumns[$tableName], $updateValues);
  360. $sql = substr($sql, 0, -2);
  361. if (!empty($columns)) {
  362. foreach ($columns as $colName) {
  363. $sb = "";
  364. $selectCriteria->getCriterion($colName)->appendPsTo($sb, $params);
  365. $whereClause[] = $sb;
  366. }
  367. $sql .= " WHERE " . implode(" AND ", $whereClause);
  368. }
  369. $stmt = $con->prepare($sql);
  370. // Replace ':p?' with the actual values
  371. self::populateStmtValues($stmt, $params, $dbMap, $db);
  372. $stmt->execute();
  373. $affectedRows = $stmt->rowCount();
  374. $stmt = null; // close
  375. } catch (Exception $e) {
  376. if ($stmt) $stmt = null; // close
  377. Propel::log($e->getMessage(), Propel::LOG_ERR);
  378. throw new PropelException(sprintf('Unable to execute UPDATE statement [%s]', $sql), $e);
  379. }
  380. } // foreach table in the criteria
  381. return $affectedRows;
  382. }
  383. /**
  384. * Executes query build by createSelectSql() and returns the resultset statement.
  385. *
  386. * @param Criteria $criteria A Criteria.
  387. * @param PropelPDO $con A PropelPDO connection to use.
  388. * @return PDOStatement The resultset.
  389. * @throws PropelException
  390. * @see createSelectSql()
  391. */
  392. public static function doSelect(Criteria $criteria, PropelPDO $con = null)
  393. {
  394. $dbMap = Propel::getDatabaseMap($criteria->getDbName());
  395. $db = Propel::getDB($criteria->getDbName());
  396. $stmt = null;
  397. if ($con === null) {
  398. $con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ);
  399. }
  400. if ($criteria->isUseTransaction()) {
  401. $con->beginTransaction();
  402. }
  403. try {
  404. $params = array();
  405. $sql = self::createSelectSql($criteria, $params);
  406. $stmt = $con->prepare($sql);
  407. self::populateStmtValues($stmt, $params, $dbMap, $db);
  408. $stmt->execute();
  409. if ($criteria->isUseTransaction()) {
  410. $con->commit();
  411. }
  412. } catch (Exception $e) {
  413. if ($stmt) {
  414. $stmt = null; // close
  415. }
  416. if ($criteria->isUseTransaction()) {
  417. $con->rollBack();
  418. }
  419. Propel::log($e->getMessage(), Propel::LOG_ERR);
  420. throw new PropelException(sprintf('Unable to execute SELECT statement [%s]', $sql), $e);
  421. }
  422. return $stmt;
  423. }
  424. /**
  425. * Executes a COUNT query using either a simple SQL rewrite or, for more complex queries, a
  426. * sub-select of the SQL created by createSelectSql() and returns the statement.
  427. *
  428. * @param Criteria $criteria A Criteria.
  429. * @param PropelPDO $con A PropelPDO connection to use.
  430. * @return PDOStatement The resultset statement.
  431. * @throws PropelException
  432. * @see createSelectSql()
  433. */
  434. public static function doCount(Criteria $criteria, PropelPDO $con = null)
  435. {
  436. $dbMap = Propel::getDatabaseMap($criteria->getDbName());
  437. $db = Propel::getDB($criteria->getDbName());
  438. if ($con === null) {
  439. $con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ);
  440. }
  441. $stmt = null;
  442. if ($criteria->isUseTransaction()) {
  443. $con->beginTransaction();
  444. }
  445. $needsComplexCount = $criteria->getGroupByColumns()
  446. || $criteria->getOffset()
  447. || $criteria->getLimit()
  448. || $criteria->getHaving()
  449. || in_array(Criteria::DISTINCT, $criteria->getSelectModifiers());
  450. try {
  451. $params = array();
  452. if ($needsComplexCount) {
  453. if (self::needsSelectAliases($criteria)) {
  454. if ($criteria->getHaving()) {
  455. throw new PropelException('Propel cannot create a COUNT query when using HAVING and duplicate column names in the SELECT part');
  456. }
  457. self::turnSelectColumnsToAliases($criteria);
  458. }
  459. $selectSql = self::createSelectSql($criteria, $params);
  460. $sql = 'SELECT COUNT(*) FROM (' . $selectSql . ') propelmatch4cnt';
  461. } else {
  462. // Replace SELECT columns with COUNT(*)
  463. $criteria->clearSelectColumns()->addSelectColumn('COUNT(*)');
  464. $sql = self::createSelectSql($criteria, $params);
  465. }
  466. $stmt = $con->prepare($sql);
  467. self::populateStmtValues($stmt, $params, $dbMap, $db);
  468. $stmt->execute();
  469. if ($criteria->isUseTransaction()) {
  470. $con->commit();
  471. }
  472. } catch (Exception $e) {
  473. if ($stmt !== null) {
  474. $stmt = null;
  475. }
  476. if ($criteria->isUseTransaction()) {
  477. $con->rollBack();
  478. }
  479. Propel::log($e->getMessage(), Propel::LOG_ERR);
  480. throw new PropelException(sprintf('Unable to execute COUNT statement [%s]', $sql), $e);
  481. }
  482. return $stmt;
  483. }
  484. /**
  485. * Populates values in a prepared statement.
  486. *
  487. * This method is designed to work with the createSelectSql() method, which creates
  488. * both the SELECT SQL statement and populates a passed-in array of parameter
  489. * values that should be substituted.
  490. *
  491. * <code>
  492. * $params = array();
  493. * $sql = BasePeer::createSelectSql($criteria, $params);
  494. * BasePeer::populateStmtValues($stmt, $params, Propel::getDatabaseMap($critera->getDbName()), Propel::getDB($criteria->getDbName()));
  495. * </code>
  496. *
  497. * @param PDOStatement $stmt
  498. * @param array $params array('column' => ..., 'table' => ..., 'value' => ...)
  499. * @param DatabaseMap $dbMap
  500. * @return int The number of params replaced.
  501. * @see createSelectSql()
  502. * @see doSelect()
  503. */
  504. public static function populateStmtValues(PDOStatement $stmt, array $params, DatabaseMap $dbMap, DBAdapter $db)
  505. {
  506. $i = 1;
  507. foreach ($params as $param) {
  508. $tableName = $param['table'];
  509. $columnName = $param['column'];
  510. $value = $param['value'];
  511. if (null === $value) {
  512. $stmt->bindValue(':p'.$i++, null, PDO::PARAM_NULL);
  513. } elseif (null !== $tableName) {
  514. $cMap = $dbMap->getTable($tableName)->getColumn($columnName);
  515. $type = $cMap->getType();
  516. $pdoType = $cMap->getPdoType();
  517. // FIXME - This is a temporary hack to get around apparent bugs w/ PDO+MYSQL
  518. // See http://pecl.php.net/bugs/bug.php?id=9919
  519. if ($pdoType == PDO::PARAM_BOOL && $db instanceof DBMySQL) {
  520. $value = (int) $value;
  521. $pdoType = PDO::PARAM_INT;
  522. } elseif (is_numeric($value) && $cMap->isEpochTemporal()) { // it's a timestamp that needs to be formatted
  523. if ($type == PropelColumnTypes::TIMESTAMP) {
  524. $value = date($db->getTimestampFormatter(), $value);
  525. } else if ($type == PropelColumnTypes::DATE) {
  526. $value = date($db->getDateFormatter(), $value);
  527. } else if ($type == PropelColumnTypes::TIME) {
  528. $value = date($db->getTimeFormatter(), $value);
  529. }
  530. } elseif ($value instanceof DateTime && $cMap->isTemporal()) { // it's a timestamp that needs to be formatted
  531. if ($type == PropelColumnTypes::TIMESTAMP || $type == PropelColumnTypes::BU_TIMESTAMP) {
  532. $value = $value->format($db->getTimestampFormatter());
  533. } else if ($type == PropelColumnTypes::DATE || $type == PropelColumnTypes::BU_DATE) {
  534. $value = $value->format($db->getDateFormatter());
  535. } else if ($type == PropelColumnTypes::TIME) {
  536. $value = $value->format($db->getTimeFormatter());
  537. }
  538. } elseif (is_resource($value) && $cMap->isLob()) {
  539. // we always need to make sure that the stream is rewound, otherwise nothing will
  540. // get written to database.
  541. rewind($value);
  542. }
  543. $stmt->bindValue(':p'.$i++, $value, $pdoType);
  544. } else {
  545. $stmt->bindValue(':p'.$i++, $value);
  546. }
  547. } // foreach
  548. }
  549. /**
  550. * Applies any validators that were defined in the schema to the specified columns.
  551. *
  552. * @param string $dbName The name of the database
  553. * @param string $tableName The name of the table
  554. * @param array $columns Array of column names as key and column values as value.
  555. */
  556. public static function doValidate($dbName, $tableName, $columns)
  557. {
  558. $dbMap = Propel::getDatabaseMap($dbName);
  559. $tableMap = $dbMap->getTable($tableName);
  560. $failureMap = array(); // map of ValidationFailed objects
  561. foreach ($columns as $colName => $colValue) {
  562. if ($tableMap->containsColumn($colName)) {
  563. $col = $tableMap->getColumn($colName);
  564. foreach ($col->getValidators() as $validatorMap) {
  565. $validator = BasePeer::getValidator($validatorMap->getClass());
  566. if ($validator && ($col->isNotNull() || $colValue !== null) && $validator->isValid($validatorMap, $colValue) === false) {
  567. if (!isset($failureMap[$colName])) { // for now we do one ValidationFailed per column, not per rule
  568. $failureMap[$colName] = new ValidationFailed($colName, $validatorMap->getMessage(), $validator);
  569. }
  570. }
  571. }
  572. }
  573. }
  574. return (!empty($failureMap) ? $failureMap : true);
  575. }
  576. /**
  577. * Helper method which returns the primary key contained
  578. * in the given Criteria object.
  579. *
  580. * @param Criteria $criteria A Criteria.
  581. * @return ColumnMap If the Criteria object contains a primary
  582. * key, or null if it doesn't.
  583. * @throws PropelException
  584. */
  585. private static function getPrimaryKey(Criteria $criteria)
  586. {
  587. // Assume all the keys are for the same table.
  588. $keys = $criteria->keys();
  589. $key = $keys[0];
  590. $table = $criteria->getTableName($key);
  591. $pk = null;
  592. if (!empty($table)) {
  593. $dbMap = Propel::getDatabaseMap($criteria->getDbName());
  594. $pks = $dbMap->getTable($table)->getPrimaryKeys();
  595. if (!empty($pks)) {
  596. $pk = array_shift($pks);
  597. }
  598. }
  599. return $pk;
  600. }
  601. /**
  602. * Checks whether the Criteria needs to use column aliasing
  603. * This is implemented in a service class rather than in Criteria itself
  604. * in order to avoid doing the tests when it's not necessary (e.g. for SELECTs)
  605. */
  606. public static function needsSelectAliases(Criteria $criteria)
  607. {
  608. $columnNames = array();
  609. foreach ($criteria->getSelectColumns() as $fullyQualifiedColumnName) {
  610. if ($pos = strrpos($fullyQualifiedColumnName, '.')) {
  611. $columnName = substr($fullyQualifiedColumnName, $pos);
  612. if (isset($columnNames[$columnName])) {
  613. // more than one column with the same name, so aliasing is required
  614. return true;
  615. }
  616. $columnNames[$columnName] = true;
  617. }
  618. }
  619. return false;
  620. }
  621. /**
  622. * Ensures uniqueness of select column names by turning them all into aliases
  623. * This is necessary for queries on more than one table when the tables share a column name
  624. * @see http://propel.phpdb.org/trac/ticket/795
  625. *
  626. * @param Criteria $criteria
  627. *
  628. * @return Criteria The input, with Select columns replaced by aliases
  629. */
  630. public static function turnSelectColumnsToAliases(Criteria $criteria)
  631. {
  632. $selectColumns = $criteria->getSelectColumns();
  633. // clearSelectColumns also clears the aliases, so get them too
  634. $asColumns = $criteria->getAsColumns();
  635. $criteria->clearSelectColumns();
  636. $columnAliases = $asColumns;
  637. // add the select columns back
  638. foreach ($selectColumns as $clause) {
  639. // Generate a unique alias
  640. $baseAlias = preg_replace('/\W/', '_', $clause);
  641. $alias = $baseAlias;
  642. // If it already exists, add a unique suffix
  643. $i = 0;
  644. while (isset($columnAliases[$alias])) {
  645. $i++;
  646. $alias = $baseAlias . '_' . $i;
  647. }
  648. // Add it as an alias
  649. $criteria->addAsColumn($alias, $clause);
  650. $columnAliases[$alias] = $clause;
  651. }
  652. // Add the aliases back, don't modify them
  653. foreach ($asColumns as $name => $clause) {
  654. $criteria->addAsColumn($name, $clause);
  655. }
  656. return $criteria;
  657. }
  658. /**
  659. * Method to create an SQL query based on values in a Criteria.
  660. *
  661. * This method creates only prepared statement SQL (using ? where values
  662. * will go). The second parameter ($params) stores the values that need
  663. * to be set before the statement is executed. The reason we do it this way
  664. * is to let the PDO layer handle all escaping & value formatting.
  665. *
  666. * @param Criteria $criteria Criteria for the SELECT query.
  667. * @param array &$params Parameters that are to be replaced in prepared statement.
  668. * @return string
  669. * @throws PropelException Trouble creating the query string.
  670. */
  671. public static function createSelectSql(Criteria $criteria, &$params)
  672. {
  673. $db = Propel::getDB($criteria->getDbName());
  674. $dbMap = Propel::getDatabaseMap($criteria->getDbName());
  675. $fromClause = array();
  676. $joinClause = array();
  677. $joinTables = array();
  678. $whereClause = array();
  679. $orderByClause = array();
  680. $orderBy = $criteria->getOrderByColumns();
  681. $groupBy = $criteria->getGroupByColumns();
  682. $ignoreCase = $criteria->isIgnoreCase();
  683. // get the first part of the SQL statement, the SELECT part
  684. $selectSql = self::createSelectSqlPart($criteria, $fromClause);
  685. // add the criteria to WHERE clause
  686. // this will also add the table names to the FROM clause if they are not already
  687. // included via a LEFT JOIN
  688. foreach ($criteria->keys() as $key) {
  689. $criterion = $criteria->getCriterion($key);
  690. $table = null;
  691. foreach ($criterion->getAttachedCriterion() as $attachedCriterion) {
  692. $tableName = $attachedCriterion->getTable();
  693. $table = $criteria->getTableForAlias($tableName);
  694. if ($table !== null) {
  695. $fromClause[] = $table . ' ' . $tableName;
  696. } else {
  697. $fromClause[] = $tableName;
  698. $table = $tableName;
  699. }
  700. if (($criteria->isIgnoreCase() || $attachedCriterion->isIgnoreCase())
  701. && $dbMap->getTable($table)->getColumn($attachedCriterion->getColumn())->isText()) {
  702. $attachedCriterion->setIgnoreCase(true);
  703. }
  704. }
  705. $criterion->setDB($db);
  706. $sb = '';
  707. $criterion->appendPsTo($sb, $params);
  708. $whereClause[] = $sb;
  709. }
  710. // Handle joins
  711. // joins with a null join type will be added to the FROM clause and the condition added to the WHERE clause.
  712. // joins of a specified type: the LEFT side will be added to the fromClause and the RIGHT to the joinClause
  713. foreach ($criteria->getJoins() as $join) {
  714. // The join might have been established using an alias name
  715. $leftTable = $join->getLeftTableName();
  716. if ($realTable = $criteria->getTableForAlias($leftTable)) {
  717. $leftTableForFrom = $realTable . ' ' . $leftTable;
  718. $leftTable = $realTable;
  719. } else {
  720. $leftTableForFrom = $leftTable;
  721. }
  722. $rightTable = $join->getRightTableName();
  723. if ($realTable = $criteria->getTableForAlias($rightTable)) {
  724. $rightTableForFrom = $realTable . ' ' . $rightTable;
  725. $rightTable = $realTable;
  726. } else {
  727. $rightTableForFrom = $rightTable;
  728. }
  729. // determine if casing is relevant.
  730. if ($ignoreCase = $criteria->isIgnoreCase()) {
  731. $leftColType = $dbMap->getTable($leftTable)->getColumn($join->getLeftColumnName())->getType();
  732. $rightColType = $dbMap->getTable($rightTable)->getColumn($join->getRightColumnName())->getType();
  733. $ignoreCase = ($leftColType == 'string' || $rightColType == 'string');
  734. }
  735. // build the condition
  736. $condition = '';
  737. foreach ($join->getConditions() as $index => $conditionDesc) {
  738. if ($ignoreCase) {
  739. $condition .= $db->ignoreCase($conditionDesc['left']) . $conditionDesc['operator'] . $db->ignoreCase($conditionDesc['right']);
  740. } else {
  741. $condition .= implode($conditionDesc);
  742. }
  743. if ($index + 1 < $join->countConditions()) {
  744. $condition .= ' AND ';
  745. }
  746. }
  747. // add 'em to the queues..
  748. if ($joinType = $join->getJoinType()) {
  749. // real join
  750. if (!$fromClause) {
  751. $fromClause[] = $leftTableForFrom;
  752. }
  753. $joinTables[] = $rightTableForFrom;
  754. $joinClause[] = $join->getJoinType() . ' ' . $rightTableForFrom . " ON ($condition)";
  755. } else {
  756. // implicit join, translates to a where
  757. $fromClause[] = $leftTableForFrom;
  758. $fromClause[] = $rightTableForFrom;
  759. $whereClause[] = $condition;
  760. }
  761. }
  762. // Unique from clause elements
  763. $fromClause = array_unique($fromClause);
  764. $fromClause = array_diff($fromClause, array(''));
  765. // tables should not exist in both the from and join clauses
  766. if ($joinTables && $fromClause) {
  767. foreach ($fromClause as $fi => $ftable) {
  768. if (in_array($ftable, $joinTables)) {
  769. unset($fromClause[$fi]);
  770. }
  771. }
  772. }
  773. // Add the GROUP BY columns
  774. $groupByClause = $groupBy;
  775. $having = $criteria->getHaving();
  776. $havingString = null;
  777. if ($having !== null) {
  778. $sb = '';
  779. $having->appendPsTo($sb, $params);
  780. $havingString = $sb;
  781. }
  782. if (!empty($orderBy)) {
  783. foreach ($orderBy as $orderByColumn) {
  784. // Add function expression as-is.
  785. if (strpos($orderByColumn, '(') !== false) {
  786. $orderByClause[] = $orderByColumn;
  787. continue;
  788. }
  789. // Split orderByColumn (i.e. "table.column DESC")
  790. $dotPos = strrpos($orderByColumn, '.');
  791. if ($dotPos !== false) {
  792. $tableName = substr($orderByColumn, 0, $dotPos);
  793. $columnName = substr($orderByColumn, $dotPos + 1);
  794. } else {
  795. $tableName = '';
  796. $columnName = $orderByColumn;
  797. }
  798. $spacePos = strpos($columnName, ' ');
  799. if ($spacePos !== false) {
  800. $direction = substr($columnName, $spacePos);
  801. $columnName = substr($columnName, 0, $spacePos);
  802. } else {
  803. $direction = '';
  804. }
  805. $tableAlias = $tableName;
  806. if ($aliasTableName = $criteria->getTableForAlias($tableName)) {
  807. $tableName = $aliasTableName;
  808. }
  809. $columnAlias = $columnName;
  810. if ($asColumnName = $criteria->getColumnForAs($columnName)) {
  811. $columnName = $asColumnName;
  812. }
  813. $column = $tableName ? $dbMap->getTable($tableName)->getColumn($columnName) : null;
  814. if ($criteria->isIgnoreCase() && $column && $column->isText()) {
  815. $ignoreCaseColumn = $db->ignoreCaseInOrderBy("$tableAlias.$columnAlias");
  816. $orderByClause[] = $ignoreCaseColumn . $direction;
  817. $selectSql .= ', ' . $ignoreCaseColumn;
  818. } else {
  819. $orderByClause[] = $orderByColumn;
  820. }
  821. }
  822. }
  823. if (empty($fromClause) && $criteria->getPrimaryTableName()) {
  824. $fromClause[] = $criteria->getPrimaryTableName();
  825. }
  826. // from / join tables quoted if it is necessary
  827. if ($db->useQuoteIdentifier()) {
  828. $fromClause = array_map(array($db, 'quoteIdentifierTable'), $fromClause);
  829. $joinClause = $joinClause ? $joinClause : array_map(array($db, 'quoteIdentifierTable'), $joinClause);
  830. }
  831. // build from-clause
  832. $from = '';
  833. if (!empty($joinClause) && count($fromClause) > 1) {
  834. $from .= implode(" CROSS JOIN ", $fromClause);
  835. } else {
  836. $from .= implode(", ", $fromClause);
  837. }
  838. $from .= $joinClause ? ' ' . implode(' ', $joinClause) : '';
  839. // Build the SQL from the arrays we compiled
  840. $sql = $selectSql
  841. ." FROM " . $from
  842. .($whereClause ? " WHERE ".implode(" AND ", $whereClause) : "")
  843. .($groupByClause ? " GROUP BY ".implode(",", $groupByClause) : "")
  844. .($havingString ? " HAVING ".$havingString : "")
  845. .($orderByClause ? " ORDER BY ".implode(",", $orderByClause) : "");
  846. // APPLY OFFSET & LIMIT to the query.
  847. if ($criteria->getLimit() || $criteria->getOffset()) {
  848. $db->applyLimit($sql, $criteria->getOffset(), $criteria->getLimit(), $criteria);
  849. }
  850. return $sql;
  851. }
  852. /**
  853. * Builds the SELECT part of a SQL statement based on a Criteria
  854. * taking into account select columns and 'as' columns (i.e. columns aliases)
  855. */
  856. public static function createSelectSqlPart(Criteria $criteria, &$fromClause, $aliasAll = false)
  857. {
  858. $selectClause = array();
  859. if ($aliasAll) {
  860. self::turnSelectColumnsToAliases($criteria);
  861. // no select columns after that, they are all aliases
  862. } else {
  863. foreach ($criteria->getSelectColumns() as $columnName) {
  864. // expect every column to be of "table.column" formation
  865. // it could be a function: e.g. MAX(books.price)
  866. $tableName = null;
  867. $selectClause[] = $columnName; // the full column name: e.g. MAX(books.price)
  868. $parenPos = strrpos($columnName, '(');
  869. $dotPos = strrpos($columnName, '.', ($parenPos !== false ? $parenPos : 0));
  870. if ($dotPos !== false) {
  871. if ($parenPos === false) { // table.column
  872. $tableName = substr($columnName, 0, $dotPos);
  873. } else { // FUNC(table.column)
  874. // functions may contain qualifiers so only take the last
  875. // word as the table name.
  876. // COUNT(DISTINCT books.price)
  877. $lastSpace = strpos($tableName, ' ');
  878. if ($lastSpace !== false) { // COUNT(DISTINCT books.price)
  879. $tableName = substr($tableName, $lastSpace + 1);
  880. } else {
  881. $tableName = substr($columnName, $parenPos + 1, $dotPos - ($parenPos + 1));
  882. }
  883. }
  884. // is it a table alias?
  885. $tableName2 = $criteria->getTableForAlias($tableName);
  886. if ($tableName2 !== null) {
  887. $fromClause[] = $tableName2 . ' ' . $tableName;
  888. } else {
  889. $fromClause[] = $tableName;
  890. }
  891. } // if $dotPost !== false
  892. }
  893. }
  894. // set the aliases
  895. foreach ($criteria->getAsColumns() as $alias => $col) {
  896. $selectClause[] = $col . ' AS ' . $alias;
  897. }
  898. $selectModifiers = $criteria->getSelectModifiers();
  899. $queryComment = $criteria->getComment();
  900. // Build the SQL from the arrays we compiled
  901. $sql = "SELECT "
  902. . ($queryComment ? '/* ' . $queryComment . ' */ ' : '')
  903. . ($selectModifiers ? (implode(' ', $selectModifiers) . ' ') : '')
  904. . implode(", ", $selectClause);
  905. return $sql;
  906. }
  907. /**
  908. * Builds a params array, like the kind populated by Criterion::appendPsTo().
  909. * This is useful for building an array even when it is not using the appendPsTo() method.
  910. * @param array $columns
  911. * @param Criteria $values
  912. * @return array params array('column' => ..., 'table' => ..., 'value' => ...)
  913. */
  914. private static function buildParams($columns, Criteria $values)
  915. {
  916. $params = array();
  917. foreach ($columns as $key) {
  918. if ($values->containsKey($key)) {
  919. $crit = $values->getCriterion($key);
  920. $params[] = array('column' => $crit->getColumn(), 'table' => $crit->getTable(), 'value' => $crit->getValue());
  921. }
  922. }
  923. return $params;
  924. }
  925. /**
  926. * This function searches for the given validator $name under propel/validator/$name.php,
  927. * imports and caches it.
  928. *
  929. * @param string $classname The dot-path name of class (e.g. myapp.propel.MyValidator)
  930. * @return Validator object or null if not able to instantiate validator class (and error will be logged in this case)
  931. */
  932. public static function getValidator($classname)
  933. {
  934. try {
  935. $v = isset(self::$validatorMap[$classname]) ? self::$validatorMap[$classname] : null;
  936. if ($v === null) {
  937. $cls = Propel::importClass($classname);
  938. $v = new $cls();
  939. self::$validatorMap[$classname] = $v;
  940. }
  941. return $v;
  942. } catch (Exception $e) {
  943. Propel::log("BasePeer::getValidator(): failed trying to instantiate " . $classname . ": ".$e->getMessage(), Propel::LOG_ERR);
  944. }
  945. }
  946. }