SqlWalker.php 80 KB

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