]> git.mxchange.org Git - core.git/blob - framework/main/classes/database/result/class_CachedDatabaseResult.php
Continued:
[core.git] / framework / main / classes / database / result / class_CachedDatabaseResult.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Result\Database;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Criteria\Local\LocalSearchCriteria;
7 use Org\Mxchange\CoreFramework\Criteria\Local\LocalUpdateCriteria;
8 use Org\Mxchange\CoreFramework\Criteria\Storing\StoreableCriteria;
9 use Org\Mxchange\CoreFramework\Database\Frontend\DatabaseFrontend;
10 use Org\Mxchange\CoreFramework\Database\Backend\BaseDatabaseBackend;
11 use Org\Mxchange\CoreFramework\Result\Search\SearchableResult;
12 use Org\Mxchange\CoreFramework\Result\Update\UpdateableResult;
13
14 // Import SPL stuff
15 use \InvalidArgumentException;
16 use \SeekableIterator;
17
18 /**
19  * A database result class
20  *
21  * @author              Roland Haeder <webmaster@shipsimu.org>
22  * @version             0.0.0
23  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
24  * @license             GNU GPL 3.0 or any newer version
25  * @link                http://www.shipsimu.org
26  *
27  * This program is free software: you can redistribute it and/or modify
28  * it under the terms of the GNU General Public License as published by
29  * the Free Software Foundation, either version 3 of the License, or
30  * (at your option) any later version.
31  *
32  * This program is distributed in the hope that it will be useful,
33  * but WITHOUT ANY WARRANTY; without even the implied warranty of
34  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35  * GNU General Public License for more details.
36  *
37  * You should have received a copy of the GNU General Public License
38  * along with this program. If not, see <http://www.gnu.org/licenses/>.
39  */
40 class CachedDatabaseResult extends BaseDatabaseResult implements SearchableResult, UpdateableResult, SeekableIterator {
41         // Exception constants
42         const EXCEPTION_INVALID_DATABASE_RESULT = 0x1c0;
43         const EXCEPTION_RESULT_UPDATE_FAILED    = 0x1c1;
44
45         /**
46          * Current position in array
47          */
48         private $currentPos = -1;
49
50         /**
51          * Current row
52          */
53         private $currentRow = NULL;
54
55         /**
56          * Result array
57          */
58         private $resultArray = [];
59
60         /**
61          * Array of out-dated entries
62          */
63         private $outDated = [];
64
65         /**
66          * Affected rows
67          */
68         private $affectedRows = 0;
69
70         /**
71          * Found value
72          */
73         private $foundValue = '';
74
75         /**
76          * Protected constructor
77          *
78          * @return      void
79          */
80         protected function __construct () {
81                 // Call parent constructor
82                 parent::__construct(__CLASS__);
83         }
84
85         /**
86          * Creates an instance of this result by a provided result array
87          *
88          * @param       $resultArray            The array holding the result from query
89          * @return      $resultInstance         An instance of this class
90          * @throws      InvalidArgumentException        If a parameter is invalid
91          */
92         public static final function createCachedDatabaseResult (array $resultArray) {
93                 // Misses an element?
94                 if (count($resultArray) == 0) {
95                         // Cannot be empty
96                         throw new InvalidArgumentException('Array "resultArray" cannot be empty.');
97                 } elseif (!array_key_exists(BaseDatabaseBackend::RESULT_INDEX_ROWS, $resultArray)) {
98                         // Yes, then abort here
99                         throw new InvalidArgumentException(sprintf('resultArray(%d)=%s has no element "%s".', count($resultArray), print_r($resultArray, TRUE), BaseDatabaseBackend::RESULT_INDEX_ROWS));
100                 }
101
102                 // Get a new instance
103                 $resultInstance = new CachedDatabaseResult();
104
105                 // Set the result array and reset current position
106                 $resultInstance->setResultArray($resultArray);
107                 $resultInstance->resetCurrentPosition();
108
109                 // Set affected rows
110                 $resultInstance->setAffectedRows(count($resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS]));
111
112                 // Return the instance
113                 return $resultInstance;
114         }
115
116         /**
117          * Setter for result array
118          *
119          * @param       $resultArray    The array holding the result from query
120          * @return      void
121          */
122         protected final function setResultArray (array $resultArray) {
123                 $this->resultArray = $resultArray;
124         }
125
126         /**
127          * Updates the current entry by given update criteria
128          *
129          * @param       $updateInstance         An instance of an Updateable criteria
130          * @return      void
131          */
132         private function updateCurrentEntryByCriteria (LocalUpdateCriteria $updateInstance) {
133                 // Get the current entry key
134                 $entryKey = $this->key();
135
136                 // Now get the update criteria array and update all entries
137                 foreach ($updateInstance->getUpdateCriteria() as $criteriaKey => $criteriaValue) {
138                         // Update data
139                         $this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$entryKey][$criteriaKey] = $criteriaValue;
140
141                         // Mark it as out-dated
142                         $this->outDated[$criteriaKey] = 1;
143                 }
144         }
145
146         /**
147          * "Iterator" method next() to advance to the next valid entry. This method
148          * does also check if result is invalid
149          *
150          * @return      $nextValid      Whether the next entry is valid
151          */
152         public function next () {
153                 // Default is not valid
154                 $nextValid = false;
155
156                 // Is the result valid?
157                 if ($this->valid()) {
158                         // Next entry found, so count one up and cache it
159                         $this->currentPos++;
160                         $this->currentRow = $this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos];
161                         $nextValid = true;
162                 }
163
164                 // Return the result
165                 return $nextValid;
166         }
167
168         /**
169          * Seeks for to a specified position
170          *
171          * @param       $index  Index to seek for
172          * @return      void
173          */
174         public function seek ($index) {
175                 // Rewind to beginning
176                 $this->rewind();
177
178                 // Search for the entry
179                 while (($this->currentPos < $index) && ($this->valid())) {
180                         // Continue on
181                         $this->next();
182                 }
183         }
184
185         /**
186          * Gives back the current position or null if not found
187          *
188          * @return      $current        Current element to give back
189          */
190         public function current () {
191                 // Default is not found
192                 $current = NULL;
193
194                 // Does the current enty exist?
195                 if (isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos])) {
196                         // Then get it
197                         $current = $this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos];
198                 }
199
200                 // Return the result
201                 return $current;
202         }
203
204         /**
205          * Checks if next() and rewind will give a valid result
206          *
207          * @return      $isValid Whether the next/rewind entry is valid
208          */
209         public function valid () {
210                 // Check if all is fine ...
211                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
212                 $isValid = ($this->ifStatusIsOkay() && isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos]) && isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][0]));
213
214                 // Return the result
215                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: isValid=%d - EXIT!', intval($isValid)));
216                 return $isValid;
217         }
218
219         /**
220          * Returns count of entries
221          *
222          * @return      $isValid Whether the next/rewind entry is valid
223          */
224         public function count () {
225                 // Count rows
226                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: CALLED!');
227                 $count = count($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS]);
228
229                 // Return it
230                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: count=%d - EXIT!', $count));
231                 return $count;
232         }
233
234         /**
235          * Determines whether the status of the query was fine (BaseDatabaseBackend::RESULT_OKAY)
236          *
237          * @return      $ifStatusOkay   Whether the status of the query was okay
238          */
239         public function ifStatusIsOkay () {
240                 // Check all conditions
241                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
242                 $ifStatusOkay = (isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_STATUS]) && $this->resultArray[BaseDatabaseBackend::RESULT_INDEX_STATUS] === BaseDatabaseBackend::RESULT_OKAY);
243
244                 // Return status
245                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: ifStatusOkay=%s - EXIT!', intval($ifStatusOkay)));
246                 return $ifStatusOkay;
247         }
248
249         /**
250          * Gets the current key of iteration
251          *
252          * @return      $currentPos     Key from iterator
253          */
254         public function key () {
255                 // Return current array position
256                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
257                 return $this->currentPos;
258         }
259
260         /**
261          * Rewind to the beginning and clear array $currentRow
262          *
263          * @return      void
264          */
265         public function rewind () {
266                 // Reset both current array position and current row
267                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
268                 $this->resetCurrentPosition();
269                 $this->currentRow = [];
270
271                 // Trace message
272                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: EXIT!');
273         }
274
275         /**
276          * Resets current array position to 0 if at least one record is there or -1
277          * if no record is there.
278          *
279          * @return      void
280          */
281         private function resetCurrentPosition () {
282                 // Reset position
283                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: CALLED!');
284                 $this->currentPos = ($this->count() > 0 ? 0 : -1);
285
286                 // Trace message
287                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: EXIT!');
288         }
289
290         /**
291          * Searches for an entry in data result and returns it
292          *
293          * @param       $criteriaInstance       The criteria to look inside the data set
294          * @return      $result                         Found result entry
295          * @todo        0% done
296          */
297         public function searchEntry (LocalSearchCriteria $criteriaInstance) {
298                 // Unfinished code
299                 $this->debugBackTrace(sprintf('[%s:%d]: criteriaInstance=%s', __METHOD__, __LINE__, print_r($criteriaInstance, TRUE)));
300         }
301
302         /**
303          * Adds an update request to the database result for writing it to the
304          * database layer
305          *
306          * @param       $updateInstance An instance of a updateable criteria
307          * @return      void
308          * @throws      ResultUpdateException   If no result was updated
309          */
310         public function add2UpdateQueue (LocalUpdateCriteria $updateInstance) {
311                 // Rewind the pointer
312                 $this->rewind();
313
314                 // Get search criteria
315                 $searchInstance = $updateInstance->getSearchInstance();
316
317                 // And start looking for the result
318                 $foundEntries = $this->getAffectedRows();
319                 while (($this->valid()) && ($foundEntries < $searchInstance->getLimit())) {
320                         // Get next entry
321                         $this->next();
322                         $currentEntry = $this->current();
323
324                         // Is this entry found?
325                         if ($searchInstance->ifEntryMatches($currentEntry)) {
326                                 // Update this entry
327                                 $this->updateCurrentEntryByCriteria($updateInstance);
328
329                                 // Count one up
330                                 $foundEntries++;
331                         }
332                 }
333
334                 // If no entry is found/updated throw an exception
335                 if ($foundEntries == 0) {
336                         // Throw an exception here
337                         throw new ResultUpdateException($this, self::EXCEPTION_RESULT_UPDATE_FAILED);
338                 }
339
340                 // Set affected rows
341                 $this->setAffectedRows($foundEntries);
342
343                 // Set update instance
344                 $this->setUpdateInstance($updateInstance);
345         }
346
347         /**
348          * Setter for affected rows
349          *
350          * @param       $rows   Number of affected rows
351          * @return      void
352          */
353         public final function setAffectedRows (int $rows) {
354                 $this->affectedRows = $rows;
355         }
356
357         /**
358          * Getter for affected rows
359          *
360          * @return      $rows   Number of affected rows
361          */
362         public final function getAffectedRows () {
363                 return $this->affectedRows;
364         }
365
366         /**
367          * Getter for found value of previous found() call
368          *
369          * @return      $foundValue             Found value of previous found() call
370          */
371         public final function getFoundValue () {
372                 return $this->foundValue;
373         }
374
375         /**
376          * Checks whether we have out-dated entries or not
377          *
378          * @return      $needsUpdate    Whether we have out-dated entries
379          */
380         public function ifDataNeedsFlush () {
381                 $needsUpdate = (count($this->outDated) > 0);
382                 return $needsUpdate;
383         }
384
385         /**
386          * Adds registration elements to a given dataset instance
387          *
388          * @param       $criteriaInstance       An instance of a StoreableCriteria class
389          * @return      void
390          */
391         public function addElementsToDataSet (StoreableCriteria $criteriaInstance) {
392                 // Walk only through out-dated columns
393                 foreach ($this->outDated as $key => $dummy) {
394                         // Does this key exist?
395                         if ($this->find($key)) {
396                                 // Then update it
397                                 $criteriaInstance->addCriteria($key, $this->getFoundValue());
398                         }
399                 }
400         }
401
402         /**
403          * Find a key inside the result array
404          *
405          * @param       $key    The key we shall find
406          * @return      $found  Whether the key was found or not
407          */
408         public function find (string $key) {
409                 // By default nothing is found
410                 $found = false;
411
412                 // Rewind the pointer
413                 $this->rewind();
414
415                 // Walk through all entries
416                 while ($this->valid()) {
417                         // Advance to next entry
418                         $this->next();
419
420                         // Get the whole array
421                         $currentEntry = $this->current();
422
423                         // Is the element there?
424                         if (isset($currentEntry[$key])) {
425                                 // Okay, found!
426                                 $found = true;
427
428                                 // So "cache" it
429                                 $this->foundValue = $currentEntry[$key];
430
431                                 // And stop searching
432                                 break;
433                         }
434                 }
435
436                 // Return the result
437                 return $found;
438         }
439
440         /**
441          * Solver for result index value with call-back method
442          *
443          * @param       $databaseColumn         Database column where the index might be found
444          * @param       $frontendInstance       The frontend instance to ask for array element
445          * @para        $callBack                       Call-back object for setting the index;
446          *                                                              0=object instance,1=method name
447          * @return      void
448          * @todo        Find a caching way without modifying the result array
449          */
450         public function solveResultIndex (string $databaseColumn, DatabaseFrontend $frontendInstance, array $callBack) {
451                 // By default nothing is found
452                 $indexValue = 0;
453
454                 // Is the element in result itself found?
455                 if ($this->find($databaseColumn)) {
456                         // Use this value
457                         $indexValue = $this->getFoundValue();
458                 } elseif ($this->find($frontendInstance->getIndexKey())) {
459                         // Use this value
460                         $indexValue = $this->getFoundValue();
461                 }
462
463                 // Set the index
464                 call_user_func_array($callBack, array($indexValue));
465         }
466
467 }