PageRenderTime 35ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/doctrine/lib/Doctrine/ORM/Query/SqlWalker.php

https://github.com/casoetan/ServerGroveLiveChat
PHP | 1882 lines | 1140 code | 285 blank | 457 comment | 252 complexity | d2b00888246e968c2cd133bd4bebc7f7 MD5 | raw file
Possible License(s): LGPL-2.1, LGPL-3.0, ISC, BSD-3-Clause
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the LGPL. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\ORM\Query;
  20. use Doctrine\DBAL\LockMode,
  21. Doctrine\ORM\Mapping\ClassMetadata,
  22. Doctrine\ORM\Query,
  23. Doctrine\ORM\Query\QueryException;
  24. /**
  25. * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
  26. * the corresponding SQL.
  27. *
  28. * @author Roman Borschel <roman@code-factory.org>
  29. * @author Benjamin Eberlei <kontakt@beberlei.de>
  30. * @since 2.0
  31. * @todo Rename: SQLWalker
  32. */
  33. class SqlWalker implements TreeWalker
  34. {
  35. /**
  36. * @var ResultSetMapping
  37. */
  38. private $_rsm;
  39. /** Counters for generating unique column aliases, table aliases and parameter indexes. */
  40. private $_aliasCounter = 0;
  41. private $_tableAliasCounter = 0;
  42. private $_scalarResultCounter = 1;
  43. private $_sqlParamIndex = 1;
  44. /**
  45. * @var ParserResult
  46. */
  47. private $_parserResult;
  48. /**
  49. * @var EntityManager
  50. */
  51. private $_em;
  52. /**
  53. * @var Doctrine\DBAL\Connection
  54. */
  55. private $_conn;
  56. /**
  57. * @var AbstractQuery
  58. */
  59. private $_query;
  60. private $_tableAliasMap = array();
  61. /** Map from result variable names to their SQL column alias names. */
  62. private $_scalarResultAliasMap = array();
  63. /** Map of all components/classes that appear in the DQL query. */
  64. private $_queryComponents;
  65. /** A list of classes that appear in non-scalar SelectExpressions. */
  66. private $_selectedClasses = array();
  67. /**
  68. * The DQL alias of the root class of the currently traversed query.
  69. */
  70. private $_rootAliases = array();
  71. /**
  72. * Flag that indicates whether to generate SQL table aliases in the SQL.
  73. * These should only be generated for SELECT queries, not for UPDATE/DELETE.
  74. */
  75. private $_useSqlTableAliases = true;
  76. /**
  77. * The database platform abstraction.
  78. *
  79. * @var AbstractPlatform
  80. */
  81. private $_platform;
  82. /**
  83. * {@inheritDoc}
  84. */
  85. public function __construct($query, $parserResult, array $queryComponents)
  86. {
  87. $this->_query = $query;
  88. $this->_parserResult = $parserResult;
  89. $this->_queryComponents = $queryComponents;
  90. $this->_rsm = $parserResult->getResultSetMapping();
  91. $this->_em = $query->getEntityManager();
  92. $this->_conn = $this->_em->getConnection();
  93. $this->_platform = $this->_conn->getDatabasePlatform();
  94. }
  95. /**
  96. * Gets the Query instance used by the walker.
  97. *
  98. * @return Query.
  99. */
  100. public function getQuery()
  101. {
  102. return $this->_query;
  103. }
  104. /**
  105. * Gets the Connection used by the walker.
  106. *
  107. * @return Connection
  108. */
  109. public function getConnection()
  110. {
  111. return $this->_conn;
  112. }
  113. /**
  114. * Gets the EntityManager used by the walker.
  115. *
  116. * @return EntityManager
  117. */
  118. public function getEntityManager()
  119. {
  120. return $this->_em;
  121. }
  122. /**
  123. * Gets the information about a single query component.
  124. *
  125. * @param string $dqlAlias The DQL alias.
  126. * @return array
  127. */
  128. public function getQueryComponent($dqlAlias)
  129. {
  130. return $this->_queryComponents[$dqlAlias];
  131. }
  132. /**
  133. * Gets an executor that can be used to execute the result of this walker.
  134. *
  135. * @return AbstractExecutor
  136. */
  137. public function getExecutor($AST)
  138. {
  139. $isDeleteStatement = $AST instanceof AST\DeleteStatement;
  140. $isUpdateStatement = $AST instanceof AST\UpdateStatement;
  141. if ($isDeleteStatement) {
  142. $primaryClass = $this->_em->getClassMetadata(
  143. $AST->deleteClause->abstractSchemaName
  144. );
  145. if ($primaryClass->isInheritanceTypeJoined()) {
  146. return new Exec\MultiTableDeleteExecutor($AST, $this);
  147. } else {
  148. return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
  149. }
  150. } else if ($isUpdateStatement) {
  151. $primaryClass = $this->_em->getClassMetadata(
  152. $AST->updateClause->abstractSchemaName
  153. );
  154. if ($primaryClass->isInheritanceTypeJoined()) {
  155. return new Exec\MultiTableUpdateExecutor($AST, $this);
  156. } else {
  157. return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
  158. }
  159. }
  160. return new Exec\SingleSelectExecutor($AST, $this);
  161. }
  162. /**
  163. * Generates a unique, short SQL table alias.
  164. *
  165. * @param string $tableName Table name
  166. * @param string $dqlAlias The DQL alias.
  167. * @return string Generated table alias.
  168. */
  169. public function getSqlTableAlias($tableName, $dqlAlias = '')
  170. {
  171. $tableName .= $dqlAlias;
  172. if ( ! isset($this->_tableAliasMap[$tableName])) {
  173. $this->_tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_';
  174. }
  175. return $this->_tableAliasMap[$tableName];
  176. }
  177. /**
  178. * Forces the SqlWalker to use a specific alias for a table name, rather than
  179. * generating an alias on its own.
  180. *
  181. * @param string $tableName
  182. * @param string $alias
  183. * @param string $dqlAlias
  184. * @return string
  185. */
  186. public function setSqlTableAlias($tableName, $alias, $dqlAlias = '')
  187. {
  188. $tableName .= $dqlAlias;
  189. $this->_tableAliasMap[$tableName] = $alias;
  190. return $alias;
  191. }
  192. /**
  193. * Gets an SQL column alias for a column name.
  194. *
  195. * @param string $columnName
  196. * @return string
  197. */
  198. public function getSqlColumnAlias($columnName)
  199. {
  200. return $columnName . $this->_aliasCounter++;
  201. }
  202. /**
  203. * Generates the SQL JOINs that are necessary for Class Table Inheritance
  204. * for the given class.
  205. *
  206. * @param ClassMetadata $class The class for which to generate the joins.
  207. * @param string $dqlAlias The DQL alias of the class.
  208. * @return string The SQL.
  209. */
  210. private function _generateClassTableInheritanceJoins($class, $dqlAlias)
  211. {
  212. $sql = '';
  213. $baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
  214. // INNER JOIN parent class tables
  215. foreach ($class->parentClasses as $parentClassName) {
  216. $parentClass = $this->_em->getClassMetadata($parentClassName);
  217. $tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias);
  218. // If this is a joined association we must use left joins to preserve the correct result.
  219. $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
  220. $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform)
  221. . ' ' . $tableAlias . ' ON ';
  222. $first = true;
  223. foreach ($class->identifier as $idField) {
  224. if ($first) $first = false; else $sql .= ' AND ';
  225. $columnName = $class->getQuotedColumnName($idField, $this->_platform);
  226. $sql .= $baseTableAlias . '.' . $columnName
  227. . ' = '
  228. . $tableAlias . '.' . $columnName;
  229. }
  230. }
  231. // LEFT JOIN subclass tables, if partial objects disallowed.
  232. if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
  233. foreach ($class->subClasses as $subClassName) {
  234. $subClass = $this->_em->getClassMetadata($subClassName);
  235. $tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias);
  236. $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform)
  237. . ' ' . $tableAlias . ' ON ';
  238. $first = true;
  239. foreach ($class->identifier as $idField) {
  240. if ($first) $first = false; else $sql .= ' AND ';
  241. $columnName = $class->getQuotedColumnName($idField, $this->_platform);
  242. $sql .= $baseTableAlias . '.' . $columnName
  243. . ' = '
  244. . $tableAlias . '.' . $columnName;
  245. }
  246. }
  247. }
  248. return $sql;
  249. }
  250. private function _generateOrderedCollectionOrderByItems()
  251. {
  252. $sql = '';
  253. foreach ($this->_selectedClasses AS $dqlAlias => $class) {
  254. $qComp = $this->_queryComponents[$dqlAlias];
  255. if (isset($qComp['relation']['orderBy'])) {
  256. foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) {
  257. if ($qComp['metadata']->isInheritanceTypeJoined()) {
  258. $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
  259. } else {
  260. $tableName = $qComp['metadata']->table['name'];
  261. }
  262. if ($sql != '') {
  263. $sql .= ', ';
  264. }
  265. $sql .= $this->getSqlTableAlias($tableName, $dqlAlias) . '.' .
  266. $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation";
  267. }
  268. }
  269. }
  270. return $sql;
  271. }
  272. /**
  273. * Generates a discriminator column SQL condition for the class with the given DQL alias.
  274. *
  275. * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
  276. * @return string
  277. */
  278. private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
  279. {
  280. $encapsulate = false;
  281. $sql = '';
  282. foreach ($dqlAliases as $dqlAlias) {
  283. $class = $this->_queryComponents[$dqlAlias]['metadata'];
  284. if ($class->isInheritanceTypeSingleTable()) {
  285. $conn = $this->_em->getConnection();
  286. $values = array();
  287. if ($class->discriminatorValue !== null) { // discrimnators can be 0
  288. $values[] = $conn->quote($class->discriminatorValue);
  289. }
  290. foreach ($class->subClasses as $subclassName) {
  291. $values[] = $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue);
  292. }
  293. if ($sql != '') {
  294. $sql .= ' AND ';
  295. $encapsulate = true;
  296. }
  297. $sql .= ($sql != '' ? ' AND ' : '')
  298. . (($this->_useSqlTableAliases) ? $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.' : '')
  299. . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
  300. }
  301. }
  302. return ($encapsulate) ? '(' . $sql . ')' : $sql;
  303. }
  304. /**
  305. * Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
  306. *
  307. * @return string The SQL.
  308. */
  309. public function walkSelectStatement(AST\SelectStatement $AST)
  310. {
  311. $sql = $this->walkSelectClause($AST->selectClause);
  312. $sql .= $this->walkFromClause($AST->fromClause);
  313. if (($whereClause = $AST->whereClause) !== null) {
  314. $sql .= $this->walkWhereClause($whereClause);
  315. } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
  316. $sql .= ' WHERE ' . $discSql;
  317. }
  318. $sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : '';
  319. $sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : '';
  320. if (($orderByClause = $AST->orderByClause) !== null) {
  321. $sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : '';
  322. } else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') {
  323. $sql .= ' ORDER BY '.$orderBySql;
  324. }
  325. $sql = $this->_platform->modifyLimitQuery(
  326. $sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
  327. );
  328. if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) {
  329. if ($lockMode == LockMode::PESSIMISTIC_READ) {
  330. $sql .= " " . $this->_platform->getReadLockSQL();
  331. } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
  332. $sql .= " " . $this->_platform->getWriteLockSQL();
  333. } else if ($lockMode == LockMode::OPTIMISTIC) {
  334. foreach ($this->_selectedClasses AS $class) {
  335. if ( ! $class->isVersioned) {
  336. throw \Doctrine\ORM\OptimisticLockException::lockFailed();
  337. }
  338. }
  339. }
  340. }
  341. return $sql;
  342. }
  343. /**
  344. * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
  345. *
  346. * @param UpdateStatement
  347. * @return string The SQL.
  348. */
  349. public function walkUpdateStatement(AST\UpdateStatement $AST)
  350. {
  351. $this->_useSqlTableAliases = false;
  352. $sql = $this->walkUpdateClause($AST->updateClause);
  353. if (($whereClause = $AST->whereClause) !== null) {
  354. $sql .= $this->walkWhereClause($whereClause);
  355. } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
  356. $sql .= ' WHERE ' . $discSql;
  357. }
  358. return $sql;
  359. }
  360. /**
  361. * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL.
  362. *
  363. * @param DeleteStatement
  364. * @return string The SQL.
  365. */
  366. public function walkDeleteStatement(AST\DeleteStatement $AST)
  367. {
  368. $this->_useSqlTableAliases = false;
  369. $sql = $this->walkDeleteClause($AST->deleteClause);
  370. if (($whereClause = $AST->whereClause) !== null) {
  371. $sql .= $this->walkWhereClause($whereClause);
  372. } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
  373. $sql .= ' WHERE ' . $discSql;
  374. }
  375. return $sql;
  376. }
  377. /**
  378. * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
  379. *
  380. * @param string $identificationVariable
  381. * @param string $fieldName
  382. * @return string The SQL.
  383. */
  384. public function walkIdentificationVariable($identificationVariable, $fieldName = null)
  385. {
  386. $class = $this->_queryComponents[$identificationVariable]['metadata'];
  387. if (
  388. $fieldName !== null && $class->isInheritanceTypeJoined() &&
  389. isset($class->fieldMappings[$fieldName]['inherited'])
  390. ) {
  391. $class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
  392. }
  393. return $this->getSQLTableAlias($class->table['name'], $identificationVariable);
  394. }
  395. /**
  396. * Walks down a PathExpression AST node, thereby generating the appropriate SQL.
  397. *
  398. * @param mixed
  399. * @return string The SQL.
  400. */
  401. public function walkPathExpression($pathExpr)
  402. {
  403. $sql = '';
  404. switch ($pathExpr->type) {
  405. case AST\PathExpression::TYPE_STATE_FIELD:
  406. $fieldName = $pathExpr->field;
  407. $dqlAlias = $pathExpr->identificationVariable;
  408. $class = $this->_queryComponents[$dqlAlias]['metadata'];
  409. if ($this->_useSqlTableAliases) {
  410. $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
  411. }
  412. $sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
  413. break;
  414. case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
  415. // 1- the owning side:
  416. // Just use the foreign key, i.e. u.group_id
  417. $fieldName = $pathExpr->field;
  418. $dqlAlias = $pathExpr->identificationVariable;
  419. $class = $this->_queryComponents[$dqlAlias]['metadata'];
  420. if (isset($class->associationMappings[$fieldName]['inherited'])) {
  421. $class = $this->_em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
  422. }
  423. $assoc = $class->associationMappings[$fieldName];
  424. if ($assoc['isOwningSide']) {
  425. // COMPOSITE KEYS NOT (YET?) SUPPORTED
  426. if (count($assoc['sourceToTargetKeyColumns']) > 1) {
  427. throw QueryException::associationPathCompositeKeyNotSupported();
  428. }
  429. if ($this->_useSqlTableAliases) {
  430. $sql .= $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.';
  431. }
  432. $sql .= reset($assoc['targetToSourceKeyColumns']);
  433. } else {
  434. throw QueryException::associationPathInverseSideNotSupported();
  435. }
  436. break;
  437. default:
  438. throw QueryException::invalidPathExpression($pathExpr);
  439. }
  440. return $sql;
  441. }
  442. /**
  443. * Walks down a SelectClause AST node, thereby generating the appropriate SQL.
  444. *
  445. * @param $selectClause
  446. * @return string The SQL.
  447. */
  448. public function walkSelectClause($selectClause)
  449. {
  450. $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '') . implode(
  451. ', ', array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)
  452. );
  453. $addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
  454. $this->_query->getHydrationMode() == Query::HYDRATE_OBJECT
  455. ||
  456. $this->_query->getHydrationMode() != Query::HYDRATE_OBJECT &&
  457. $this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
  458. foreach ($this->_selectedClasses as $dqlAlias => $class) {
  459. // Register as entity or joined entity result
  460. if ($this->_queryComponents[$dqlAlias]['relation'] === null) {
  461. $this->_rsm->addEntityResult($class->name, $dqlAlias);
  462. } else {
  463. $this->_rsm->addJoinedEntityResult(
  464. $class->name, $dqlAlias,
  465. $this->_queryComponents[$dqlAlias]['parent'],
  466. $this->_queryComponents[$dqlAlias]['relation']['fieldName']
  467. );
  468. }
  469. if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
  470. // Add discriminator columns to SQL
  471. $rootClass = $this->_em->getClassMetadata($class->rootEntityName);
  472. $tblAlias = $this->getSqlTableAlias($rootClass->table['name'], $dqlAlias);
  473. $discrColumn = $rootClass->discriminatorColumn;
  474. $columnAlias = $this->getSqlColumnAlias($discrColumn['name']);
  475. $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias;
  476. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  477. $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
  478. $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $discrColumn['fieldName']);
  479. // Add foreign key columns to SQL, if necessary
  480. if ($addMetaColumns) {
  481. //FIXME: Include foreign key columns of child classes also!!??
  482. foreach ($class->associationMappings as $assoc) {
  483. if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
  484. if (isset($assoc['inherited'])) {
  485. $owningClass = $this->_em->getClassMetadata($assoc['inherited']);
  486. $sqlTableAlias = $this->getSqlTableAlias($owningClass->table['name'], $dqlAlias);
  487. } else {
  488. $sqlTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
  489. }
  490. foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
  491. $columnAlias = $this->getSqlColumnAlias($srcColumn);
  492. $sql .= ", $sqlTableAlias." . $srcColumn . ' AS ' . $columnAlias;
  493. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  494. $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
  495. }
  496. }
  497. }
  498. }
  499. } else {
  500. // Add foreign key columns to SQL, if necessary
  501. if ($addMetaColumns) {
  502. $sqlTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
  503. foreach ($class->associationMappings as $assoc) {
  504. if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
  505. foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
  506. $columnAlias = $this->getSqlColumnAlias($srcColumn);
  507. $sql .= ', ' . $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
  508. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  509. $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
  510. }
  511. }
  512. }
  513. }
  514. }
  515. }
  516. return $sql;
  517. }
  518. /**
  519. * Walks down a FromClause AST node, thereby generating the appropriate SQL.
  520. *
  521. * @return string The SQL.
  522. */
  523. public function walkFromClause($fromClause)
  524. {
  525. $identificationVarDecls = $fromClause->identificationVariableDeclarations;
  526. $sqlParts = array();
  527. foreach ($identificationVarDecls as $identificationVariableDecl) {
  528. $sql = '';
  529. $rangeDecl = $identificationVariableDecl->rangeVariableDeclaration;
  530. $dqlAlias = $rangeDecl->aliasIdentificationVariable;
  531. $this->_rootAliases[] = $dqlAlias;
  532. $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
  533. $sql .= $class->getQuotedTableName($this->_platform) . ' '
  534. . $this->getSqlTableAlias($class->table['name'], $dqlAlias);
  535. if ($class->isInheritanceTypeJoined()) {
  536. $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
  537. }
  538. foreach ($identificationVariableDecl->joinVariableDeclarations as $joinVarDecl) {
  539. $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
  540. }
  541. if ($identificationVariableDecl->indexBy) {
  542. $this->_rsm->addIndexBy(
  543. $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
  544. $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field
  545. );
  546. }
  547. $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
  548. }
  549. return ' FROM ' . implode(', ', $sqlParts);
  550. }
  551. /**
  552. * Walks down a FunctionNode AST node, thereby generating the appropriate SQL.
  553. *
  554. * @return string The SQL.
  555. */
  556. public function walkFunction($function)
  557. {
  558. return $function->getSql($this);
  559. }
  560. /**
  561. * Walks down an OrderByClause AST node, thereby generating the appropriate SQL.
  562. *
  563. * @param OrderByClause
  564. * @return string The SQL.
  565. */
  566. public function walkOrderByClause($orderByClause)
  567. {
  568. $colSql = $this->_generateOrderedCollectionOrderByItems();
  569. if ($colSql != '') {
  570. $colSql = ", ".$colSql;
  571. }
  572. // OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
  573. return ' ORDER BY ' . implode(
  574. ', ', array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems)
  575. ) . $colSql;
  576. }
  577. /**
  578. * Walks down an OrderByItem AST node, thereby generating the appropriate SQL.
  579. *
  580. * @param OrderByItem
  581. * @return string The SQL.
  582. */
  583. public function walkOrderByItem($orderByItem)
  584. {
  585. $sql = '';
  586. $expr = $orderByItem->expression;
  587. if ($expr instanceof AST\PathExpression) {
  588. $sql = $this->walkPathExpression($expr);
  589. } else {
  590. $columnName = $this->_queryComponents[$expr]['token']['value'];
  591. $sql = $this->_scalarResultAliasMap[$columnName];
  592. }
  593. return $sql . ' ' . strtoupper($orderByItem->type);
  594. }
  595. /**
  596. * Walks down a HavingClause AST node, thereby generating the appropriate SQL.
  597. *
  598. * @param HavingClause
  599. * @return string The SQL.
  600. */
  601. public function walkHavingClause($havingClause)
  602. {
  603. return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
  604. }
  605. /**
  606. * Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL.
  607. *
  608. * @param JoinVariableDeclaration $joinVarDecl
  609. * @return string The SQL.
  610. */
  611. public function walkJoinVariableDeclaration($joinVarDecl)
  612. {
  613. $join = $joinVarDecl->join;
  614. $joinType = $join->joinType;
  615. if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) {
  616. $sql = ' LEFT JOIN ';
  617. } else {
  618. $sql = ' INNER JOIN ';
  619. }
  620. $joinAssocPathExpr = $join->joinAssociationPathExpression;
  621. $joinedDqlAlias = $join->aliasIdentificationVariable;
  622. $relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
  623. $targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
  624. $sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
  625. $targetTableName = $targetClass->getQuotedTableName($this->_platform);
  626. $targetTableAlias = $this->getSqlTableAlias($targetClass->table['name'], $joinedDqlAlias);
  627. $sourceTableAlias = $this->getSqlTableAlias($sourceClass->table['name'], $joinAssocPathExpr->identificationVariable);
  628. // Ensure we got the owning side, since it has all mapping info
  629. $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
  630. if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true) {
  631. if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
  632. throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
  633. }
  634. }
  635. // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
  636. // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
  637. // The owning side is necessary at this point because only it contains the JoinColumn information.
  638. if ($assoc['type'] & ClassMetadata::TO_ONE) {
  639. $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
  640. $first = true;
  641. foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
  642. if ( ! $first) $sql .= ' AND '; else $first = false;
  643. if ($relation['isOwningSide']) {
  644. if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
  645. $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
  646. } else {
  647. $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
  648. }
  649. $sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
  650. } else {
  651. if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
  652. $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
  653. } else {
  654. $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
  655. }
  656. $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
  657. }
  658. }
  659. } else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
  660. // Join relation table
  661. $joinTable = $assoc['joinTable'];
  662. $joinTableAlias = $this->getSqlTableAlias($joinTable['name'], $joinedDqlAlias);
  663. $sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON ';
  664. $first = true;
  665. if ($relation['isOwningSide']) {
  666. foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
  667. if ( ! $first) $sql .= ' AND '; else $first = false;
  668. if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn])) {
  669. $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
  670. } else {
  671. $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform);
  672. }
  673. $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
  674. }
  675. } else {
  676. foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
  677. if ( ! $first) $sql .= ' AND '; else $first = false;
  678. if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
  679. $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
  680. } else {
  681. $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
  682. }
  683. $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
  684. }
  685. }
  686. // Join target table
  687. $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
  688. ? ' LEFT JOIN ' : ' INNER JOIN ';
  689. $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
  690. $first = true;
  691. if ($relation['isOwningSide']) {
  692. foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
  693. if ( ! $first) $sql .= ' AND '; else $first = false;
  694. if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
  695. $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
  696. } else {
  697. $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
  698. }
  699. $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
  700. }
  701. } else {
  702. foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
  703. if ( ! $first) $sql .= ' AND '; else $first = false;
  704. if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$sourceColumn])) {
  705. $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
  706. } else {
  707. $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform);
  708. }
  709. $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
  710. }
  711. }
  712. }
  713. // Handle WITH clause
  714. if (($condExpr = $join->conditionalExpression) !== null) {
  715. // Phase 2 AST optimization: Skip processment of ConditionalExpression
  716. // if only one ConditionalTerm is defined
  717. $sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')';
  718. }
  719. $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
  720. if ($discrSql) {
  721. $sql .= ' AND ' . $discrSql;
  722. }
  723. // FIXME: these should either be nested or all forced to be left joins (DDC-XXX)
  724. if ($targetClass->isInheritanceTypeJoined()) {
  725. $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
  726. }
  727. return $sql;
  728. }
  729. /**
  730. * Walks down a SelectExpression AST node and generates the corresponding SQL.
  731. *
  732. * @param SelectExpression $selectExpression
  733. * @return string The SQL.
  734. */
  735. public function walkSelectExpression($selectExpression)
  736. {
  737. $sql = '';
  738. $expr = $selectExpression->expression;
  739. if ($expr instanceof AST\PathExpression) {
  740. if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) {
  741. $fieldName = $expr->field;
  742. $dqlAlias = $expr->identificationVariable;
  743. $qComp = $this->_queryComponents[$dqlAlias];
  744. $class = $qComp['metadata'];
  745. if ( ! $selectExpression->fieldIdentificationVariable) {
  746. $resultAlias = $fieldName;
  747. } else {
  748. $resultAlias = $selectExpression->fieldIdentificationVariable;
  749. }
  750. if ($class->isInheritanceTypeJoined()) {
  751. $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
  752. } else {
  753. $tableName = $class->getTableName();
  754. }
  755. $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias);
  756. $columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
  757. $columnAlias = $this->getSqlColumnAlias($columnName);
  758. $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
  759. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  760. $this->_rsm->addScalarResult($columnAlias, $resultAlias);
  761. } else {
  762. throw QueryException::invalidPathExpression($expr->type);
  763. }
  764. }
  765. else if ($expr instanceof AST\AggregateExpression) {
  766. if ( ! $selectExpression->fieldIdentificationVariable) {
  767. $resultAlias = $this->_scalarResultCounter++;
  768. } else {
  769. $resultAlias = $selectExpression->fieldIdentificationVariable;
  770. }
  771. $columnAlias = 'sclr' . $this->_aliasCounter++;
  772. $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias;
  773. $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
  774. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  775. $this->_rsm->addScalarResult($columnAlias, $resultAlias);
  776. }
  777. else if ($expr instanceof AST\Subselect) {
  778. if ( ! $selectExpression->fieldIdentificationVariable) {
  779. $resultAlias = $this->_scalarResultCounter++;
  780. } else {
  781. $resultAlias = $selectExpression->fieldIdentificationVariable;
  782. }
  783. $columnAlias = 'sclr' . $this->_aliasCounter++;
  784. $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias;
  785. $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
  786. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  787. $this->_rsm->addScalarResult($columnAlias, $resultAlias);
  788. }
  789. else if ($expr instanceof AST\Functions\FunctionNode) {
  790. if ( ! $selectExpression->fieldIdentificationVariable) {
  791. $resultAlias = $this->_scalarResultCounter++;
  792. } else {
  793. $resultAlias = $selectExpression->fieldIdentificationVariable;
  794. }
  795. $columnAlias = 'sclr' . $this->_aliasCounter++;
  796. $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
  797. $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
  798. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  799. $this->_rsm->addScalarResult($columnAlias, $resultAlias);
  800. }
  801. else if (
  802. $expr instanceof AST\SimpleArithmeticExpression ||
  803. $expr instanceof AST\ArithmeticTerm ||
  804. $expr instanceof AST\ArithmeticFactor ||
  805. $expr instanceof AST\ArithmeticPrimary
  806. ) {
  807. if ( ! $selectExpression->fieldIdentificationVariable) {
  808. $resultAlias = $this->_scalarResultCounter++;
  809. } else {
  810. $resultAlias = $selectExpression->fieldIdentificationVariable;
  811. }
  812. $columnAlias = 'sclr' . $this->_aliasCounter++;
  813. $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
  814. $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
  815. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  816. $this->_rsm->addScalarResult($columnAlias, $resultAlias);
  817. } else {
  818. // IdentificationVariable or PartialObjectExpression
  819. if ($expr instanceof AST\PartialObjectExpression) {
  820. $dqlAlias = $expr->identificationVariable;
  821. $partialFieldSet = $expr->partialFieldSet;
  822. } else {
  823. $dqlAlias = $expr;
  824. $partialFieldSet = array();
  825. }
  826. $queryComp = $this->_queryComponents[$dqlAlias];
  827. $class = $queryComp['metadata'];
  828. if ( ! isset($this->_selectedClasses[$dqlAlias])) {
  829. $this->_selectedClasses[$dqlAlias] = $class;
  830. }
  831. $beginning = true;
  832. // Select all fields from the queried class
  833. foreach ($class->fieldMappings as $fieldName => $mapping) {
  834. if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
  835. continue;
  836. }
  837. if (isset($mapping['inherited'])) {
  838. $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name'];
  839. } else {
  840. $tableName = $class->table['name'];
  841. }
  842. if ($beginning) $beginning = false; else $sql .= ', ';
  843. $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias);
  844. $columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
  845. $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform)
  846. . ' AS ' . $columnAlias;
  847. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  848. $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
  849. }
  850. if ($class->containsForeignIdentifier) {
  851. // Add double entry for association identifier columns to simplify hydrator code
  852. foreach ($class->identifier AS $idField) {
  853. if (isset($class->associationMappings[$idField])) {
  854. if (isset($mapping['inherited'])) {
  855. $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name'];
  856. } else {
  857. $tableName = $class->table['name'];
  858. }
  859. if ($beginning) $beginning = false; else $sql .= ', ';
  860. $joinColumnName = $class->associationMappings[$idField]['joinColumns'][0]['name'];
  861. $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias);
  862. $columnAlias = $this->getSqlColumnAlias($joinColumnName);
  863. $sql .= $sqlTableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias;
  864. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  865. $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $idField);
  866. }
  867. }
  868. }
  869. // Add any additional fields of subclasses (excluding inherited fields)
  870. // 1) on Single Table Inheritance: always, since its marginal overhead
  871. // 2) on Class Table Inheritance only if partial objects are disallowed,
  872. // since it requires outer joining subtables.
  873. if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
  874. foreach ($class->subClasses as $subClassName) {
  875. $subClass = $this->_em->getClassMetadata($subClassName);
  876. $sqlTableAlias = $this->getSqlTableAlias($subClass->table['name'], $dqlAlias);
  877. foreach ($subClass->fieldMappings as $fieldName => $mapping) {
  878. if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
  879. continue;
  880. }
  881. if ($beginning) $beginning = false; else $sql .= ', ';
  882. $columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
  883. $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform)
  884. . ' AS ' . $columnAlias;
  885. $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
  886. $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
  887. }
  888. // Add join columns (foreign keys) of the subclass
  889. //TODO: Probably better do this in walkSelectClause to honor the INCLUDE_META_COLUMNS hint
  890. foreach ($subClass->associationMappings as $fieldName => $assoc) {
  891. if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
  892. foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
  893. if ($beginning) $beginning = false; else $sql .= ', ';
  894. $columnAlias = $this->getSqlColumnAlias($srcColumn);
  895. $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
  896. $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
  897. }
  898. }
  899. }
  900. }
  901. }
  902. }
  903. return $sql;
  904. }
  905. /**
  906. * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL.
  907. *
  908. * @param QuantifiedExpression
  909. * @return string The SQL.
  910. */
  911. public function walkQuantifiedExpression($qExpr)
  912. {
  913. return ' ' . strtoupper($qExpr->type)
  914. . '(' . $this->walkSubselect($qExpr->subselect) . ')';
  915. }
  916. /**
  917. * Walks down a Subselect AST node, thereby generating the appropriate SQL.
  918. *
  919. * @param Subselect
  920. * @return string The SQL.
  921. */
  922. public function walkSubselect($subselect)
  923. {
  924. $useAliasesBefore = $this->_useSqlTableAliases;
  925. $this->_useSqlTableAliases = true;
  926. $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
  927. $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
  928. $sql .= $subselect->whereClause ? $this->walkWhereClause($subselect->whereClause) : '';
  929. $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
  930. $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
  931. $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
  932. $this->_useSqlTableAliases = $useAliasesBefore;
  933. return $sql;
  934. }
  935. /**
  936. * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL.
  937. *
  938. * @param SubselectFromClause
  939. * @return string The SQL.
  940. */
  941. public function walkSubselectFromClause($subselectFromClause)
  942. {
  943. $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
  944. $sqlParts = array ();
  945. foreach ($identificationVarDecls as $subselectIdVarDecl) {
  946. $sql = '';
  947. $rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration;
  948. $dqlAlias = $rangeDecl->aliasIdentificationVariable;
  949. $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
  950. $sql .= $class->getQuotedTableName($this->_platform) . ' '
  951. . $this->getSqlTableAlias($class->table['name'], $dqlAlias);
  952. if ($class->isInheritanceTypeJoined()) {
  953. $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
  954. }
  955. foreach ($subselectIdVarDecl->joinVariableDeclarations as $joinVarDecl) {
  956. $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
  957. }
  958. $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
  959. }
  960. return ' FROM ' . implode(', ', $sqlParts);
  961. }
  962. /**
  963. * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL.
  964. *
  965. * @param SimpleSelectClause
  966. * @return string The SQL.
  967. */
  968. public function walkSimpleSelectClause($simpleSelectClause)
  969. {
  970. return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
  971. . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
  972. }
  973. /**
  974. * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL.
  975. *
  976. * @param SimpleSelectExpression
  977. * @return string The SQL.
  978. */
  979. public function walkSimpleSelectExpression($simpleSelectExpression)
  980. {
  981. $sql = '';
  982. $expr = $simpleSelectExpression->expression;
  983. if ($expr instanceof AST\PathExpression) {
  984. $sql .= $this->walkPathExpression($expr);
  985. } else if ($expr instanceof AST\AggregateExpression) {
  986. if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
  987. $alias = $this->_scalarResultCounter++;
  988. } else {
  989. $alias = $simpleSelectExpression->fieldIdentificationVariable;
  990. }
  991. $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
  992. } else if ($expr instanceof AST\Subselect) {
  993. if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
  994. $alias = $this->_scalarResultCounter++;
  995. } else {
  996. $alias = $simpleSelectExpression->fieldIdentificationVariable;
  997. }
  998. $columnAlias = 'sclr' . $this->_aliasCounter++;
  999. $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
  1000. $this->_scalarResultAliasMap[$alias] = $columnAlias;
  1001. } else if ($expr instanceof AST\Functions\FunctionNode) {
  1002. if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
  1003. $alias = $this->_scalarResultCounter++;
  1004. } else {
  1005. $alias = $simpleSelectExpression->fieldIdentificationVariable;
  1006. }
  1007. $columnAlias = 'sclr' . $this->_aliasCounter++;
  1008. $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
  1009. $this->_scalarResultAliasMap[$alias] = $columnAlias;
  1010. } else if (
  1011. $expr instanceof AST\SimpleArithmeticExpression ||
  1012. $expr instanceof AST\ArithmeticTerm ||
  1013. $expr instanceof AST\ArithmeticFactor ||
  1014. $expr instanceof AST\ArithmeticPrimary
  1015. ) {
  1016. if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
  1017. $alias = $this->_scalarResultCounter++;
  1018. } else {
  1019. $alias = $simpleSelectExpression->fieldIdentificationVariable;
  1020. }
  1021. $columnAlias = 'sclr' . $this->_aliasCounter++;
  1022. $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
  1023. $this->_scalarResultAliasMap[$alias] = $columnAlias;
  1024. } else {
  1025. // IdentificationVariable
  1026. $class = $this->_queryComponents[$expr]['metadata'];
  1027. $tableAlias = $this->getSqlTableAlias($class->getTableName(), $expr);
  1028. $first = true;
  1029. foreach ($class->identifier as $identifier) {
  1030. if ($first) $first = false; else $sql .= ', ';
  1031. $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform);
  1032. }
  1033. }
  1034. return ' ' . $sql;
  1035. }
  1036. /**
  1037. * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL.
  1038. *
  1039. * @param AggregateExpression
  1040. * @return string The SQL.
  1041. */
  1042. public function walkAggregateExpression($aggExpression)
  1043. {
  1044. return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
  1045. . $this->walkPathExpression($aggExpression->pathExpression) . ')';
  1046. }
  1047. /**
  1048. * Walks down a GroupByClause AST node, thereby generating the appropriate SQL.
  1049. *
  1050. * @param GroupByClause
  1051. * @return string The SQL.
  1052. */
  1053. public function walkGroupByClause($groupByClause)
  1054. {
  1055. return ' GROUP BY ' . implode(
  1056. ', ', array_map(array($this, 'walkGroupByItem'), $groupByClause->groupByItems)
  1057. );
  1058. }
  1059. /**
  1060. * Walks down a GroupByItem AST node, thereby generating the appropriate SQL.
  1061. *
  1062. * @param GroupByItem
  1063. * @return string The SQL.
  1064. */
  1065. public function walkGroupByItem(AST\PathExpression $pathExpr)
  1066. {
  1067. return $this->walkPathExpression($pathExpr);
  1068. }
  1069. /**
  1070. * Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
  1071. *
  1072. * @param DeleteClause
  1073. * @return string The SQL.
  1074. */
  1075. public function walkDeleteClause(AST\DeleteClause $deleteClause)
  1076. {
  1077. $sql = 'DELETE FROM ';
  1078. $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
  1079. $sql .= $class->getQuotedTableName($this->_platform);
  1080. $this->setSqlTableAlias($class->getTableName(), $class->getTableName(), $deleteClause->aliasIdentificationVariable);
  1081. $this->_rootAliases[] = $deleteClause->aliasIdentificationVariable;
  1082. return $sql;
  1083. }
  1084. /**
  1085. * Walks down an UpdateClause AST node, thereby generating the appropriate SQL.
  1086. *
  1087. * @param UpdateClause
  1088. * @return string The SQL.
  1089. */
  1090. public function walkUpdateClause($updateClause)
  1091. {
  1092. $sql = 'UPDATE ';
  1093. $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName);
  1094. $sql .= $class->getQuotedTableName($this->_platform);
  1095. $this->setSqlTableAlias($class->getTableName(), $class->getTableName(), $updateClause->aliasIdentificationVariable);
  1096. $this->_rootAliases[] = $updateClause->aliasIdentificationVariable;
  1097. $sql .= ' SET ' . implode(
  1098. ', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems)
  1099. );
  1100. return $sql;
  1101. }
  1102. /**
  1103. * Walks down an UpdateItem AST node, thereby generating the appropriate SQL.
  1104. *
  1105. * @param UpdateItem
  1106. * @return string The SQL.
  1107. */
  1108. public function walkUpdateItem($updateItem)
  1109. {
  1110. $useTableAliasesBefore = $this->_useSqlTableAliases;
  1111. $this->_useSqlTableAliases = false;
  1112. $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
  1113. $newValue = $updateItem->newValue;
  1114. if ($newValue === null) {
  1115. $sql .= 'NULL';
  1116. } else if ($newValue instanceof AST\Node) {
  1117. $sql .= $newValue->dispatch($this);
  1118. } else {
  1119. $sql .= $this->_conn->quote($newValue);
  1120. }
  1121. $this->_useSqlTableAliases = $useTableAliasesBefore;
  1122. return $sql;
  1123. }
  1124. /**
  1125. * Walks down a WhereClause AST node, thereby generating the appropriate SQL.
  1126. *
  1127. * @param WhereClause
  1128. * @return string The SQL.
  1129. */
  1130. public function walkWhereClause($whereClause)
  1131. {
  1132. $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases);
  1133. $condSql = $this->walkConditionalExpression($whereClause->conditionalExpression);
  1134. return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
  1135. }
  1136. /**
  1137. * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL.
  1138. *
  1139. * @param ConditionalExpression
  1140. * @return string The SQL.
  1141. */
  1142. public function walkConditionalExpression($condExpr)
  1143. {
  1144. // Phase 2 AST optimization: Skip processment of ConditionalExpression
  1145. // if only one ConditionalTerm is defined
  1146. return ( ! ($condExpr instanceof AST\ConditionalExpression))
  1147. ? $this->walkConditionalTerm($condExpr)
  1148. : implode(
  1149. ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)
  1150. );
  1151. }
  1152. /**
  1153. * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL.
  1154. *
  1155. * @param ConditionalTerm
  1156. * @return string The SQL.
  1157. */
  1158. public function walkConditionalTerm($condTerm)
  1159. {
  1160. // Phase 2 AST optimization: Skip processment of ConditionalTerm
  1161. // if only one ConditionalFactor is defined
  1162. return ( ! ($condTerm instanceof AST\ConditionalTerm))
  1163. ? $this->walkConditionalFactor($condTerm)
  1164. : implode(
  1165. ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors)
  1166. );
  1167. }
  1168. /**
  1169. * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL.
  1170. *
  1171. * @param ConditionalFactor
  1172. * @return string The SQL.
  1173. */
  1174. public function walkConditionalFactor($factor)
  1175. {
  1176. // Phase 2 AST optimization: Skip processment of ConditionalFactor
  1177. // if only one ConditionalPrimary is defined
  1178. return ( ! ($factor instanceof AST\ConditionalFactor))
  1179. ? $this->walkConditionalPrimary($factor)
  1180. : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
  1181. }
  1182. /**
  1183. * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL.
  1184. *
  1185. * @param ConditionalPrimary
  1186. * @return string The SQL.
  1187. */
  1188. public function walkConditionalPrimary($primary)
  1189. {
  1190. if ($primary->isSimpleConditionalExpression()) {
  1191. return $primary->simpleConditionalExpression->dispatch($this);
  1192. } else if ($primary->isConditionalExpression()) {
  1193. $condExpr = $primary->conditionalExpression;
  1194. return '(' . $this->walkConditionalExpression($condExpr) . ')';
  1195. }
  1196. }
  1197. /**
  1198. * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL.
  1199. *
  1200. * @param ExistsExpression
  1201. * @return string The SQL.
  1202. */
  1203. public function walkExistsExpression($existsExpr)
  1204. {
  1205. $sql = ($existsExpr->not) ? 'NOT ' : '';
  1206. $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
  1207. return $sql;
  1208. }
  1209. /**
  1210. * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL.
  1211. *
  1212. * @param CollectionMemberExpression
  1213. * @return string The SQL.
  1214. */
  1215. public function walkCollectionMemberExpression($collMemberExpr)
  1216. {
  1217. $sql = $collMemberExpr->not ? 'NOT ' : '';
  1218. $sql .= 'EXISTS (SELECT 1 FROM ';
  1219. $entityExpr = $collMemberExpr->entityExpression;
  1220. $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
  1221. $fieldName = $collPathExpr->field;
  1222. $dqlAlias = $collPathExpr->identificationVariable;
  1223. $class = $this->_queryComponents[$dqlAlias]['metadata'];
  1224. if ($entityExpr instanceof AST\InputParameter) {
  1225. $dqlParamKey = $entityExpr->name;
  1226. $entity = $this->_query->getParameter($dqlParamKey);
  1227. } else {
  1228. //TODO
  1229. throw new \BadMethodCallException("Not implemented");
  1230. }
  1231. $assoc = $class->associationMappings[$fieldName];
  1232. if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
  1233. $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
  1234. $targetTableAlias = $this->getSqlTableAlias($targetClass->table['name']);
  1235. $sourceTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
  1236. $sql .= $targetClass->getQuotedTableName($this->_platform)
  1237. . ' ' . $targetTableAlias . ' WHERE ';
  1238. $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
  1239. $first = true;
  1240. foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
  1241. if ($first) $first = false; else $sql .= ' AND ';
  1242. $sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform)
  1243. . ' = '
  1244. . $targetTableAlias . '.' . $sourceColumn;
  1245. }
  1246. $sql .= ' AND ';
  1247. $first = true;
  1248. foreach ($targetClass->identifier as $idField) {
  1249. if ($first) $first = false; else $sql .= ' AND ';
  1250. $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
  1251. $sql .= $targetTableAlias . '.'
  1252. . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
  1253. }
  1254. } else { // many-to-many
  1255. $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
  1256. $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
  1257. $joinTable = $owningAssoc['joinTable'];
  1258. // SQL table aliases
  1259. $joinTableAlias = $this->getSqlTableAlias($joinTable['name']);
  1260. $targetTableAlias = $this->getSqlTableAlias($targetClass->table['name']);
  1261. $sourceTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
  1262. // join to target table
  1263. $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform)
  1264. . ' ' . $joinTableAlias . ' INNER JOIN '
  1265. . $targetClass->getQuotedTableName($this->_platform)
  1266. . ' ' . $targetTableAlias . ' ON ';
  1267. // join conditions
  1268. $joinColumns = $assoc['isOwningSide']
  1269. ? $joinTable['inverseJoinColumns']
  1270. : $joinTable['joinColumns'];
  1271. $first = true;
  1272. foreach ($joinColumns as $joinColumn) {
  1273. if ($first) $first = false; else $sql .= ' AND ';
  1274. $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
  1275. . $targetTableAlias . '.' . $targetClass->getQuotedColumnName(
  1276. $targetClass->fieldNames[$joinColumn['referencedColumnName']],
  1277. $this->_platform);
  1278. }
  1279. $sql .= ' WHERE ';
  1280. $joinColumns = $assoc['isOwningSide']
  1281. ? $joinTable['joinColumns']
  1282. : $joinTable['inverseJoinColumns'];
  1283. $first = true;
  1284. foreach ($joinColumns as $joinColumn) {
  1285. if ($first) $first = false; else $sql .= ' AND ';
  1286. $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
  1287. . $sourceTableAlias . '.' . $class->getQuotedColumnName(
  1288. $class->fieldNames[$joinColumn['referencedColumnName']],
  1289. $this->_platform);
  1290. }
  1291. $sql .= ' AND ';
  1292. $first = true;
  1293. foreach ($targetClass->identifier as $idField) {
  1294. if ($first) $first = false; else $sql .= ' AND ';
  1295. $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
  1296. $sql .= $targetTableAlias . '.'
  1297. . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
  1298. }
  1299. }
  1300. return $sql . ')';
  1301. }
  1302. /**
  1303. * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL.
  1304. *
  1305. * @param EmptyCollectionComparisonExpression
  1306. * @return string The SQL.
  1307. */
  1308. public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
  1309. {
  1310. $sizeFunc = new AST\Functions\SizeFunction('size');
  1311. $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
  1312. return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
  1313. }
  1314. /**
  1315. * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL.
  1316. *
  1317. * @param NullComparisonExpression
  1318. * @return string The SQL.
  1319. */
  1320. public function walkNullComparisonExpression($nullCompExpr)
  1321. {
  1322. $sql = '';
  1323. $innerExpr = $nullCompExpr->expression;
  1324. if ($innerExpr instanceof AST\InputParameter) {
  1325. $dqlParamKey = $innerExpr->name;
  1326. $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
  1327. $sql .= ' ?';
  1328. } else {
  1329. $sql .= $this->walkPathExpression($innerExpr);
  1330. }
  1331. $sql .= ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
  1332. return $sql;
  1333. }
  1334. /**
  1335. * Walks down an InExpression AST node, thereby generating the appropriate SQL.
  1336. *
  1337. * @param InExpression
  1338. * @return string The SQL.
  1339. */
  1340. public function walkInExpression($inExpr)
  1341. {
  1342. $sql = $this->walkPathExpression($inExpr->pathExpression)
  1343. . ($inExpr->not ? ' NOT' : '') . ' IN (';
  1344. if ($inExpr->subselect) {
  1345. $sql .= $this->walkSubselect($inExpr->subselect);
  1346. } else {
  1347. $sql .= implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals));
  1348. }
  1349. $sql .= ')';
  1350. return $sql;
  1351. }
  1352. /**
  1353. * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
  1354. *
  1355. * @param InstanceOfExpression
  1356. * @return string The SQL.
  1357. */
  1358. public function walkInstanceOfExpression($instanceOfExpr)
  1359. {
  1360. $sql = '';
  1361. $dqlAlias = $instanceOfExpr->identificationVariable;
  1362. $discrClass = $class = $this->_queryComponents[$dqlAlias]['metadata'];
  1363. $fieldName = null;
  1364. if ($class->discriminatorColumn) {
  1365. $discrClass = $this->_em->getClassMetadata($class->rootEntityName);
  1366. }
  1367. if ($this->_useSqlTableAliases) {
  1368. $sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.';
  1369. }
  1370. $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' <> ' : ' = ');
  1371. if ($instanceOfExpr->value instanceof AST\InputParameter) {
  1372. // We need to modify the parameter value to be its correspondent mapped value
  1373. $dqlParamKey = $instanceOfExpr->value->name;
  1374. $paramValue = $this->_query->getParameter($dqlParamKey);
  1375. if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) {
  1376. throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue));
  1377. }
  1378. $entityClassName = $paramValue->name;
  1379. } else {
  1380. // Get name from ClassMetadata to resolve aliases.
  1381. $entityClassName = $this->_em->getClassMetadata($instanceOfExpr->value)->name;
  1382. }
  1383. if ($entityClassName == $class->name) {
  1384. $sql .= $this->_conn->quote($class->discriminatorValue);
  1385. } else {
  1386. $discrMap = array_flip($class->discriminatorMap);
  1387. $sql .= $this->_conn->quote($discrMap[$entityClassName]);
  1388. }
  1389. return $sql;
  1390. }
  1391. /**
  1392. * Walks down an InParameter AST node, thereby generating the appropriate SQL.
  1393. *
  1394. * @param InParameter
  1395. * @return string The SQL.
  1396. */
  1397. public function walkInParameter($inParam)
  1398. {
  1399. return $inParam instanceof AST\InputParameter ?
  1400. $this->walkInputParameter($inParam) :
  1401. $this->walkLiteral($inParam);
  1402. }
  1403. /**
  1404. * Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
  1405. *
  1406. * @param mixed
  1407. * @return string The SQL.
  1408. */
  1409. public function walkLiteral($literal)
  1410. {
  1411. switch ($literal->type) {
  1412. case AST\Literal::STRING:
  1413. return $this->_conn->quote($literal->value);
  1414. case AST\Literal::BOOLEAN:
  1415. $bool = strtolower($literal->value) == 'true' ? true : false;
  1416. $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
  1417. return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal;
  1418. case AST\Literal::NUMERIC:
  1419. return $literal->value;
  1420. default:
  1421. throw QueryException::invalidLiteral($literal);
  1422. }
  1423. }
  1424. /**
  1425. * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL.
  1426. *
  1427. * @param BetweenExpression
  1428. * @return string The SQL.
  1429. */
  1430. public function walkBetweenExpression($betweenExpr)
  1431. {
  1432. $sql = $this->walkArithmeticExpression($betweenExpr->expression);
  1433. if ($betweenExpr->not) $sql .= ' NOT';
  1434. $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
  1435. . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
  1436. return $sql;
  1437. }
  1438. /**
  1439. * Walks down a LikeExpression AST node, thereby generating the appropriate SQL.
  1440. *
  1441. * @param LikeExpression
  1442. * @return string The SQL.
  1443. */
  1444. public function walkLikeExpression($likeExpr)
  1445. {
  1446. $stringExpr = $likeExpr->stringExpression;
  1447. $sql = $stringExpr->dispatch($this) . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
  1448. if ($likeExpr->stringPattern instanceof AST\InputParameter) {
  1449. $inputParam = $likeExpr->stringPattern;
  1450. $dqlParamKey = $inputParam->name;
  1451. $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
  1452. $sql .= '?';
  1453. } else {
  1454. $sql .= $this->_conn->quote($likeExpr->stringPattern);
  1455. }
  1456. if ($likeExpr->escapeChar) {
  1457. $sql .= ' ESCAPE ' . $this->_conn->quote($likeExpr->escapeChar);
  1458. }
  1459. return $sql;
  1460. }
  1461. /**
  1462. * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL.
  1463. *
  1464. * @param StateFieldPathExpression
  1465. * @return string The SQL.
  1466. */
  1467. public function walkStateFieldPathExpression($stateFieldPathExpression)
  1468. {
  1469. return $this->walkPathExpression($stateFieldPathExpression);
  1470. }
  1471. /**
  1472. * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL.
  1473. *
  1474. * @param ComparisonExpression
  1475. * @return string The SQL.
  1476. */
  1477. public function walkComparisonExpression($compExpr)
  1478. {
  1479. $sql = '';
  1480. $leftExpr = $compExpr->leftExpression;
  1481. $rightExpr = $compExpr->rightExpression;
  1482. if ($leftExpr instanceof AST\Node) {
  1483. $sql .= $leftExpr->dispatch($this);
  1484. } else {
  1485. $sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr);
  1486. }
  1487. $sql .= ' ' . $compExpr->operator . ' ';
  1488. if ($rightExpr instanceof AST\Node) {
  1489. $sql .= $rightExpr->dispatch($this);
  1490. } else {
  1491. $sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr);
  1492. }
  1493. return $sql;
  1494. }
  1495. /**
  1496. * Walks down an InputParameter AST node, thereby generating the appropriate SQL.
  1497. *
  1498. * @param InputParameter
  1499. * @return string The SQL.
  1500. */
  1501. public function walkInputParameter($inputParam)
  1502. {
  1503. $this->_parserResult->addParameterMapping($inputParam->name, $this->_sqlParamIndex++);
  1504. return '?';
  1505. }
  1506. /**
  1507. * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL.
  1508. *
  1509. * @param ArithmeticExpression
  1510. * @return string The SQL.
  1511. */
  1512. public function walkArithmeticExpression($arithmeticExpr)
  1513. {
  1514. return ($arithmeticExpr->isSimpleArithmeticExpression())
  1515. ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
  1516. : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
  1517. }
  1518. /**
  1519. * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL.
  1520. *
  1521. * @param SimpleArithmeticExpression
  1522. * @return string The SQL.
  1523. */
  1524. public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
  1525. {
  1526. return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression))
  1527. ? $this->walkArithmeticTerm($simpleArithmeticExpr)
  1528. : implode(
  1529. ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms)
  1530. );
  1531. }
  1532. /**
  1533. * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL.
  1534. *
  1535. * @param mixed
  1536. * @return string The SQL.
  1537. */
  1538. public function walkArithmeticTerm($term)
  1539. {
  1540. if (is_string($term)) {
  1541. return $term;
  1542. }
  1543. // Phase 2 AST optimization: Skip processment of ArithmeticTerm
  1544. // if only one ArithmeticFactor is defined
  1545. return ( ! ($term instanceof AST\ArithmeticTerm))
  1546. ? $this->walkArithmeticFactor($term)
  1547. : implode(
  1548. ' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors)
  1549. );
  1550. }
  1551. /**
  1552. * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL.
  1553. *
  1554. * @param mixed
  1555. * @return string The SQL.
  1556. */
  1557. public function walkArithmeticFactor($factor)
  1558. {
  1559. if (is_string($factor)) {
  1560. return $factor;
  1561. }
  1562. // Phase 2 AST optimization: Skip processment of ArithmeticFactor
  1563. // if only one ArithmeticPrimary is defined
  1564. return ( ! ($factor instanceof AST\ArithmeticFactor))
  1565. ? $this->walkArithmeticPrimary($factor)
  1566. : ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''))
  1567. . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
  1568. }
  1569. /**
  1570. * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
  1571. *
  1572. * @param mixed
  1573. * @return string The SQL.
  1574. */
  1575. public function walkArithmeticPrimary($primary)
  1576. {
  1577. if ($primary instanceof AST\SimpleArithmeticExpression) {
  1578. return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
  1579. } else if ($primary instanceof AST\Node) {
  1580. return $primary->dispatch($this);
  1581. }
  1582. // TODO: We need to deal with IdentificationVariable here
  1583. return '';
  1584. }
  1585. /**
  1586. * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL.
  1587. *
  1588. * @param mixed
  1589. * @return string The SQL.
  1590. */
  1591. public function walkStringPrimary($stringPrimary)
  1592. {
  1593. return (is_string($stringPrimary))
  1594. ? $this->_conn->quote($stringPrimary)
  1595. : $stringPrimary->dispatch($this);
  1596. }
  1597. }