SqlWalker.php 81 KB

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