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