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
      • Reflection
      • 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
  • Other releases
  • Nette homepage
   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\Context;
  12: use Nette\Database\IConventions;
  13: 
  14: 
  15: /**
  16:  * Filtered table representation.
  17:  * Selection is based on the great library NotORM http://www.notorm.com written by Jakub Vrana.
  18:  *
  19:  * @property-read string $sql
  20:  */
  21: class Selection extends Nette\Object implements \Iterator, IRowContainer, \ArrayAccess, \Countable
  22: {
  23:     /** @var Context */
  24:     protected $context;
  25: 
  26:     /** @var IConventions */
  27:     protected $conventions;
  28: 
  29:     /** @var Nette\Caching\Cache */
  30:     protected $cache;
  31: 
  32:     /** @var SqlBuilder */
  33:     protected $sqlBuilder;
  34: 
  35:     /** @var string table name */
  36:     protected $name;
  37: 
  38:     /** @var string|array|NULL primary key field name */
  39:     protected $primary;
  40: 
  41:     /** @var string|bool primary column sequence name, FALSE for autodetection */
  42:     protected $primarySequence = FALSE;
  43: 
  44:     /** @var IRow[] data read from database in [primary key => IRow] format */
  45:     protected $rows;
  46: 
  47:     /** @var IRow[] modifiable data in [primary key => IRow] format */
  48:     protected $data;
  49: 
  50:     /** @var bool */
  51:     protected $dataRefreshed = FALSE;
  52: 
  53:     /** @var mixed cache array of Selection and GroupedSelection prototypes */
  54:     protected $globalRefCache;
  55: 
  56:     /** @var mixed */
  57:     protected $refCache;
  58: 
  59:     /** @var string */
  60:     protected $generalCacheKey;
  61: 
  62:     /** @var array */
  63:     protected $generalCacheTraceKey;
  64: 
  65:     /** @var string */
  66:     protected $specificCacheKey;
  67: 
  68:     /** @var array of [conditions => [key => IRow]]; used by GroupedSelection */
  69:     protected $aggregation = array();
  70: 
  71:     /** @var array of touched columns */
  72:     protected $accessedColumns;
  73: 
  74:     /** @var array of earlier touched columns */
  75:     protected $previousAccessedColumns;
  76: 
  77:     /** @var bool should instance observe accessed columns caching */
  78:     protected $observeCache = FALSE;
  79: 
  80:     /** @var array of primary key values */
  81:     protected $keys = array();
  82: 
  83: 
  84:     /**
  85:      * Creates filtered table representation.
  86:      * @param  Context
  87:      * @param  IConventions
  88:      * @param  string  table name
  89:      * @param  Nette\Caching\IStorage|NULL
  90:      */
  91:     public function __construct(Context $context, IConventions $conventions, $tableName, Nette\Caching\IStorage $cacheStorage = NULL)
  92:     {
  93:         $this->context = $context;
  94:         $this->conventions = $conventions;
  95:         $this->name = $tableName;
  96: 
  97:         $this->cache = $cacheStorage ? new Nette\Caching\Cache($cacheStorage, 'Nette.Database.' . md5($context->getConnection()->getDsn())) : NULL;
  98:         $this->primary = $conventions->getPrimary($tableName);
  99:         $this->sqlBuilder = new SqlBuilder($tableName, $context);
 100:         $this->refCache = & $this->getRefTable($refPath)->globalRefCache[$refPath];
 101:     }
 102: 
 103: 
 104:     public function __destruct()
 105:     {
 106:         $this->saveCacheState();
 107:     }
 108: 
 109: 
 110:     public function __clone()
 111:     {
 112:         $this->sqlBuilder = clone $this->sqlBuilder;
 113:     }
 114: 
 115: 
 116:     /** @deprecated */
 117:     public function getConnection()
 118:     {
 119:         trigger_error(__METHOD__ . '() is deprecated; use DI container to autowire Nette\Database\Connection instead.', E_USER_DEPRECATED);
 120:         return $this->context->getConnection();
 121:     }
 122: 
 123: 
 124:     /** @deprecated */
 125:     public function getDatabaseReflection()
 126:     {
 127:         trigger_error(__METHOD__ . '() is deprecated; use DI container to autowire Nette\Database\IConventions instead.', E_USER_DEPRECATED);
 128:         return $this->conventions;
 129:     }
 130: 
 131: 
 132:     /**
 133:      * @return string
 134:      */
 135:     public function getName()
 136:     {
 137:         return $this->name;
 138:     }
 139: 
 140: 
 141:     /**
 142:      * @param  bool
 143:      * @return string|array|NULL
 144:      */
 145:     public function getPrimary($need = TRUE)
 146:     {
 147:         if ($this->primary === NULL && $need) {
 148:             throw new \LogicException("Table '{$this->name}' does not have a primary key.");
 149:         }
 150:         return $this->primary;
 151:     }
 152: 
 153: 
 154:     /**
 155:      * @return string
 156:      */
 157:     public function getPrimarySequence()
 158:     {
 159:         if ($this->primarySequence === FALSE) {
 160:             $this->primarySequence = $this->context->getStructure()->getPrimaryKeySequence($this->name);
 161:         }
 162: 
 163:         return $this->primarySequence;
 164:     }
 165: 
 166: 
 167:     /**
 168:      * @param  string
 169:      * @return self
 170:      */
 171:     public function setPrimarySequence($sequence)
 172:     {
 173:         $this->primarySequence = $sequence;
 174:         return $this;
 175:     }
 176: 
 177: 
 178:     /**
 179:      * @return string
 180:      */
 181:     public function getSql()
 182:     {
 183:         return $this->sqlBuilder->buildSelectQuery($this->getPreviousAccessedColumns());
 184:     }
 185: 
 186: 
 187:     /**
 188:      * Loads cache of previous accessed columns and returns it.
 189:      * @internal
 190:      * @return array|false
 191:      */
 192:     public function getPreviousAccessedColumns()
 193:     {
 194:         if ($this->cache && $this->previousAccessedColumns === NULL) {
 195:             $this->accessedColumns = $this->previousAccessedColumns = $this->cache->load($this->getGeneralCacheKey());
 196:             if ($this->previousAccessedColumns === NULL) {
 197:                 $this->previousAccessedColumns = array();
 198:             }
 199:         }
 200: 
 201:         return array_keys(array_filter((array) $this->previousAccessedColumns));
 202:     }
 203: 
 204: 
 205:     /**
 206:      * @internal
 207:      * @return SqlBuilder
 208:      */
 209:     public function getSqlBuilder()
 210:     {
 211:         return $this->sqlBuilder;
 212:     }
 213: 
 214: 
 215:     /********************* quick access ****************d*g**/
 216: 
 217: 
 218:     /**
 219:      * Returns row specified by primary key.
 220:      * @param  mixed primary key
 221:      * @return IRow or FALSE if there is no such row
 222:      */
 223:     public function get($key)
 224:     {
 225:         $clone = clone $this;
 226:         return $clone->wherePrimary($key)->fetch();
 227:     }
 228: 
 229: 
 230:     /**
 231:      * @inheritDoc
 232:      */
 233:     public function fetch()
 234:     {
 235:         $this->execute();
 236:         $return = current($this->data);
 237:         next($this->data);
 238:         return $return;
 239:     }
 240: 
 241: 
 242:     /**
 243:      * Fetches single field.
 244:      * @param  string|NULL
 245:      * @return mixed|FALSE
 246:      */
 247:     public function fetchField($column = NULL)
 248:     {
 249:         if ($column) {
 250:             $this->select($column);
 251:         }
 252: 
 253:         $row = $this->fetch();
 254:         if ($row) {
 255:             return $column ? $row[$column] : current($row->toArray());
 256:         }
 257: 
 258:         return FALSE;
 259:     }
 260: 
 261: 
 262:     /**
 263:      * @inheritDoc
 264:      */
 265:     public function fetchPairs($key = NULL, $value = NULL)
 266:     {
 267:         return Nette\Database\Helpers::toPairs($this->fetchAll(), $key, $value);
 268:     }
 269: 
 270: 
 271:     /**
 272:      * @inheritDoc
 273:      */
 274:     public function fetchAll()
 275:     {
 276:         return iterator_to_array($this);
 277:     }
 278: 
 279: 
 280:     /**
 281:      * @inheritDoc
 282:      */
 283:     public function fetchAssoc($path)
 284:     {
 285:         $rows = array_map('iterator_to_array', $this->fetchAll());
 286:         return Nette\Utils\Arrays::associate($rows, $path);
 287:     }
 288: 
 289: 
 290:     /********************* sql selectors ****************d*g**/
 291: 
 292: 
 293:     /**
 294:      * Adds select clause, more calls appends to the end.
 295:      * @param  string for example "column, MD5(column) AS column_md5"
 296:      * @return self
 297:      */
 298:     public function select($columns)
 299:     {
 300:         $this->emptyResultSet();
 301:         call_user_func_array(array($this->sqlBuilder, 'addSelect'), func_get_args());
 302:         return $this;
 303:     }
 304: 
 305: 
 306:     /**
 307:      * Adds condition for primary key.
 308:      * @param  mixed
 309:      * @return self
 310:      */
 311:     public function wherePrimary($key)
 312:     {
 313:         if (is_array($this->primary) && Nette\Utils\Arrays::isList($key)) {
 314:             if (isset($key[0]) && is_array($key[0])) {
 315:                 $this->where($this->primary, $key);
 316:             } else {
 317:                 foreach ($this->primary as $i => $primary) {
 318:                     $this->where($this->name . '.' . $primary, $key[$i]);
 319:                 }
 320:             }
 321:         } elseif (is_array($key) && !Nette\Utils\Arrays::isList($key)) { // key contains column names
 322:             $this->where($key);
 323:         } else {
 324:             $this->where($this->name . '.' . $this->getPrimary(), $key);
 325:         }
 326: 
 327:         return $this;
 328:     }
 329: 
 330: 
 331:     /**
 332:      * Adds where condition, more calls appends with AND.
 333:      * @param  string condition possibly containing ?
 334:      * @param  mixed
 335:      * @param  mixed ...
 336:      * @return self
 337:      */
 338:     public function where($condition, $parameters = array())
 339:     {
 340:         if (is_array($condition) && $parameters === array()) { // where(array('column1' => 1, 'column2 > ?' => 2))
 341:             foreach ($condition as $key => $val) {
 342:                 if (is_int($key)) {
 343:                     $this->where($val); // where('full condition')
 344:                 } else {
 345:                     $this->where($key, $val); // where('column', 1)
 346:                 }
 347:             }
 348:             return $this;
 349:         }
 350: 
 351:         $this->emptyResultSet();
 352:         call_user_func_array(array($this->sqlBuilder, 'addWhere'), func_get_args());
 353:         return $this;
 354:     }
 355: 
 356: 
 357:     /**
 358:      * Adds order clause, more calls appends to the end.
 359:      * @param  string for example 'column1, column2 DESC'
 360:      * @return self
 361:      */
 362:     public function order($columns)
 363:     {
 364:         $this->emptyResultSet();
 365:         call_user_func_array(array($this->sqlBuilder, 'addOrder'), func_get_args());
 366:         return $this;
 367:     }
 368: 
 369: 
 370:     /**
 371:      * Sets limit clause, more calls rewrite old values.
 372:      * @param  int
 373:      * @param  int
 374:      * @return self
 375:      */
 376:     public function limit($limit, $offset = NULL)
 377:     {
 378:         $this->emptyResultSet();
 379:         $this->sqlBuilder->setLimit($limit, $offset);
 380:         return $this;
 381:     }
 382: 
 383: 
 384:     /**
 385:      * Sets offset using page number, more calls rewrite old values.
 386:      * @param  int
 387:      * @param  int
 388:      * @return self
 389:      */
 390:     public function page($page, $itemsPerPage, & $numOfPages = NULL)
 391:     {
 392:         if (func_num_args() > 2) {
 393:             $numOfPages = (int) ceil($this->count('*') / $itemsPerPage);
 394:         }
 395:         return $this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
 396:     }
 397: 
 398: 
 399:     /**
 400:      * Sets group clause, more calls rewrite old value.
 401:      * @param  string
 402:      * @return self
 403:      */
 404:     public function group($columns)
 405:     {
 406:         $this->emptyResultSet();
 407:         call_user_func_array(array($this->sqlBuilder, 'setGroup'), func_get_args());
 408:         return $this;
 409:     }
 410: 
 411: 
 412:     /**
 413:      * Sets having clause, more calls rewrite old value.
 414:      * @param  string
 415:      * @return self
 416:      */
 417:     public function having($having)
 418:     {
 419:         $this->emptyResultSet();
 420:         call_user_func_array(array($this->sqlBuilder, 'setHaving'), func_get_args());
 421:         return $this;
 422:     }
 423: 
 424: 
 425:     /********************* aggregations ****************d*g**/
 426: 
 427: 
 428:     /**
 429:      * Executes aggregation function.
 430:      * @param  string select call in "FUNCTION(column)" format
 431:      * @return string
 432:      */
 433:     public function aggregation($function)
 434:     {
 435:         $selection = $this->createSelectionInstance();
 436:         $selection->getSqlBuilder()->importConditions($this->getSqlBuilder());
 437:         $selection->select($function);
 438:         foreach ($selection->fetch() as $val) {
 439:             return $val;
 440:         }
 441:     }
 442: 
 443: 
 444:     /**
 445:      * Counts number of rows.
 446:      * @param  string  if it is not provided returns count of result rows, otherwise runs new sql counting query
 447:      * @return int
 448:      */
 449:     public function count($column = NULL)
 450:     {
 451:         if (!$column) {
 452:             $this->execute();
 453:             return count($this->data);
 454:         }
 455:         return $this->aggregation("COUNT($column)");
 456:     }
 457: 
 458: 
 459:     /**
 460:      * Returns minimum value from a column.
 461:      * @param  string
 462:      * @return int
 463:      */
 464:     public function min($column)
 465:     {
 466:         return $this->aggregation("MIN($column)");
 467:     }
 468: 
 469: 
 470:     /**
 471:      * Returns maximum value from a column.
 472:      * @param  string
 473:      * @return int
 474:      */
 475:     public function max($column)
 476:     {
 477:         return $this->aggregation("MAX($column)");
 478:     }
 479: 
 480: 
 481:     /**
 482:      * Returns sum of values in a column.
 483:      * @param  string
 484:      * @return int
 485:      */
 486:     public function sum($column)
 487:     {
 488:         return $this->aggregation("SUM($column)");
 489:     }
 490: 
 491: 
 492:     /********************* internal ****************d*g**/
 493: 
 494: 
 495:     protected function execute()
 496:     {
 497:         if ($this->rows !== NULL) {
 498:             return;
 499:         }
 500: 
 501:         $this->observeCache = $this;
 502: 
 503:         if ($this->primary === NULL && $this->sqlBuilder->getSelect() === NULL) {
 504:             throw new Nette\InvalidStateException('Table with no primary key requires an explicit select clause.');
 505:         }
 506: 
 507:         try {
 508:             $result = $this->query($this->getSql());
 509: 
 510:         } catch (Nette\Database\DriverException $exception) {
 511:             if (!$this->sqlBuilder->getSelect() && $this->previousAccessedColumns) {
 512:                 $this->previousAccessedColumns = FALSE;
 513:                 $this->accessedColumns = array();
 514:                 $result = $this->query($this->getSql());
 515:             } else {
 516:                 throw $exception;
 517:             }
 518:         }
 519: 
 520:         $this->rows = array();
 521:         $usedPrimary = TRUE;
 522:         foreach ($result->getPdoStatement() as $key => $row) {
 523:             $row = $this->createRow($result->normalizeRow($row));
 524:             $primary = $row->getSignature(FALSE);
 525:             $usedPrimary = $usedPrimary && (string) $primary !== '';
 526:             $this->rows[$usedPrimary ? $primary : $key] = $row;
 527:         }
 528:         $this->data = $this->rows;
 529: 
 530:         if ($usedPrimary && $this->accessedColumns !== FALSE) {
 531:             foreach ((array) $this->primary as $primary) {
 532:                 $this->accessedColumns[$primary] = TRUE;
 533:             }
 534:         }
 535:     }
 536: 
 537: 
 538:     protected function createRow(array $row)
 539:     {
 540:         return new ActiveRow($row, $this);
 541:     }
 542: 
 543: 
 544:     public function createSelectionInstance($table = NULL)
 545:     {
 546:         return new self($this->context, $this->conventions, $table ?: $this->name, $this->cache ? $this->cache->getStorage() : NULL);
 547:     }
 548: 
 549: 
 550:     protected function createGroupedSelectionInstance($table, $column)
 551:     {
 552:         return new GroupedSelection($this->context, $this->conventions, $table, $column, $this, $this->cache ? $this->cache->getStorage() : NULL);
 553:     }
 554: 
 555: 
 556:     protected function query($query)
 557:     {
 558:         return $this->context->queryArgs($query, $this->sqlBuilder->getParameters());
 559:     }
 560: 
 561: 
 562:     protected function emptyResultSet($saveCache = TRUE, $deleteRererencedCache = TRUE)
 563:     {
 564:         if ($this->rows !== NULL && $saveCache) {
 565:             $this->saveCacheState();
 566:         }
 567: 
 568:         if ($saveCache) {
 569:             // null only if missing some column
 570:             $this->generalCacheTraceKey = NULL;
 571:         }
 572: 
 573:         $this->rows = NULL;
 574:         $this->specificCacheKey = NULL;
 575:         $this->generalCacheKey = NULL;
 576:         $this->refCache['referencingPrototype'] = array();
 577:         if ($deleteRererencedCache) {
 578:             $this->refCache['referenced'] = array();
 579:         }
 580:     }
 581: 
 582: 
 583:     protected function saveCacheState()
 584:     {
 585:         if ($this->observeCache === $this && $this->cache && !$this->sqlBuilder->getSelect() && $this->accessedColumns !== $this->previousAccessedColumns) {
 586:             $previousAccessed = $this->cache->load($this->getGeneralCacheKey());
 587:             $accessed = $this->accessedColumns;
 588:             $needSave = is_array($accessed) && is_array($previousAccessed)
 589:                 ? array_intersect_key($accessed, $previousAccessed) !== $accessed
 590:                 : $accessed !== $previousAccessed;
 591: 
 592:             if ($needSave) {
 593:                 $save = is_array($accessed) && is_array($previousAccessed) ? $previousAccessed + $accessed : $accessed;
 594:                 $this->cache->save($this->getGeneralCacheKey(), $save);
 595:                 $this->previousAccessedColumns = NULL;
 596:             }
 597:         }
 598:     }
 599: 
 600: 
 601:     /**
 602:      * Returns Selection parent for caching.
 603:      * @return Selection
 604:      */
 605:     protected function getRefTable(& $refPath)
 606:     {
 607:         return $this;
 608:     }
 609: 
 610: 
 611:     /**
 612:      * Loads refCache references
 613:      */
 614:     protected function loadRefCache()
 615:     {
 616:     }
 617: 
 618: 
 619:     /**
 620:      * Returns general cache key independent on query parameters or sql limit
 621:      * Used e.g. for previously accessed columns caching
 622:      * @return string
 623:      */
 624:     protected function getGeneralCacheKey()
 625:     {
 626:         if ($this->generalCacheKey) {
 627:             return $this->generalCacheKey;
 628:         }
 629: 
 630:         $key = array(__CLASS__, $this->name, $this->sqlBuilder->getConditions());
 631:         if (!$this->generalCacheTraceKey) {
 632:             $trace = array();
 633:             foreach (debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) as $item) {
 634:                 $trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : NULL;
 635:             };
 636:             $this->generalCacheTraceKey = $trace;
 637:         }
 638: 
 639:         $key[] = $this->generalCacheTraceKey;
 640:         return $this->generalCacheKey = md5(serialize($key));
 641:     }
 642: 
 643: 
 644:     /**
 645:      * Returns object specific cache key dependent on query parameters
 646:      * Used e.g. for reference memory caching
 647:      * @return string
 648:      */
 649:     protected function getSpecificCacheKey()
 650:     {
 651:         if ($this->specificCacheKey) {
 652:             return $this->specificCacheKey;
 653:         }
 654: 
 655:         return $this->specificCacheKey = md5($this->getSql() . json_encode($this->sqlBuilder->getParameters()));
 656:     }
 657: 
 658: 
 659:     /**
 660:      * @internal
 661:      * @param  string|NULL column name or NULL to reload all columns
 662:      * @param  bool
 663:      * @return bool if selection requeried for more columns.
 664:      */
 665:     public function accessColumn($key, $selectColumn = TRUE)
 666:     {
 667:         if (!$this->cache) {
 668:             return FALSE;
 669:         }
 670: 
 671:         if ($key === NULL) {
 672:             $this->accessedColumns = FALSE;
 673:             $currentKey = key((array) $this->data);
 674:         } elseif ($this->accessedColumns !== FALSE) {
 675:             $this->accessedColumns[$key] = $selectColumn;
 676:         }
 677: 
 678:         if ($selectColumn && $this->previousAccessedColumns && ($key === NULL || !isset($this->previousAccessedColumns[$key])) && !$this->sqlBuilder->getSelect()) {
 679:             $this->previousAccessedColumns = array();
 680: 
 681:             if ($this->sqlBuilder->getLimit()) {
 682:                 $generalCacheKey = $this->generalCacheKey;
 683:                 $sqlBuilder = $this->sqlBuilder;
 684: 
 685:                 $primaryValues = array();
 686:                 foreach ((array) $this->rows as $row) {
 687:                     $primary = $row->getPrimary();
 688:                     $primaryValues[] = is_array($primary) ? array_values($primary) : $primary;
 689:                 }
 690: 
 691:                 $this->emptyResultSet(FALSE);
 692:                 $this->sqlBuilder = clone $this->sqlBuilder;
 693:                 $this->sqlBuilder->setLimit(NULL, NULL);
 694:                 $this->wherePrimary($primaryValues);
 695: 
 696:                 $this->generalCacheKey = $generalCacheKey;
 697:                 $this->execute();
 698:                 $this->sqlBuilder = $sqlBuilder;
 699:             } else {
 700:                 $this->emptyResultSet(FALSE);
 701:                 $this->execute();
 702:             }
 703: 
 704:             $this->dataRefreshed = TRUE;
 705: 
 706:             // move iterator to specific key
 707:             if (isset($currentKey)) {
 708:                 while (key($this->data) !== NULL && key($this->data) !== $currentKey) {
 709:                     next($this->data);
 710:                 }
 711:             }
 712:         }
 713:         return $this->dataRefreshed;
 714:     }
 715: 
 716: 
 717:     /**
 718:      * @internal
 719:      * @param  string
 720:      */
 721:     public function removeAccessColumn($key)
 722:     {
 723:         if ($this->cache && is_array($this->accessedColumns)) {
 724:             $this->accessedColumns[$key] = FALSE;
 725:         }
 726:     }
 727: 
 728: 
 729:     /**
 730:      * Returns if selection requeried for more columns.
 731:      * @return bool
 732:      */
 733:     public function getDataRefreshed()
 734:     {
 735:         return $this->dataRefreshed;
 736:     }
 737: 
 738: 
 739:     /********************* manipulation ****************d*g**/
 740: 
 741: 
 742:     /**
 743:      * Inserts row in a table.
 744:      * @param  array|\Traversable|Selection array($column => $value)|\Traversable|Selection for INSERT ... SELECT
 745:      * @return IRow|int|bool Returns IRow or number of affected rows for Selection or table without primary key
 746:      */
 747:     public function insert($data)
 748:     {
 749:         if ($data instanceof self) {
 750:             $return = $this->context->queryArgs($this->sqlBuilder->buildInsertQuery() . ' ' . $data->getSql(), $data->getSqlBuilder()->getParameters());
 751: 
 752:         } else {
 753:             if ($data instanceof \Traversable) {
 754:                 $data = iterator_to_array($data);
 755:             }
 756:             $return = $this->context->query($this->sqlBuilder->buildInsertQuery() . ' ?values', $data);
 757:         }
 758: 
 759:         $this->loadRefCache();
 760: 
 761:         if ($data instanceof self || $this->primary === NULL) {
 762:             unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]);
 763:             return $return->getRowCount();
 764:         }
 765: 
 766:         $primaryKey = $this->context->getInsertId(
 767:             ($tmp = $this->getPrimarySequence())
 768:                 ? implode('.', array_map(array($this->context->getConnection()->getSupplementalDriver(), 'delimite'), explode('.', $tmp)))
 769:                 : NULL
 770:         );
 771:         if (!$primaryKey) {
 772:             unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]);
 773:             return $return->getRowCount();
 774:         }
 775: 
 776:         if (is_array($this->getPrimary())) {
 777:             $primaryKey = array();
 778: 
 779:             foreach ((array) $this->getPrimary() as $key) {
 780:                 if (!isset($data[$key])) {
 781:                     return $data;
 782:                 }
 783: 
 784:                 $primaryKey[$key] = $data[$key];
 785:             }
 786:             if (count($primaryKey) === 1) {
 787:                 $primaryKey = reset($primaryKey);
 788:             }
 789:         }
 790: 
 791:         $row = $this->createSelectionInstance()
 792:             ->select('*')
 793:             ->wherePrimary($primaryKey)
 794:             ->fetch();
 795: 
 796:         if ($this->rows !== NULL) {
 797:             if ($signature = $row->getSignature(FALSE)) {
 798:                 $this->rows[$signature] = $row;
 799:                 $this->data[$signature] = $row;
 800:             } else {
 801:                 $this->rows[] = $row;
 802:                 $this->data[] = $row;
 803:             }
 804:         }
 805: 
 806:         return $row;
 807:     }
 808: 
 809: 
 810:     /**
 811:      * Updates all rows in result set.
 812:      * Joins in UPDATE are supported only in MySQL
 813:      * @param  array|\Traversable ($column => $value)
 814:      * @return int number of affected rows
 815:      */
 816:     public function update($data)
 817:     {
 818:         if ($data instanceof \Traversable) {
 819:             $data = iterator_to_array($data);
 820: 
 821:         } elseif (!is_array($data)) {
 822:             throw new Nette\InvalidArgumentException;
 823:         }
 824: 
 825:         if (!$data) {
 826:             return 0;
 827:         }
 828: 
 829:         return $this->context->queryArgs(
 830:             $this->sqlBuilder->buildUpdateQuery(),
 831:             array_merge(array($data), $this->sqlBuilder->getParameters())
 832:         )->getRowCount();
 833:     }
 834: 
 835: 
 836:     /**
 837:      * Deletes all rows in result set.
 838:      * @return int number of affected rows
 839:      */
 840:     public function delete()
 841:     {
 842:         return $this->query($this->sqlBuilder->buildDeleteQuery())->getRowCount();
 843:     }
 844: 
 845: 
 846:     /********************* references ****************d*g**/
 847: 
 848: 
 849:     /**
 850:      * Returns referenced row.
 851:      * @param  ActiveRow
 852:      * @param  string
 853:      * @param  string|NULL
 854:      * @return ActiveRow|NULL|FALSE NULL if the row does not exist, FALSE if the relationship does not exist
 855:      */
 856:     public function getReferencedTable(ActiveRow $row, $table, $column = NULL)
 857:     {
 858:         if (!$column) {
 859:             $belongsTo = $this->conventions->getBelongsToReference($this->name, $table);
 860:             if (!$belongsTo) {
 861:                 return FALSE;
 862:             }
 863:             list($table, $column) = $belongsTo;
 864:         }
 865:         if (!$row->accessColumn($column)) {
 866:             return FALSE;
 867:         }
 868: 
 869:         $checkPrimaryKey = $row[$column];
 870: 
 871:         $referenced = & $this->refCache['referenced'][$this->getSpecificCacheKey()]["$table.$column"];
 872:         $selection = & $referenced['selection'];
 873:         $cacheKeys = & $referenced['cacheKeys'];
 874:         if ($selection === NULL || ($checkPrimaryKey !== NULL && !isset($cacheKeys[$checkPrimaryKey]))) {
 875:             $this->execute();
 876:             $cacheKeys = array();
 877:             foreach ($this->rows as $row) {
 878:                 if ($row[$column] === NULL) {
 879:                     continue;
 880:                 }
 881: 
 882:                 $key = $row[$column];
 883:                 $cacheKeys[$key] = TRUE;
 884:             }
 885: 
 886:             if ($cacheKeys) {
 887:                 $selection = $this->createSelectionInstance($table);
 888:                 $selection->where($selection->getPrimary(), array_keys($cacheKeys));
 889:             } else {
 890:                 $selection = array();
 891:             }
 892:         }
 893: 
 894:         return isset($selection[$checkPrimaryKey]) ? $selection[$checkPrimaryKey] : NULL;
 895:     }
 896: 
 897: 
 898:     /**
 899:      * Returns referencing rows.
 900:      * @param  string
 901:      * @param  string
 902:      * @param  int primary key
 903:      * @return GroupedSelection
 904:      */
 905:     public function getReferencingTable($table, $column, $active = NULL)
 906:     {
 907:         if (strpos($table, '.') !== FALSE) {
 908:             list($table, $column) = explode('.', $table);
 909:         } elseif (!$column) {
 910:             $hasMany = $this->conventions->getHasManyReference($this->name, $table);
 911:             if (!$hasMany) {
 912:                 return FALSE;
 913:             }
 914:             list($table, $column) = $hasMany;
 915:         }
 916: 
 917:         $prototype = & $this->refCache['referencingPrototype'][$this->getSpecificCacheKey()]["$table.$column"];
 918:         if (!$prototype) {
 919:             $prototype = $this->createGroupedSelectionInstance($table, $column);
 920:             $prototype->where("$table.$column", array_keys((array) $this->rows));
 921:         }
 922: 
 923:         $clone = clone $prototype;
 924:         $clone->setActive($active);
 925:         return $clone;
 926:     }
 927: 
 928: 
 929:     /********************* interface Iterator ****************d*g**/
 930: 
 931: 
 932:     public function rewind()
 933:     {
 934:         $this->execute();
 935:         $this->keys = array_keys($this->data);
 936:         reset($this->keys);
 937:     }
 938: 
 939: 
 940:     /** @return IRow */
 941:     public function current()
 942:     {
 943:         if (($key = current($this->keys)) !== FALSE) {
 944:             return $this->data[$key];
 945:         } else {
 946:             return FALSE;
 947:         }
 948:     }
 949: 
 950: 
 951:     /**
 952:      * @return string row ID
 953:      */
 954:     public function key()
 955:     {
 956:         return current($this->keys);
 957:     }
 958: 
 959: 
 960:     public function next()
 961:     {
 962:         next($this->keys);
 963:     }
 964: 
 965: 
 966:     public function valid()
 967:     {
 968:         return current($this->keys) !== FALSE;
 969:     }
 970: 
 971: 
 972:     /********************* interface ArrayAccess ****************d*g**/
 973: 
 974: 
 975:     /**
 976:      * Mimic row.
 977:      * @param  string row ID
 978:      * @param  IRow
 979:      * @return NULL
 980:      */
 981:     public function offsetSet($key, $value)
 982:     {
 983:         $this->execute();
 984:         $this->rows[$key] = $value;
 985:     }
 986: 
 987: 
 988:     /**
 989:      * Returns specified row.
 990:      * @param  string row ID
 991:      * @return IRow or NULL if there is no such row
 992:      */
 993:     public function offsetGet($key)
 994:     {
 995:         $this->execute();
 996:         return $this->rows[$key];
 997:     }
 998: 
 999: 
1000:     /**
1001:      * Tests if row exists.
1002:      * @param  string row ID
1003:      * @return bool
1004:      */
1005:     public function offsetExists($key)
1006:     {
1007:         $this->execute();
1008:         return isset($this->rows[$key]);
1009:     }
1010: 
1011: 
1012:     /**
1013:      * Removes row from result set.
1014:      * @param  string row ID
1015:      * @return NULL
1016:      */
1017:     public function offsetUnset($key)
1018:     {
1019:         $this->execute();
1020:         unset($this->rows[$key], $this->data[$key]);
1021:     }
1022: 
1023: }
1024: 
Nette 2.3-20161221 API API documentation generated by ApiGen 2.8.0