Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • ActiveRow
  • GroupedSelection
  • Selection
  • SqlBuilder

Interfaces

  • IRow
  • IRowContainer
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Database\Table;
  9: 
 10: use Nette;
 11: use Nette\Database\ISupplementalDriver;
 12: use Nette\Database\SqlLiteral;
 13: use Nette\Database\IConventions;
 14: use Nette\Database\Context;
 15: use Nette\Database\IStructure;
 16: 
 17: 
 18: /**
 19:  * Builds SQL query.
 20:  * SqlBuilder is based on great library NotORM http://www.notorm.com written by Jakub Vrana.
 21:  */
 22: class SqlBuilder
 23: {
 24:     use Nette\SmartObject;
 25: 
 26:     /** @var string */
 27:     protected $tableName;
 28: 
 29:     /** @var IConventions */
 30:     protected $conventions;
 31: 
 32:     /** @var string delimited table name */
 33:     protected $delimitedTable;
 34: 
 35:     /** @var array of column to select */
 36:     protected $select = [];
 37: 
 38:     /** @var array of where conditions */
 39:     protected $where = [];
 40: 
 41:     /** @var array of array of join conditions */
 42:     protected $joinCondition = [];
 43: 
 44:     /** @var array of where conditions for caching */
 45:     protected $conditions = [];
 46: 
 47:     /** @var array of parameters passed to where conditions */
 48:     protected $parameters = [
 49:         'select' => [],
 50:         'joinCondition' => [],
 51:         'where' => [],
 52:         'group' => [],
 53:         'having' => [],
 54:         'order' => [],
 55:     ];
 56: 
 57:     /** @var array or columns to order by */
 58:     protected $order = [];
 59: 
 60:     /** @var int number of rows to fetch */
 61:     protected $limit = NULL;
 62: 
 63:     /** @var int first row to fetch */
 64:     protected $offset = NULL;
 65: 
 66:     /** @var string columns to grouping */
 67:     protected $group = '';
 68: 
 69:     /** @var string grouping condition */
 70:     protected $having = '';
 71: 
 72:     /** @var array of reserved table names associated with chain */
 73:     protected $reservedTableNames = [];
 74: 
 75:     /** @var array of table aliases */
 76:     protected $aliases = [];
 77: 
 78:     /** @var string currently parsing alias for joins */
 79:     protected $currentAlias = NULL;
 80: 
 81:     /** @var ISupplementalDriver */
 82:     private $driver;
 83: 
 84:     /** @var IStructure */
 85:     private $structure;
 86: 
 87:     /** @var array */
 88:     private $cacheTableList;
 89: 
 90:     /** @var array of expanding joins */
 91:     private $expandingJoins = [];
 92: 
 93: 
 94:     public function __construct($tableName, Context $context)
 95:     {
 96:         $this->tableName = $tableName;
 97:         $this->driver = $context->getConnection()->getSupplementalDriver();
 98:         $this->conventions = $context->getConventions();
 99:         $this->structure = $context->getStructure();
100:         $tableNameParts = explode('.', $tableName);
101:         $this->delimitedTable = implode('.', array_map([$this->driver, 'delimite'], $tableNameParts));
102:         $this->checkUniqueTableName(end($tableNameParts), $tableName);
103:     }
104: 
105: 
106:     /**
107:      * @return string
108:      */
109:     public function getTableName()
110:     {
111:         return $this->tableName;
112:     }
113: 
114: 
115:     public function buildInsertQuery()
116:     {
117:         return "INSERT INTO {$this->delimitedTable}";
118:     }
119: 
120: 
121:     public function buildUpdateQuery()
122:     {
123:         $query = "UPDATE {$this->delimitedTable} SET ?set" . $this->tryDelimite($this->buildConditions());
124:         if ($this->limit !== NULL || $this->offset) {
125:             $this->driver->applyLimit($query, $this->limit, $this->offset);
126:         }
127: 
128:         return $query;
129:     }
130: 
131: 
132:     public function buildDeleteQuery()
133:     {
134:         $query = "DELETE FROM {$this->delimitedTable}" . $this->tryDelimite($this->buildConditions());
135:         if ($this->limit !== NULL || $this->offset) {
136:             $this->driver->applyLimit($query, $this->limit, $this->offset);
137:         }
138: 
139:         return $query;
140:     }
141: 
142: 
143:     /**
144:      * Returns select query hash for caching.
145:      * @return string
146:      */
147:     public function getSelectQueryHash($columns = NULL)
148:     {
149:         $parts = [
150:             'delimitedTable' => $this->delimitedTable,
151:             'queryCondition' => $this->buildConditions(),
152:             'queryEnd' => $this->buildQueryEnd(),
153:             $this->aliases,
154:             $this->limit, $this->offset,
155:         ];
156:         if ($this->select) {
157:             $parts[] = $this->select;
158:         } elseif ($columns) {
159:             $parts[] = [$this->delimitedTable, $columns];
160:         } elseif ($this->group && !$this->driver->isSupported(ISupplementalDriver::SUPPORT_SELECT_UNGROUPED_COLUMNS)) {
161:             $parts[] = [$this->group];
162:         } else {
163:             $parts[] = "{$this->delimitedTable}.*";
164:         }
165:         return $this->getConditionHash(json_encode($parts), [
166:             $this->parameters['select'],
167:             $this->parameters['joinCondition'],
168:             $this->parameters['where'],
169:             $this->parameters['group'],
170:             $this->parameters['having'],
171:             $this->parameters['order']
172:         ]);
173:     }
174: 
175: 
176:     /**
177:      * Returns SQL query.
178:      * @param  string list of columns
179:      * @return string
180:      */
181:     public function buildSelectQuery($columns = NULL)
182:     {
183:         if (!$this->order && ($this->limit !== NULL || $this->offset)) {
184:             $this->order = array_map(
185:                 function ($col) { return "$this->tableName.$col"; },
186:                 (array) $this->conventions->getPrimary($this->tableName)
187:             );
188:         }
189: 
190:         $queryJoinConditions = $this->buildJoinConditions();
191:         $queryCondition = $this->buildConditions();
192:         $queryEnd = $this->buildQueryEnd();
193: 
194:         $joins = [];
195:         $finalJoinConditions = $this->parseJoinConditions($joins, $queryJoinConditions);
196:         $this->parseJoins($joins, $queryCondition);
197:         $this->parseJoins($joins, $queryEnd);
198: 
199:         if ($this->select) {
200:             $querySelect = $this->buildSelect($this->select);
201:             $this->parseJoins($joins, $querySelect);
202: 
203:         } elseif ($columns) {
204:             $prefix = $joins ? "{$this->delimitedTable}." : '';
205:             $cols = [];
206:             foreach ($columns as $col) {
207:                 $cols[] = $prefix . $col;
208:             }
209:             $querySelect = $this->buildSelect($cols);
210: 
211:         } elseif ($this->group && !$this->driver->isSupported(ISupplementalDriver::SUPPORT_SELECT_UNGROUPED_COLUMNS)) {
212:             $querySelect = $this->buildSelect([$this->group]);
213:             $this->parseJoins($joins, $querySelect);
214: 
215:         } else {
216:             $prefix = $joins ? "{$this->delimitedTable}." : '';
217:             $querySelect = $this->buildSelect([$prefix . '*']);
218:         }
219: 
220:         $queryJoins = $this->buildQueryJoins($joins, $finalJoinConditions);
221:         $query = "{$querySelect} FROM {$this->delimitedTable}{$queryJoins}{$queryCondition}{$queryEnd}";
222: 
223:         $this->driver->applyLimit($query, $this->limit, $this->offset);
224: 
225:         return $this->tryDelimite($query);
226:     }
227: 
228: 
229:     public function getParameters()
230:     {
231:         if (!isset($this->parameters['joinConditionSorted'])) {
232:             $this->buildSelectQuery();
233:         }
234:         return array_merge(
235:             $this->parameters['select'],
236:             $this->parameters['joinConditionSorted'] ? call_user_func_array('array_merge', $this->parameters['joinConditionSorted']) : [],
237:             $this->parameters['where'],
238:             $this->parameters['group'],
239:             $this->parameters['having'],
240:             $this->parameters['order']
241:         );
242:     }
243: 
244: 
245:     public function importConditions(SqlBuilder $builder)
246:     {
247:         $this->where = $builder->where;
248:         $this->joinCondition = $builder->joinCondition;
249:         $this->parameters['where'] = $builder->parameters['where'];
250:         $this->parameters['joinCondition'] = $builder->parameters['joinCondition'];
251:         $this->conditions = $builder->conditions;
252:         $this->aliases = $builder->aliases;
253:         $this->reservedTableNames = $builder->reservedTableNames;
254:     }
255: 
256: 
257:     /********************* SQL selectors ****************d*g**/
258: 
259: 
260:     public function addSelect($columns, ...$params)
261:     {
262:         if (is_array($columns)) {
263:             throw new Nette\InvalidArgumentException('Select column must be a string.');
264:         }
265:         $this->select[] = $columns;
266:         $this->parameters['select'] = array_merge($this->parameters['select'], $params);
267:     }
268: 
269: 
270:     public function getSelect()
271:     {
272:         return $this->select;
273:     }
274: 
275: 
276:     public function addWhere($condition, ...$params)
277:     {
278:         return $this->addCondition($condition, $params, $this->where, $this->parameters['where']);
279:     }
280: 
281: 
282:     public function addJoinCondition($tableChain, $condition, ...$params)
283:     {
284:         $this->parameters['joinConditionSorted'] = NULL;
285:         if (!isset($this->joinCondition[$tableChain])) {
286:             $this->joinCondition[$tableChain] = $this->parameters['joinCondition'][$tableChain] = [];
287:         }
288:         return $this->addCondition($condition, $params, $this->joinCondition[$tableChain], $this->parameters['joinCondition'][$tableChain]);
289:     }
290: 
291: 
292:     protected function addCondition($condition, array $params, array & $conditions, array & $conditionsParameters)
293:     {
294:         if (is_array($condition) && !empty($params[0]) && is_array($params[0])) {
295:             return $this->addConditionComposition($condition, $params[0], $conditions, $conditionsParameters);
296:         }
297: 
298:         $hash = $this->getConditionHash($condition, $params);
299:         if (isset($this->conditions[$hash])) {
300:             return FALSE;
301:         }
302: 
303:         $this->conditions[$hash] = $condition;
304:         $placeholderCount = substr_count($condition, '?');
305:         if ($placeholderCount > 1 && count($params) === 1 && is_array($params[0])) {
306:             $params = $params[0];
307:         }
308: 
309:         $condition = trim($condition);
310:         if ($placeholderCount === 0 && count($params) === 1) {
311:             $condition .= ' ?';
312:         } elseif ($placeholderCount !== count($params)) {
313:             throw new Nette\InvalidArgumentException('Argument count does not match placeholder count.');
314:         }
315: 
316:         $replace = NULL;
317:         $placeholderNum = 0;
318:         foreach ($params as $arg) {
319:             preg_match('#(?:.*?\?.*?){' . $placeholderNum . '}(((?:&|\||^|~|\+|-|\*|/|%|\(|,|<|>|=|(?<=\W|^)(?:REGEXP|ALL|AND|ANY|BETWEEN|EXISTS|IN|[IR]?LIKE|OR|NOT|SOME|INTERVAL))\s*)?(?:\(\?\)|\?))#s', $condition, $match, PREG_OFFSET_CAPTURE);
320:             $hasOperator = ($match[1][0] === '?' && $match[1][1] === 0) ? TRUE : !empty($match[2][0]);
321: 
322:             if ($arg === NULL) {
323:                 $replace = 'IS NULL';
324:                 if ($hasOperator) {
325:                     if (trim($match[2][0]) === 'NOT') {
326:                         $replace = 'IS NOT NULL';
327:                     } else {
328:                         throw new Nette\InvalidArgumentException('Column operator does not accept NULL argument.');
329:                     }
330:                 }
331:             } elseif (is_array($arg) || $arg instanceof Selection) {
332:                 if ($hasOperator) {
333:                     if (trim($match[2][0]) === 'NOT') {
334:                         $match[2][0] = rtrim($match[2][0]) . ' IN ';
335:                     } elseif (trim($match[2][0]) !== 'IN') {
336:                         throw new Nette\InvalidArgumentException('Column operator does not accept array argument.');
337:                     }
338:                 } else {
339:                     $match[2][0] = 'IN ';
340:                 }
341: 
342:                 if ($arg instanceof Selection) {
343:                     $clone = clone $arg;
344:                     if (!$clone->getSqlBuilder()->select) {
345:                         try {
346:                             $clone->select($clone->getPrimary());
347:                         } catch (\LogicException $e) {
348:                             throw new Nette\InvalidArgumentException('Selection argument must have defined a select column.', 0, $e);
349:                         }
350:                     }
351: 
352:                     if ($this->driver->isSupported(ISupplementalDriver::SUPPORT_SUBSELECT)) {
353:                         $arg = NULL;
354:                         $replace = $match[2][0] . '(' . $clone->getSql() . ')';
355:                         $conditionsParameters = array_merge($conditionsParameters, $clone->getSqlBuilder()->getParameters());
356:                     } else {
357:                         $arg = [];
358:                         foreach ($clone as $row) {
359:                             $arg[] = array_values(iterator_to_array($row));
360:                         }
361:                     }
362:                 }
363: 
364:                 if ($arg !== NULL) {
365:                     if (!$arg) {
366:                         $hasBrackets = strpos($condition, '(') !== FALSE;
367:                         $hasOperators = preg_match('#AND|OR#', $condition);
368:                         $hasNot = strpos($condition, 'NOT') !== FALSE;
369:                         $hasPrefixNot = strpos($match[2][0], 'NOT') !== FALSE;
370:                         if (!$hasBrackets && ($hasOperators || ($hasNot && !$hasPrefixNot))) {
371:                             throw new Nette\InvalidArgumentException('Possible SQL query corruption. Add parentheses around operators.');
372:                         }
373:                         if ($hasPrefixNot) {
374:                             $replace = 'IS NULL OR TRUE';
375:                         } else {
376:                             $replace = 'IS NULL AND FALSE';
377:                         }
378:                         $arg = NULL;
379:                     } else {
380:                         $replace = $match[2][0] . '(?)';
381:                         $conditionsParameters[] = $arg;
382:                     }
383:                 }
384:             } elseif ($arg instanceof SqlLiteral) {
385:                 $conditionsParameters[] = $arg;
386:             } else {
387:                 if (!$hasOperator) {
388:                     $replace = '= ?';
389:                 }
390:                 $conditionsParameters[] = $arg;
391:             }
392: 
393:             if ($replace) {
394:                 $condition = substr_replace($condition, $replace, $match[1][1], strlen($match[1][0]));
395:                 $replace = NULL;
396:             }
397: 
398:             if ($arg !== NULL) {
399:                 $placeholderNum++;
400:             }
401:         }
402: 
403:         $conditions[] = $condition;
404:         return TRUE;
405:     }
406: 
407: 
408:     public function getConditions()
409:     {
410:         return array_values($this->conditions);
411:     }
412: 
413: 
414:     /**
415:      * Adds alias.
416:      * @return void
417:      */
418:     public function addAlias($chain, $alias)
419:     {
420:         if (isset($chain[0]) && $chain[0] !== '.' && $chain[0] !== ':') {
421:             $chain = '.' . $chain; // unified chain format
422:         }
423:         $this->checkUniqueTableName($alias, $chain);
424:         $this->aliases[$alias] = $chain;
425:     }
426: 
427: 
428:     protected function checkUniqueTableName($tableName, $chain)
429:     {
430:         if (isset($this->aliases[$tableName]) && ('.' . $tableName === $chain)) {
431:             $chain = $this->aliases[$tableName];
432:         }
433:         if (isset($this->reservedTableNames[$tableName])) {
434:             if ($this->reservedTableNames[$tableName] === $chain) {
435:                 return;
436:             }
437:             throw new \Nette\InvalidArgumentException("Table alias '$tableName' from chain '$chain' is already in use by chain '{$this->reservedTableNames[$tableName]}'. Please add/change alias for one of them.");
438:         }
439:         $this->reservedTableNames[$tableName] = $chain;
440:     }
441: 
442: 
443:     public function addOrder($columns, ...$params)
444:     {
445:         $this->order[] = $columns;
446:         $this->parameters['order'] = array_merge($this->parameters['order'], $params);
447:     }
448: 
449: 
450:     public function setOrder(array $columns, array $parameters)
451:     {
452:         $this->order = $columns;
453:         $this->parameters['order'] = $parameters;
454:     }
455: 
456: 
457:     public function getOrder()
458:     {
459:         return $this->order;
460:     }
461: 
462: 
463:     public function setLimit($limit, $offset)
464:     {
465:         $this->limit = $limit;
466:         $this->offset = $offset;
467:     }
468: 
469: 
470:     public function getLimit()
471:     {
472:         return $this->limit;
473:     }
474: 
475: 
476:     public function getOffset()
477:     {
478:         return $this->offset;
479:     }
480: 
481: 
482:     public function setGroup($columns, ...$params)
483:     {
484:         $this->group = $columns;
485:         $this->parameters['group'] = $params;
486:     }
487: 
488: 
489:     public function getGroup()
490:     {
491:         return $this->group;
492:     }
493: 
494: 
495:     public function setHaving($having, ...$params)
496:     {
497:         $this->having = $having;
498:         $this->parameters['having'] = $params;
499:     }
500: 
501: 
502:     public function getHaving()
503:     {
504:         return $this->having;
505:     }
506: 
507: 
508:     /********************* SQL building ****************d*g**/
509: 
510: 
511:     protected function buildSelect(array $columns)
512:     {
513:         return 'SELECT ' . implode(', ', $columns);
514:     }
515: 
516: 
517:     protected function parseJoinConditions(& $joins, $joinConditions)
518:     {
519:         $tableJoins = $leftJoinDependency = $finalJoinConditions = [];
520:         foreach ($joinConditions as $tableChain => & $joinCondition) {
521:             $fooQuery = $tableChain . '.foo';
522:             $requiredJoins = [];
523:             $this->parseJoins($requiredJoins, $fooQuery);
524:             $tableAlias = substr($fooQuery, 0, -4);
525:             $tableJoins[$tableAlias] = $requiredJoins;
526:             $leftJoinDependency[$tableAlias] = [];
527:             $finalJoinConditions[$tableAlias] = preg_replace_callback($this->getColumnChainsRegxp(), function ($match) use ($tableAlias, & $tableJoins, & $leftJoinDependency) {
528:                 $requiredJoins = [];
529:                 $query = $this->parseJoinsCb($requiredJoins, $match);
530:                 $queryParts = explode('.', $query);
531:                 $tableJoins[$queryParts[0]] = $requiredJoins;
532:                 if ($queryParts[0] !== $tableAlias) {
533:                     foreach (array_keys($requiredJoins) as $requiredTable) {
534:                         $leftJoinDependency[$tableAlias][$requiredTable] = $requiredTable;
535:                     }
536:                 }
537:                 return $query;
538:             }, $joinCondition);
539:         }
540:         $this->parameters['joinConditionSorted'] = [];
541:         if (count($joinConditions)) {
542:             while (reset($tableJoins)) {
543:                 $this->getSortedJoins(key($tableJoins), $leftJoinDependency, $tableJoins, $joins);
544:             }
545:         }
546:         return $finalJoinConditions;
547:     }
548: 
549: 
550:     protected function getSortedJoins($table, & $leftJoinDependency, & $tableJoins, & $finalJoins)
551:     {
552:         if (isset($this->expandingJoins[$table])) {
553:             $path = implode("' => '", array_map(function($value) { return $this->reservedTableNames[$value]; }, array_merge(array_keys($this->expandingJoins), [$table])));
554:             throw new Nette\InvalidArgumentException("Circular reference detected at left join conditions (tables '$path').");
555:         }
556:         if (isset($tableJoins[$table])) {
557:             $this->expandingJoins[$table] = TRUE;
558:             if (isset($leftJoinDependency[$table])) {
559:                 foreach ($leftJoinDependency[$table] as $requiredTable) {
560:                     if ($requiredTable === $table) {
561:                         continue;
562:                     }
563:                     $this->getSortedJoins($requiredTable, $leftJoinDependency, $tableJoins, $finalJoins);
564:                 }
565:             }
566:             if ($tableJoins[$table]) {
567:                 foreach ($tableJoins[$table] as $requiredTable => $tmp) {
568:                     if ($requiredTable === $table) {
569:                         continue;
570:                     }
571:                     $this->getSortedJoins($requiredTable, $leftJoinDependency, $tableJoins, $finalJoins);
572:                 }
573:             }
574:             $finalJoins += $tableJoins[$table];
575:             $this->parameters['joinConditionSorted'] += isset($this->parameters['joinCondition'][$this->reservedTableNames[$table]])
576:                 ? [$table => $this->parameters['joinCondition'][$this->reservedTableNames[$table]]]
577:                 : [];
578:             unset($tableJoins[$table]);
579:             unset($this->expandingJoins[$table]);
580:         }
581:     }
582: 
583: 
584:     protected function parseJoins(& $joins, & $query)
585:     {
586:         $query = preg_replace_callback($this->getColumnChainsRegxp(), function ($match) use (& $joins) {
587:             return $this->parseJoinsCb($joins, $match);
588:         }, $query);
589:     }
590: 
591: 
592:     private function getColumnChainsRegxp()
593:     {
594:         return '~
595:             (?(DEFINE)
596:                 (?P<word> [\w_]*[a-z][\w_]* )
597:                 (?P<del> [.:] )
598:                 (?P<node> (?&del)? (?&word) (\((?&word)\))? )
599:             )
600:             (?P<chain> (?!\.) (?&node)*)  \. (?P<column> (?&word) | \*  )
601:         ~xi';
602:     }
603: 
604: 
605:     public function parseJoinsCb(& $joins, $match)
606:     {
607:         $chain = $match['chain'];
608:         if (!empty($chain[0]) && ($chain[0] !== '.' && $chain[0] !== ':')) {
609:             $chain = '.' . $chain;  // unified chain format
610:         }
611: 
612:         preg_match_all('~
613:             (?(DEFINE)
614:                 (?P<word> [\w_]*[a-z][\w_]* )
615:             )
616:             (?P<del> [.:])?(?P<key> (?&word))(\((?P<throughColumn> (?&word))\))?
617:         ~xi', $chain, $keyMatches, PREG_SET_ORDER);
618: 
619:         $parent = $this->tableName;
620:         $parentAlias = preg_replace('#^(.*\.)?(.*)$#', '$2', $this->tableName);
621: 
622:         // join schema keyMatch and table keyMatch to schema.table keyMatch
623:         if ($this->driver->isSupported(ISupplementalDriver::SUPPORT_SCHEMA) && count($keyMatches) > 1) {
624:             $tables = $this->getCachedTableList();
625:             if (!isset($tables[$keyMatches[0]['key']]) && isset($tables[$keyMatches[0]['key'] . '.' . $keyMatches[1]['key']])) {
626:                 $keyMatch = array_shift($keyMatches);
627:                 $keyMatches[0]['key'] = $keyMatch['key'] . '.' . $keyMatches[0]['key'];
628:                 $keyMatches[0]['del'] = $keyMatch['del'];
629:             }
630:         }
631: 
632:         // do not make a join when referencing to the current table column - inner conditions
633:         // check it only when not making backjoin on itself - outer condition
634:         if ($keyMatches[0]['del'] === '.') {
635:             if (count($keyMatches) > 1 && ($parent === $keyMatches[0]['key'] || $parentAlias === $keyMatches[0]['key'])) {
636:                 throw new Nette\InvalidArgumentException("Do not prefix table chain with origin table name '{$keyMatches[0]['key']}'. If you want to make self reference, please add alias.");
637:             }
638:             if ($parent === $keyMatches[0]['key']) {
639:                 return "{$parent}.{$match['column']}";
640:             } elseif ($parentAlias === $keyMatches[0]['key']) {
641:                 return "{$parentAlias}.{$match['column']}";
642:             }
643:         }
644:         $tableChain = NULL;
645:         foreach ($keyMatches as $index => $keyMatch) {
646:             $isLast = !isset($keyMatches[$index + 1]);
647:             if (!$index && isset($this->aliases[$keyMatch['key']])) {
648:                 if ($keyMatch['del'] === ':') {
649:                     throw new Nette\InvalidArgumentException("You are using has many syntax with alias (':{$keyMatch['key']}'). You have to move it to alias definition.");
650:                 } else {
651:                     $previousAlias = $this->currentAlias;
652:                     $this->currentAlias = $keyMatch['key'];
653:                     $requiredJoins = [];
654:                     $query = $this->aliases[$keyMatch['key']] . '.foo';
655:                     $this->parseJoins($requiredJoins, $query);
656:                     $aliasJoin = array_pop($requiredJoins);
657:                     $joins += $requiredJoins;
658:                     list($table, , $parentAlias, $column, $primary) = $aliasJoin;
659:                     $this->currentAlias = $previousAlias;
660:                 }
661:             } elseif ($keyMatch['del'] === ':') {
662:                 if (isset($keyMatch['throughColumn'])) {
663:                     $table = $keyMatch['key'];
664:                     $belongsTo = $this->conventions->getBelongsToReference($table, $keyMatch['throughColumn']);
665:                     if (!$belongsTo) {
666:                         throw new Nette\InvalidArgumentException("No reference found for \${$parent}->{$keyMatch['key']}.");
667:                     }
668:                     list(, $primary) = $belongsTo;
669: 
670:                 } else {
671:                     $hasMany = $this->conventions->getHasManyReference($parent, $keyMatch['key']);
672:                     if (!$hasMany) {
673:                         throw new Nette\InvalidArgumentException("No reference found for \${$parent}->related({$keyMatch['key']}).");
674:                     }
675:                     list($table, $primary) = $hasMany;
676:                 }
677:                 $column = $this->conventions->getPrimary($parent);
678: 
679:             } else {
680:                 $belongsTo = $this->conventions->getBelongsToReference($parent, $keyMatch['key']);
681:                 if (!$belongsTo) {
682:                     throw new Nette\InvalidArgumentException("No reference found for \${$parent}->{$keyMatch['key']}.");
683:                 }
684:                 list($table, $column) = $belongsTo;
685:                 $primary = $this->conventions->getPrimary($table);
686:             }
687: 
688:             if ($this->currentAlias && $isLast) {
689:                 $tableAlias = $this->currentAlias;
690:             } elseif ($parent === $table) {
691:                 $tableAlias = $parentAlias . '_ref';
692:             } elseif ($keyMatch['key']) {
693:                 $tableAlias = $keyMatch['key'];
694:             } else {
695:                 $tableAlias = preg_replace('#^(.*\.)?(.*)$#', '$2', $table);
696:             }
697: 
698:             $tableChain .= $keyMatch[0];
699:             if (!$isLast || !$this->currentAlias) {
700:                 $this->checkUniqueTableName($tableAlias, $tableChain);
701:             }
702:             $joins[$tableAlias] = [$table, $tableAlias, $parentAlias, $column, $primary];
703:             $parent = $table;
704:             $parentAlias = $tableAlias;
705:         }
706: 
707:         return $tableAlias . ".{$match['column']}";
708:     }
709: 
710: 
711:     protected function buildQueryJoins(array $joins, array $leftJoinConditions = [])
712:     {
713:         $return = '';
714:         foreach ($joins as list($joinTable, $joinAlias, $table, $tableColumn, $joinColumn)) {
715:             $return .=
716:                 " LEFT JOIN {$joinTable}" . ($joinTable !== $joinAlias ? " {$joinAlias}" : '') .
717:                 " ON {$table}.{$tableColumn} = {$joinAlias}.{$joinColumn}" .
718:                 (isset($leftJoinConditions[$joinAlias]) ? " {$leftJoinConditions[$joinAlias]}" : '');
719:         }
720:         return $return;
721:     }
722: 
723: 
724:     protected function buildJoinConditions()
725:     {
726:         $conditions = [];
727:         foreach ($this->joinCondition as $tableChain => $joinConditions) {
728:             $conditions[$tableChain] = 'AND (' . implode(') AND (', $joinConditions) . ')';
729:         }
730:         return $conditions;
731:     }
732: 
733: 
734:     protected function buildConditions()
735:     {
736:         return $this->where ? ' WHERE (' . implode(') AND (', $this->where) . ')' : '';
737:     }
738: 
739: 
740:     protected function buildQueryEnd()
741:     {
742:         $return = '';
743:         if ($this->group) {
744:             $return .= ' GROUP BY '. $this->group;
745:         }
746:         if ($this->having) {
747:             $return .= ' HAVING '. $this->having;
748:         }
749:         if ($this->order) {
750:             $return .= ' ORDER BY ' . implode(', ', $this->order);
751:         }
752:         return $return;
753:     }
754: 
755: 
756:     protected function tryDelimite($s)
757:     {
758:         return preg_replace_callback('#(?<=[^\w`"\[?]|^)[a-z_][a-z0-9_]*(?=[^\w`"(\]]|\z)#i', function ($m) {
759:             return strtoupper($m[0]) === $m[0] ? $m[0] : $this->driver->delimite($m[0]);
760:         }, $s);
761:     }
762: 
763: 
764:     protected function addConditionComposition(array $columns, array $parameters, array & $conditions, array & $conditionsParameters)
765:     {
766:         if ($this->driver->isSupported(ISupplementalDriver::SUPPORT_MULTI_COLUMN_AS_OR_COND)) {
767:             $conditionFragment = '(' . implode(' = ? AND ', $columns) . ' = ?) OR ';
768:             $condition = substr(str_repeat($conditionFragment, count($parameters)), 0, -4);
769:             return $this->addCondition($condition, [Nette\Utils\Arrays::flatten($parameters)], $conditions, $conditionsParameters);
770:         } else {
771:             return $this->addCondition('(' . implode(', ', $columns) . ') IN', [$parameters], $conditions, $conditionsParameters);
772:         }
773:     }
774: 
775: 
776:     private function getConditionHash($condition, $parameters)
777:     {
778:         foreach ($parameters as $key => & $parameter) {
779:             if ($parameter instanceof Selection) {
780:                 $parameter = $this->getConditionHash($parameter->getSql(), $parameter->getSqlBuilder()->getParameters());
781:             } elseif ($parameter instanceof SqlLiteral) {
782:                 $parameter = $this->getConditionHash($parameter->__toString(), $parameter->getParameters());
783:             } elseif (is_object($parameter) && method_exists($parameter, '__toString')) {
784:                 $parameter = $parameter->__toString();
785:             } elseif (is_array($parameter) || $parameter instanceof \ArrayAccess) {
786:                 $parameter = $this->getConditionHash($key, $parameter);
787:             }
788:         }
789:         return md5($condition . json_encode($parameters));
790:     }
791: 
792: 
793:     private function getCachedTableList()
794:     {
795:         if (!$this->cacheTableList) {
796:             $this->cacheTableList = array_flip(array_map(function ($pair) {
797:                 return isset($pair['fullName']) ? $pair['fullName'] : $pair['name'];
798:             }, $this->structure->getTables()));
799:         }
800: 
801:         return $this->cacheTableList;
802:     }
803: 
804: }
805: 
Nette 2.4-20170119 API API documentation generated by ApiGen 2.8.0