SqlWalker.php 84 KB

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