]> git.mxchange.org Git - core.git/blob - framework/main/classes/database/result/class_CachedDatabaseResult.php
03af7c2c702e9cc507fb03084bbbddb5258b241c
[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                 // Increase position
157                 $this->currentPos++;
158
159                 // Is the result valid?
160                 if ($this->valid()) {
161                         // Next entry found, so cache it
162                         $this->currentRow = $this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos];
163                         $nextValid = true;
164                 }
165
166                 // Return the result
167                 return $nextValid;
168         }
169
170         /**
171          * Seeks for to a specified position
172          *
173          * @param       $index  Index to seek for
174          * @return      void
175          */
176         public function seek ($index) {
177                 // Rewind to beginning
178                 $this->rewind();
179
180                 // Search for the entry
181                 while (($this->currentPos < $index) && ($this->valid())) {
182                         // Continue on
183                         $this->next();
184                 }
185         }
186
187         /**
188          * Gives back the current position or null if not found
189          *
190          * @return      $current        Current element to give back
191          */
192         public function current () {
193                 // Default is not found
194                 $current = NULL;
195
196                 // Does the current enty exist?
197                 if (isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos])) {
198                         // Then get it
199                         $current = $this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos];
200                 }
201
202                 // Return the result
203                 return $current;
204         }
205
206         /**
207          * Checks if next() and rewind will give a valid result
208          *
209          * @return      $isValid Whether the next/rewind entry is valid
210          */
211         public function valid () {
212                 // Check if all is fine ...
213                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
214                 $isValid = ($this->ifStatusIsOkay() && isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][$this->currentPos]) && isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS][0]));
215
216                 // Return the result
217                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: isValid=%d - EXIT!', intval($isValid)));
218                 return $isValid;
219         }
220
221         /**
222          * Returns count of entries
223          *
224          * @return      $isValid Whether the next/rewind entry is valid
225          */
226         public function count () {
227                 // Count rows
228                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: CALLED!');
229                 $count = count($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_ROWS]);
230
231                 // Return it
232                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: count=%d - EXIT!', $count));
233                 return $count;
234         }
235
236         /**
237          * Determines whether the status of the query was fine (BaseDatabaseBackend::RESULT_OKAY)
238          *
239          * @return      $ifStatusOkay   Whether the status of the query was okay
240          */
241         public function ifStatusIsOkay () {
242                 // Check all conditions
243                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
244                 $ifStatusOkay = (isset($this->resultArray[BaseDatabaseBackend::RESULT_INDEX_STATUS]) && $this->resultArray[BaseDatabaseBackend::RESULT_INDEX_STATUS] === BaseDatabaseBackend::RESULT_OKAY);
245
246                 // Return status
247                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: ifStatusOkay=%s - EXIT!', intval($ifStatusOkay)));
248                 return $ifStatusOkay;
249         }
250
251         /**
252          * Gets the current key of iteration
253          *
254          * @return      $currentPos     Key from iterator
255          */
256         public function key () {
257                 // Return current array position
258                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
259                 return $this->currentPos;
260         }
261
262         /**
263          * Rewind to the beginning and clear array $currentRow
264          *
265          * @return      void
266          */
267         public function rewind () {
268                 // Reset both current array position and current row
269                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
270                 $this->resetCurrentPosition();
271                 $this->currentRow = [];
272
273                 // Trace message
274                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: EXIT!');
275         }
276
277         /**
278          * Resets current array position to 0 if at least one record is there or -1
279          * if no record is there.
280          *
281          * @return      void
282          */
283         private function resetCurrentPosition () {
284                 // Reset position
285                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: CALLED!');
286                 $this->currentPos = ($this->count() > 0 ? 0 : -1);
287
288                 // Trace message
289                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: EXIT!');
290         }
291
292         /**
293          * Searches for an entry in data result and returns it
294          *
295          * @param       $criteriaInstance       The criteria to look inside the data set
296          * @return      $result                         Found result entry
297          * @todo        0% done
298          */
299         public function searchEntry (LocalSearchCriteria $criteriaInstance) {
300                 // Unfinished code
301                 $this->debugBackTrace(sprintf('[%s:%d]: criteriaInstance=%s', __METHOD__, __LINE__, print_r($criteriaInstance, TRUE)));
302         }
303
304         /**
305          * Adds an update request to the database result for writing it to the
306          * database layer
307          *
308          * @param       $updateInstance An instance of a updateable criteria
309          * @return      void
310          * @throws      ResultUpdateException   If no result was updated
311          */
312         public function add2UpdateQueue (LocalUpdateCriteria $updateInstance) {
313                 // Rewind the pointer
314                 $this->rewind();
315
316                 // Get search criteria
317                 $searchInstance = $updateInstance->getSearchInstance();
318
319                 // And start looking for the result
320                 $foundEntries = $this->getAffectedRows();
321                 while (($this->valid()) && ($foundEntries < $searchInstance->getLimit())) {
322                         // Get next entry
323                         $this->next();
324                         $currentEntry = $this->current();
325
326                         // Is this entry found?
327                         if ($searchInstance->ifEntryMatches($currentEntry)) {
328                                 // Update this entry
329                                 $this->updateCurrentEntryByCriteria($updateInstance);
330
331                                 // Count one up
332                                 $foundEntries++;
333                         }
334                 }
335
336                 // If no entry is found/updated throw an exception
337                 if ($foundEntries == 0) {
338                         // Throw an exception here
339                         throw new ResultUpdateException($this, self::EXCEPTION_RESULT_UPDATE_FAILED);
340                 }
341
342                 // Set affected rows
343                 $this->setAffectedRows($foundEntries);
344
345                 // Set update instance
346                 $this->setUpdateInstance($updateInstance);
347         }
348
349         /**
350          * Setter for affected rows
351          *
352          * @param       $rows   Number of affected rows
353          * @return      void
354          */
355         public final function setAffectedRows (int $rows) {
356                 $this->affectedRows = $rows;
357         }
358
359         /**
360          * Getter for affected rows
361          *
362          * @return      $rows   Number of affected rows
363          */
364         public final function getAffectedRows () {
365                 return $this->affectedRows;
366         }
367
368         /**
369          * Getter for found value of previous found() call
370          *
371          * @return      $foundValue             Found value of previous found() call
372          */
373         public final function getFoundValue () {
374                 return $this->foundValue;
375         }
376
377         /**
378          * Checks whether we have out-dated entries or not
379          *
380          * @return      $needsUpdate    Whether we have out-dated entries
381          */
382         public function ifDataNeedsFlush () {
383                 $needsUpdate = (count($this->outDated) > 0);
384                 return $needsUpdate;
385         }
386
387         /**
388          * Adds registration elements to a given dataset instance
389          *
390          * @param       $criteriaInstance       An instance of a StoreableCriteria class
391          * @return      void
392          */
393         public function addElementsToDataSet (StoreableCriteria $criteriaInstance) {
394                 // Walk only through out-dated columns
395                 foreach ($this->outDated as $key => $dummy) {
396                         // Does this key exist?
397                         if ($this->find($key)) {
398                                 // Then update it
399                                 $criteriaInstance->addCriteria($key, $this->getFoundValue());
400                         }
401                 }
402         }
403
404         /**
405          * Find a key inside the result array
406          *
407          * @param       $key    The key we shall find
408          * @return      $found  Whether the key was found or not
409          */
410         public function find (string $key) {
411                 // By default nothing is found
412                 $found = false;
413
414                 // Rewind the pointer
415                 $this->rewind();
416
417                 // Walk through all entries
418                 while ($this->valid()) {
419                         // Advance to next entry
420                         $this->next();
421
422                         // Get the whole array
423                         $currentEntry = $this->current();
424
425                         // Is the element there?
426                         if (isset($currentEntry[$key])) {
427                                 // Okay, found!
428                                 $found = true;
429
430                                 // So "cache" it
431                                 $this->foundValue = $currentEntry[$key];
432
433                                 // And stop searching
434                                 break;
435                         }
436                 }
437
438                 // Return the result
439                 return $found;
440         }
441
442         /**
443          * Solver for result index value with call-back method
444          *
445          * @param       $databaseColumn         Database column where the index might be found
446          * @param       $frontendInstance       The frontend instance to ask for array element
447          * @para        $callBack                       Call-back object for setting the index;
448          *                                                              0=object instance,1=method name
449          * @return      void
450          * @todo        Find a caching way without modifying the result array
451          */
452         public function solveResultIndex (string $databaseColumn, DatabaseFrontend $frontendInstance, array $callBack) {
453                 // By default nothing is found
454                 $indexValue = 0;
455
456                 // Is the element in result itself found?
457                 if ($this->find($databaseColumn)) {
458                         // Use this value
459                         $indexValue = $this->getFoundValue();
460                 } elseif ($this->find($frontendInstance->getIndexKey())) {
461                         // Use this value
462                         $indexValue = $this->getFoundValue();
463                 }
464
465                 // Set the index
466                 call_user_func_array($callBack, array($indexValue));
467         }
468
469 }