2efa187e04662eb14b58a28baa2983aeab1a3f81
[core.git] / framework / main / classes / database / backend / class_CachedLocalFileDatabase.php
1 <?php
2 // Own namespace
3 namespace CoreFramework\Database\Backend\Lfdb;
4
5 // Import framework stuff
6 use CoreFramework\Criteria\Criteria;
7 use CoreFramework\Criteria\Local\LocalSearchCriteria;
8 use CoreFramework\Criteria\Storing\StoreableCriteria;
9 use CoreFramework\Database\Backend\BaseDatabaseBackend;
10 use CoreFramework\Database\Backend\DatabaseBackend;
11 use CoreFramework\Factory\ObjectFactory;
12 use CoreFramework\Generic\FrameworkException;
13
14 /**
15  * Database backend class for storing objects in locally created files.
16  *
17  * This class serializes arrays stored in the dataset instance and saves them
18  * to local files. Every file (except 'info') represents a single line. Every
19  * directory within the 'db' (base) directory represents a table.
20  *
21  * A configurable 'file_io_class' is being used as "storage backend".
22  *
23  * @author              Roland Haeder <webmaster@shipsimu.org>
24  * @version             0.0.0
25  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 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 CachedLocalFileDatabase extends BaseDatabaseBackend implements DatabaseBackend {
43         /**
44          * The file's extension
45          */
46         private $fileExtension = 'serialized';
47
48         /**
49          * The last read file's name
50          */
51         private $lastFile = '';
52
53         /**
54          * The last read file's content including header information
55          */
56         private $lastContents = array();
57
58         /**
59          * Whether the "connection is already up
60          */
61         private $alreadyConnected = false;
62
63         /**
64          * Table information array
65          */
66         private $tableInfo = array();
67
68         /**
69          * Element for index
70          */
71         private $indexKey = '__idx';
72
73         /**
74          * The protected constructor. Do never instance from outside! You need to
75          * set a local file path. The class will then validate it.
76          *
77          * @return      void
78          */
79         protected function __construct () {
80                 // Call parent constructor
81                 parent::__construct(__CLASS__);
82         }
83
84         /**
85          * Create an object of CachedLocalFileDatabase and set the save path from
86          * configuration for local files.
87          *
88          * @return      $databaseInstance       An instance of CachedLocalFileDatabase
89          */
90         public static final function createCachedLocalFileDatabase () {
91                 // Get an instance
92                 $databaseInstance = new CachedLocalFileDatabase();
93
94                 // Get a new compressor channel instance
95                 $compressorInstance = ObjectFactory::createObjectByConfiguredName('compressor_channel_class');
96
97                 // Set the compressor channel
98                 $databaseInstance->setCompressorChannel($compressorInstance);
99
100                 // Get a file IO handler
101                 $fileIoInstance = ObjectFactory::createObjectByConfiguredName('file_io_class');
102
103                 // ... and set it
104                 $databaseInstance->setFileIoInstance($fileIoInstance);
105
106                 // "Connect" to the database
107                 $databaseInstance->connectToDatabase();
108
109                 // Return database instance
110                 return $databaseInstance;
111         }
112
113         /**
114          * Setter for the last read file
115          *
116          * @param       $fqfn   The FQFN of the last read file
117          * @return      void
118          */
119         private final function setLastFile ($fqfn) {
120                 // Cast string and set it
121                 $this->lastFile = (string) $fqfn;
122         }
123
124         /**
125          * Getter for last read file
126          *
127          * @return      $lastFile       The last read file's name with full path
128          */
129         public final function getLastFile () {
130                 return $this->lastFile;
131         }
132
133         /**
134          * Setter for contents of the last read file
135          *
136          * @param               $contents       An array with header and data elements
137          * @return      void
138          */
139         private final function setLastFileContents (array $contents) {
140                 // Set array
141                 $this->lastContents = $contents;
142         }
143
144         /**
145          * Getter for last read file's content as an array
146          *
147          * @return      $lastContent    The array with elements 'header' and 'data'.
148          */
149         public final function getLastContents () {
150                 return $this->lastContents;
151         }
152
153         /**
154          * Getter for file extension
155          *
156          * @return      $fileExtension  The array with elements 'header' and 'data'.
157          */
158         public final function getFileExtension () {
159                 return $this->fileExtension;
160         }
161
162         /**
163          * Getter for index key
164          *
165          * @return      $indexKey       Index key
166          */
167         public final function getIndexKey () {
168                 return $this->indexKey;
169         }
170
171         /**
172          * Reads a local data file  and returns it's contents in an array
173          *
174          * @param       $fqfn   The FQFN for the requested file
175          * @return      $dataArray
176          */
177         private function getDataArrayFromFile ($fqfn) {
178                 // Debug message
179                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: Reading elements from database file ' . $fqfn . ' ...');
180
181                 // Init compressed data
182                 $compressedData = $this->getFileIoInstance()->loadFileContents($fqfn);
183                 $compressedData = $compressedData['data'];
184
185                 // Close the file and throw the instance away
186                 unset($fileInstance);
187
188                 // Decompress it
189                 $serializedData = $this->getCompressorChannel()->getCompressor()->decompressStream($compressedData);
190
191                 // Unserialize it
192                 $dataArray = json_decode($serializedData, true);
193
194                 // Debug message
195                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: Read ' . count($dataArray) . ' elements from database file ' . $fqfn . '.');
196                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataArray=' . print_r($dataArray, true));
197
198                 // Finally return it
199                 return $dataArray;
200         }
201
202         /**
203          * Writes data array to local file
204          *
205          * @param       $fqfn           The FQFN of the local file
206          * @param       $dataArray      An array with all the data we shall write
207          * @return      void
208          */
209         private function writeDataArrayToFqfn ($fqfn, array $dataArray) {
210                 // Debug message
211                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: Flushing ' . count($dataArray) . ' elements to database file ' . $fqfn . ' ...');
212                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataArray=' . print_r($dataArray, true));
213
214                 // Serialize and compress it
215                 $compressedData = $this->getCompressorChannel()->getCompressor()->compressStream(json_encode($dataArray));
216
217                 // Write data
218                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: Writing ' . strlen($compressedData) . ' bytes ...');
219
220                 // Write this data BASE64 encoded to the file
221                 $this->getFileIoInstance()->saveStreamToFile($fqfn, $compressedData, $this);
222
223                 // Debug message
224                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: Flushing ' . count($dataArray) . ' elements to database file completed.');
225         }
226
227         /**
228          * Getter for table information file contents or an empty if info file was not created
229          *
230          * @param       $dataSetInstance        An instance of a database set class
231          * @return      $infoArray                      An array with all table informations
232          */
233         private function getContentsFromTableInfoFile (StoreableCriteria $dataSetInstance) {
234                 // Default content is no data
235                 $infoArray = array();
236
237                 // Create FQFN for getting the table information file
238                 $fqfn = $this->generateFqfnFromDataSet($dataSetInstance, 'info');
239
240                 // Get the file contents
241                 try {
242                         $infoArray = $this->getDataArrayFromFile($fqfn);
243                 } catch (FileNotFoundException $e) {
244                         // Not found, so ignore it here
245                 }
246
247                 // ... and return it
248                 return $infoArray;
249         }
250
251         /**
252          * Generates an FQFN from given dataset instance and string
253          *
254          * @param       $dataSetInstance        An instance of a database set class
255          * @param       $rowName                        Name of the row
256          * @return      $fqfn                           The FQFN for this row
257          */
258         private function generateFqfnFromDataSet (Criteria $dataSetInstance, $rowName) {
259                 // This is the FQFN
260                 $fqfn = $this->getConfigInstance()->getConfigEntry('local_database_path') . $dataSetInstance->getTableName() . '/' . $rowName . '.' . $this->getFileExtension();
261
262                 // Return it
263                 return $fqfn;
264         }
265
266         /**
267          * Creates the table info file from given dataset instance
268          *
269          * @param       $dataSetInstance        An instance of a database set class
270          * @return      void
271          */
272         private function createTableInfoFile (StoreableCriteria $dataSetInstance) {
273                 // Create FQFN for creating the table information file
274                 $fqfn = $this->generateFqfnFromDataSet($dataSetInstance, 'info');
275
276                 // Get the data out from dataset in a local array
277                 $this->tableInfo[$dataSetInstance->getTableName()] = array(
278                         'primary'      => $dataSetInstance->getPrimaryKey(),
279                         'created'      => time(),
280                         'last_updated' => time()
281                 );
282
283                 // Write the data to the file
284                 $this->writeDataArrayToFqfn($fqfn, $this->tableInfo[$dataSetInstance->getTableName()]);
285         }
286
287         /**
288          * Updates the table info file from given dataset instance
289          *
290          * @param       $dataSetInstance        An instance of a database set class
291          * @return      void
292          */
293         private function updateTableInfoFile (StoreableCriteria $dataSetInstance) {
294                 // Get table name from criteria
295                 $tableName = $dataSetInstance->getTableName();
296
297                 // Create FQFN for creating the table information file
298                 $fqfn = $this->generateFqfnFromDataSet($dataSetInstance, 'info');
299
300                 // Get the data out from dataset in a local array
301                 $this->tableInfo[$tableName]['primary']      = $dataSetInstance->getPrimaryKey();
302                 $this->tableInfo[$tableName]['last_updated'] = time();
303
304                 // Write the data to the file
305                 $this->writeDataArrayToFqfn($fqfn, $this->tableInfo[$tableName]);
306         }
307
308         /**
309          * Updates the primary key information or creates the table info file if not found
310          *
311          * @param       $dataSetInstance        An instance of a database set class
312          * @return      void
313          */
314         private function updatePrimaryKey (StoreableCriteria $dataSetInstance) {
315                 // Get table name from criteria
316                 $tableName = $dataSetInstance->getTableName();
317
318                 // Get the information array from lower method
319                 $infoArray = $this->getContentsFromTableInfoFile($dataSetInstance);
320
321                 // Is the primary key there?
322                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: tableInfo=' . print_r($this->tableInfo, true));
323                 if (!isset($this->tableInfo[$tableName]['primary'])) {
324                         // Then create the info file
325                         $this->createTableInfoFile($dataSetInstance);
326                 } elseif (($this->getConfigInstance()->getConfigEntry('db_update_primary_forced') == 'Y') && ($dataSetInstance->getPrimaryKey() != $this->tableInfo[$tableName]['primary'])) {
327                         // Set the array element
328                         $this->tableInfo[$tableName]['primary'] = $dataSetInstance->getPrimaryKey();
329
330                         // Update the entry
331                         $this->updateTableInfoFile($dataSetInstance);
332                 }
333         }
334
335         /**
336          * Makes sure that the database connection is alive
337          *
338          * @return      void
339          * @todo        Do some checks on the database directory and files here
340          */
341         public function connectToDatabase () {
342         }
343
344         /**
345          * Starts a SELECT query on the database by given return type, table name
346          * and search criteria
347          *
348          * @param       $tableName                      Name of the database table
349          * @param       $searchInstance         Local search criteria class
350          * @return      $resultData                     Result data of the query
351          * @throws      UnsupportedCriteriaException    If the criteria is unsupported
352          * @throws      SqlException                                    If an 'SQL error' occurs
353          */
354         public function querySelect ($tableName, LocalSearchCriteria $searchInstance) {
355                 // Debug message
356                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: tableName=' . $tableName . ' - CALLED!');
357
358                 // The result is null by any errors
359                 $resultData = NULL;
360
361                 // Create full path name
362                 $pathName = $this->getConfigInstance()->getConfigEntry('local_database_path') . $tableName . '/';
363
364                 /*
365                  * A 'select' query is not that easy on local files, so first try to
366                  * find the 'table' which is in fact a directory on the server
367                  */
368                 try {
369                         // Get a directory pointer instance
370                         $directoryInstance = ObjectFactory::createObjectByConfiguredName('directory_class', array($pathName));
371
372                         // Initialize the result data, this need to be rewritten e.g. if a local file cannot be read
373                         $resultData = array(
374                                 BaseDatabaseBackend::RESULT_INDEX_STATUS => self::RESULT_OKAY,
375                                 BaseDatabaseBackend::RESULT_INDEX_ROWS   => array()
376                         );
377
378                         // Initialize limit/skip
379                         $limitFound = 0;
380                         $skipFound = 0;
381                         $idx = 1;
382
383                         // Read the directory with some exceptions
384                         while (($dataFile = $directoryInstance->readDirectoryExcept(array('.htaccess', 'info.' . $this->getFileExtension()))) && (($limitFound < $searchInstance->getLimit()) || ($searchInstance->getLimit() == 0))) {
385                                 // Debug message
386                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',this->getFileExtension()=' . $this->getFileExtension());
387
388                                 // Does the extension match?
389                                 if (substr($dataFile, -(strlen($this->getFileExtension()))) !== $this->getFileExtension()) {
390                                         // Skip this file!
391                                         continue;
392                                 } // END - if
393
394                                 // Read the file
395                                 $dataArray = $this->getDataArrayFromFile($pathName . $dataFile);
396                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',dataArray='.print_r($dataArray, true));
397
398                                 // Is this an array?
399                                 if (is_array($dataArray)) {
400                                         // Default is nothing found
401                                         $isFound = true;
402
403                                         // Search in the criteria with FMFW (First Matches, First Wins)
404                                         foreach ($dataArray as $key => $value) {
405                                                 // Make sure value is not bool
406                                                 assert(!is_bool($value));
407
408                                                 // Found one entry?
409                                                 $isFound = (($isFound === true) && ($searchInstance->isCriteriaMatching($key, $value)));
410                                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: key=' . $key . ',value=' . $value . ',isFound=' . intval($isFound));
411                                         } // END - foreach
412
413                                         // Is all found?
414                                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: isFound=' . intval($isFound) . ',limitFound=' . $limitFound . ',limit=' . $searchInstance->getLimit());
415                                         if ($isFound === true) {
416                                                 // Shall we skip this entry?
417                                                 if ($searchInstance->getSkip() > 0) {
418                                                         // We shall skip some entries
419                                                         if ($skipFound < $searchInstance->getSkip()) {
420                                                                 // Skip this entry
421                                                                 $skipFound++;
422                                                                 break;
423                                                         } // END - if
424                                                 } // END - if
425
426                                                 // Set id number
427                                                 $dataArray[$this->getIndexKey()] = $idx;
428
429                                                 // Entry found!
430                                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: indexKey=' . $this->getIndexKey() . ',idx=' . $idx . ',dataArray=' . print_r($dataArray, true));
431                                                 array_push($resultData[BaseDatabaseBackend::RESULT_INDEX_ROWS], $dataArray);
432
433                                                 // Count found entries up
434                                                 $limitFound++;
435                                         } // END - if
436                                 } else {
437                                         // Throw an exception here
438                                         throw new SqlException(array($this, sprintf('File &#39;%s&#39; contains invalid data.', $dataFile), self::DB_CODE_DATA_FILE_CORRUPT), self::EXCEPTION_SQL_QUERY);
439                                 }
440
441                                 // Count entry up
442                                 $idx++;
443                         } // END - while
444
445                         // Close directory and throw the instance away
446                         $directoryInstance->closeDirectory();
447                         unset($directoryInstance);
448
449                         // Reset last exception
450                         $this->resetLastException();
451                 } catch (PathIsNoDirectoryException $e) {
452                         // Path not found means "table not found" for real databases...
453                         $this->setLastException($e);
454
455                         // So throw an SqlException here with faked error message
456                         throw new SqlException (array($this, sprintf('Table &#39;%s&#39; not found', $tableName), self::DB_CODE_TABLE_MISSING), self::EXCEPTION_SQL_QUERY);
457                 } catch (FrameworkException $e) {
458                         // Catch all exceptions and store them in last error
459                         $this->setLastException($e);
460                 }
461
462                 // Return the gathered result
463                 return $resultData;
464         }
465
466         /**
467          * "Inserts" a data set instance into a local file database folder
468          *
469          * @param       $dataSetInstance        A storeable data set
470          * @return      void
471          * @throws      SqlException    If an SQL error occurs
472          */
473         public function queryInsertDataSet (StoreableCriteria $dataSetInstance) {
474                 // Try to save the request away
475                 try {
476                         // Create full path name
477                         $fqfn = $this->generateFqfnFromDataSet($dataSetInstance, md5($dataSetInstance->getUniqueValue()));
478
479                         // Write the data away
480                         $this->writeDataArrayToFqfn($fqfn, $dataSetInstance->getCriteriaArray());
481
482                         // Update the primary key
483                         $this->updatePrimaryKey($dataSetInstance);
484
485                         // Reset last exception
486                         $this->resetLastException();
487                 } catch (FrameworkException $e) {
488                         // Catch all exceptions and store them in last error
489                         $this->setLastException($e);
490
491                         // Throw an SQL exception
492                         throw new SqlException(array($this, sprintf('Cannot write data to table &#39;%s&#39;, is the table created?', $dataSetInstance->getTableName()), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY);
493                 }
494         }
495
496         /**
497          * "Updates" a data set instance with a database layer
498          *
499          * @param       $dataSetInstance        A storeable data set
500          * @return      void
501          * @throws      SqlException    If an SQL error occurs
502          */
503         public function queryUpdateDataSet (StoreableCriteria $dataSetInstance) {
504                 // Create full path name
505                 $pathName = $this->getConfigInstance()->getConfigEntry('local_database_path') . $dataSetInstance->getTableName() . '/';
506
507                 // Try all the requests
508                 try {
509                         // Get a file pointer instance
510                         $directoryInstance = ObjectFactory::createObjectByConfiguredName('directory_class', array($pathName));
511
512                         // Initialize limit/skip
513                         $limitFound = 0;
514                         $skipFound = 0;
515
516                         // Get the criteria array from the dataset
517                         $searchArray = $dataSetInstance->getCriteriaArray();
518
519                         // Get search criteria
520                         $searchInstance = $dataSetInstance->getSearchInstance();
521
522                         // Read the directory with some exceptions
523                         while (($dataFile = $directoryInstance->readDirectoryExcept(array('.htaccess', 'info.' . $this->getFileExtension()))) && (($limitFound < $searchInstance->getLimit()) || ($searchInstance->getLimit() == 0))) {
524                                 // Does the extension match?
525                                 if (substr($dataFile, -(strlen($this->getFileExtension()))) !== $this->getFileExtension()) {
526                                         // Debug message
527                                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',getFileExtension()=' . $this->getFileExtension() . ' - SKIPPED!');
528                                         // Skip this file!
529                                         continue;
530                                 } // END - if
531
532                                 // Open this file for reading
533                                 $dataArray = $this->getDataArrayFromFile($pathName . $dataFile);
534                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',dataArray='.print_r($dataArray, true));
535
536                                 // Is this an array?
537                                 if (is_array($dataArray)) {
538                                         // Default is nothing found
539                                         $isFound = true;
540
541                                         // Search in the criteria with FMFW (First Matches, First Wins)
542                                         foreach ($dataArray as $key => $value) {
543                                                 // Make sure value is not bool
544                                                 assert(!is_bool($value));
545
546                                                 // Found one entry?
547                                                 $isFound = (($isFound === true) && ($searchInstance->isCriteriaMatching($key, $value)));
548                                         } // END - foreach
549
550                                         // Is all found?
551                                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: isFound=' . intval($isFound));
552                                         if ($isFound === true) {
553                                                 // Shall we skip this entry?
554                                                 if ($searchInstance->getSkip() > 0) {
555                                                         // We shall skip some entries
556                                                         if ($skipFound < $searchInstance->getSkip()) {
557                                                                 // Skip this entry
558                                                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: Found entry, but skipping ...');
559                                                                 $skipFound++;
560                                                                 break;
561                                                         } // END - if
562                                                 } // END - if
563
564                                                 // Entry found, so update it
565                                                 foreach ($searchArray as $searchKey => $searchValue) {
566                                                         // Make sure the value is not bool again
567                                                         assert(!is_bool($searchValue));
568                                                         assert($searchKey != $this->indexKey);
569
570                                                         // Debug message + add/update it
571                                                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: criteriaKey=' . $searchKey . ',criteriaValue=' . $searchValue);
572                                                         $dataArray[$searchKey] = $searchValue;
573                                                 } // END - foreach
574
575                                                 // Write the data to a local file
576                                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: Writing dataArray()=' . count($dataArray) . ' to ' . $dataFile . ' ...');
577                                                 $this->writeDataArrayToFqfn($pathName . $dataFile, $dataArray);
578
579                                                 // Count found entries up
580                                                 $limitFound++;
581                                         } // END - if
582                                 } // END - if
583                         } // END - while
584
585                         // Close the file pointer
586                         $directoryInstance->closeDirectory();
587
588                         // Update the primary key
589                         $this->updatePrimaryKey($dataSetInstance);
590
591                         // Reset last exception
592                         $this->resetLastException();
593                 } catch (FrameworkException $e) {
594                         // Catch all exceptions and store them in last error
595                         $this->setLastException($e);
596
597                         // Throw an SQL exception
598                         throw new SqlException(array($this, sprintf('Cannot write data to table &#39;%s&#39;, is the table created? Exception: %s, message:%s', $dataSetInstance->getTableName(), $e->__toString(), $e->getMessage()), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY);
599                 }
600         }
601
602         /**
603          * Getter for primary key of specified table or if not found null will be
604          * returned. This must be database-specific.
605          *
606          * @param       $tableName              Name of the table we need the primary key from
607          * @return      $primaryKey             Primary key column of the given table
608          */
609         public function getPrimaryKeyOfTable ($tableName) {
610                 // Default key is null
611                 $primaryKey = NULL;
612
613                 // Does the table information exist?
614                 if (isset($this->tableInfo[$tableName])) {
615                         // Then return the primary key
616                         $primaryKey = $this->tableInfo[$tableName]['primary'];
617                 } // END - if
618
619                 // Return the column
620                 return $primaryKey;
621         }
622
623         /**
624          * Removes non-public data from given array.
625          *
626          * @param       $data   An array with possible non-public data that needs to be removed.
627          * @return      $data   A cleaned up array with only public data.
628          * @todo        Add more generic non-public data for removal
629          */
630         public function removeNonPublicDataFromArray (array $data) {
631                 // Remove '__idx'
632                 unset($data[$this->indexKey]);
633
634                 // Return it
635                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: data[' . gettype($data) . ']='.print_r($data, true));
636                 return $data;
637         }
638
639         /**
640          * Counts total rows of given table
641          *
642          * @param       $tableName      Table name
643          * @return      $count          Total rows of given table
644          */
645         public function countTotalRows($tableName) {
646                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: tableName=' . $tableName . ' - CALLED!');
647
648                 // Create full path name
649                 $pathName = $this->getConfigInstance()->getConfigEntry('local_database_path') . $tableName . '/';
650
651                 // Try all the requests
652                 try {
653                         // Get a file pointer instance
654                         $directoryInstance = ObjectFactory::createObjectByConfiguredName('directory_class', array($pathName));
655
656                         // Initialize counter
657                         $count = 0;
658
659                         // Read the directory with some exceptions
660                         while ($dataFile = $directoryInstance->readDirectoryExcept(array('.htaccess', 'info.' . $this->getFileExtension()))) {
661                                 // Does the extension match?
662                                 if (substr($dataFile, -(strlen($this->getFileExtension()))) !== $this->getFileExtension()) {
663                                         // Debug message
664                                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',getFileExtension()=' . $this->getFileExtension() . ' - SKIPPED!');
665                                         // Skip this file!
666                                         continue;
667                                 } // END - if
668
669                                 // Count this row up
670                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',getFileExtension()=' . $this->getFileExtension() . ' - COUNTED!');
671                                 $count++;
672                         } // END - while
673                 } catch (FrameworkException $e) {
674                         // Catch all exceptions and store them in last error
675                         $this->setLastException($e);
676
677                         // Throw an SQL exception
678                         throw new SqlException(array($this, sprintf('Cannot count on table &#39;%s&#39;, is the table created?', $dataSetInstance->getTableName()), self::DB_CODE_TABLE_NOT_FOUND), self::EXCEPTION_SQL_QUERY);
679                 }
680
681                 // Return count
682                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('DATABASE: tableName=' . $tableName . ',count=' . $count . ' - EXIT!');
683                 return $count;
684         }
685
686 }