1: <?php
2:
3: 4: 5: 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: 17: 18:
19: class GroupedSelection extends Selection
20: {
21:
22: protected $refTable;
23:
24:
25: protected $refCacheCurrent;
26:
27:
28: protected $column;
29:
30:
31: protected $active;
32:
33:
34: 35: 36: 37: 38: 39: 40: 41: 42:
43: public function __construct(Context $context, IConventions $conventions, $tableName, $column, Selection $refTable, Nette\Caching\IStorage $cacheStorage = null)
44: {
45: $this->refTable = $refTable;
46: $this->column = $column;
47: parent::__construct($context, $conventions, $tableName, $cacheStorage);
48: }
49:
50:
51: 52: 53: 54: 55: 56:
57: public function setActive($active)
58: {
59: $this->active = $active;
60: return $this;
61: }
62:
63:
64: 65: 66:
67: public function select($columns, ...$params)
68: {
69: if (!$this->sqlBuilder->getSelect()) {
70: $this->sqlBuilder->addSelect("$this->name.$this->column");
71: }
72:
73: return parent::select($columns, ...$params);
74: }
75:
76:
77: 78: 79:
80: public function order($columns, ...$params)
81: {
82: if (!$this->sqlBuilder->getOrder()) {
83:
84: $this->sqlBuilder->addOrder("$this->name.$this->column" . (preg_match('~\bDESC\z~i', $columns) ? ' DESC' : ''));
85: }
86:
87: return parent::order($columns, ...$params);
88: }
89:
90:
91:
92:
93:
94: public function aggregation($function)
95: {
96: $aggregation = &$this->getRefTable($refPath)->aggregation[$refPath . $function . $this->sqlBuilder->getSelectQueryHash($this->getPreviousAccessedColumns())];
97:
98: if ($aggregation === null) {
99: $aggregation = [];
100:
101: $selection = $this->createSelectionInstance();
102: $selection->getSqlBuilder()->importConditions($this->getSqlBuilder());
103: $selection->select($function);
104: $selection->select("$this->name.$this->column");
105: $selection->group("$this->name.$this->column");
106:
107: foreach ($selection as $row) {
108: $aggregation[$row[$this->column]] = $row;
109: }
110: }
111:
112: if (isset($aggregation[$this->active])) {
113: foreach ($aggregation[$this->active] as $val) {
114: return $val;
115: }
116: }
117: }
118:
119:
120: 121: 122:
123: public function count($column = null)
124: {
125: $return = parent::count($column);
126: return isset($return) ? $return : 0;
127: }
128:
129:
130:
131:
132:
133: protected function execute()
134: {
135: if ($this->rows !== null) {
136: $this->observeCache = $this;
137: return;
138: }
139:
140: $accessedColumns = $this->accessedColumns;
141: $this->loadRefCache();
142:
143: if (!isset($this->refCacheCurrent['data'])) {
144:
145: $this->accessedColumns = $accessedColumns;
146:
147: $limit = $this->sqlBuilder->getLimit();
148: $rows = count($this->refTable->rows);
149: if ($limit && $rows > 1) {
150: $this->sqlBuilder->setLimit(null, null);
151: }
152: parent::execute();
153: $this->sqlBuilder->setLimit($limit, null);
154: $data = [];
155: $offset = [];
156: $this->accessColumn($this->column);
157: foreach ((array) $this->rows as $key => $row) {
158: $ref = &$data[$row[$this->column]];
159: $skip = &$offset[$row[$this->column]];
160: if ($limit === null || $rows <= 1 || (count($ref ?: []) < $limit && $skip >= $this->sqlBuilder->getOffset())) {
161: $ref[$key] = $row;
162: } else {
163: unset($this->rows[$key]);
164: }
165: $skip++;
166: unset($ref, $skip);
167: }
168:
169: $this->refCacheCurrent['data'] = $data;
170: $this->data = &$this->refCacheCurrent['data'][$this->active];
171: }
172:
173: $this->observeCache = $this;
174: if ($this->data === null) {
175: $this->data = [];
176: } else {
177: foreach ($this->data as $row) {
178: $row->setTable($this);
179: }
180: reset($this->data);
181: }
182: }
183:
184:
185: 186: 187:
188: protected function getRefTable(&$refPath)
189: {
190: $refObj = $this->refTable;
191: $refPath = $this->name . '.';
192: while ($refObj instanceof self) {
193: $refPath .= $refObj->name . '.';
194: $refObj = $refObj->refTable;
195: }
196:
197: return $refObj;
198: }
199:
200:
201: protected function loadRefCache()
202: {
203: $hash = $this->getSpecificCacheKey();
204: $referencing = &$this->refCache['referencing'][$this->getGeneralCacheKey()];
205: $this->observeCache = &$referencing['observeCache'];
206: $this->refCacheCurrent = &$referencing[$hash];
207: $this->accessedColumns = &$referencing[$hash]['accessed'];
208: $this->specificCacheKey = &$referencing[$hash]['specificCacheKey'];
209: $this->rows = &$referencing[$hash]['rows'];
210:
211: if (isset($referencing[$hash]['data'][$this->active])) {
212: $this->data = &$referencing[$hash]['data'][$this->active];
213: }
214: }
215:
216:
217: protected function emptyResultSet($saveCache = true, $deleteRererencedCache = true)
218: {
219: parent::emptyResultSet($saveCache, false);
220: }
221:
222:
223:
224:
225:
226: public function insert($data)
227: {
228: if ($data instanceof \Traversable && !$data instanceof Selection) {
229: $data = iterator_to_array($data);
230: }
231:
232: if (Nette\Utils\Arrays::isList($data)) {
233: foreach (array_keys($data) as $key) {
234: $data[$key][$this->column] = $this->active;
235: }
236: } else {
237: $data[$this->column] = $this->active;
238: }
239:
240: return parent::insert($data);
241: }
242:
243:
244: public function update($data)
245: {
246: $builder = $this->sqlBuilder;
247:
248: $this->sqlBuilder = clone $this->sqlBuilder;
249: $this->where($this->column, $this->active);
250: $return = parent::update($data);
251:
252: $this->sqlBuilder = $builder;
253: return $return;
254: }
255:
256:
257: public function delete()
258: {
259: $builder = $this->sqlBuilder;
260:
261: $this->sqlBuilder = clone $this->sqlBuilder;
262: $this->where($this->column, $this->active);
263: $return = parent::delete();
264:
265: $this->sqlBuilder = $builder;
266: return $return;
267: }
268: }
269: