SqlWalker.php 76KB

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