SqlWalker.php 77KB

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