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