"Fossies" - the Fresh Open Source Software Archive

Member "papayacms-core-6.9.4/src/system/Papaya/Database/Access.php" (13 Dec 2019, 27396 Bytes) of package /linux/www/papayacms-core-6.9.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Access.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.9.3_vs_6.9.4.

    1 <?php
    2 /**
    3  * papaya CMS
    4  *
    5  * @copyright 2000-2018 by papayaCMS project - All rights reserved.
    6  * @link http://www.papaya-cms.com/
    7  * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU General Public License, version 2
    8  *
    9  *  You can redistribute and/or modify this script under the terms of the GNU General Public
   10  *  License (GPL) version 2, provided that the copyright and license notes, including these
   11  *  lines, remain unmodified. papaya is distributed in the hope that it will be useful, but
   12  *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13  *  FOR A PARTICULAR PURPOSE.
   14  */
   15 
   16 namespace Papaya\Database {
   17 
   18   use Papaya\Content\Tables as ContentTables;
   19   use Papaya\Database\Statement\Formatted as FormattedStatement;
   20   use Papaya\Database\Statement\Limited as LimitedStatement;
   21   use Papaya\Database\Statement\Prepared as PreparedStatement;
   22   use Papaya\Database\Syntax\SQLSource;
   23   use Papaya\Message;
   24   use Papaya\Database\Exception as DatabaseException;
   25 
   26   /**
   27    * Papaya Database Access
   28    *
   29    * @package Papaya-Library
   30    * @subpackage Database
   31    */
   32   class Access
   33     extends \Papaya\Application\BaseObject
   34     implements Connection {
   35 
   36     /**
   37      * a table names helper object
   38      *
   39      * @var ContentTables
   40      */
   41     private $_tables;
   42 
   43     private $_owner;
   44 
   45     /**
   46      * Database connection URI for read queries
   47      *
   48      * @var string
   49      */
   50     private $_uriRead;
   51 
   52     /**
   53      * Database connection URI for write queries
   54      *
   55      * @var string
   56      */
   57     private $_uriWrite;
   58 
   59     /**
   60      * Stored database connector object
   61      */
   62     private $_connector;
   63 
   64     /**
   65      * Data was modified (query on write connection)
   66      *
   67      * @var bool
   68      */
   69     private $_dataModified = FALSE;
   70 
   71     /**
   72      * Use only master (write) connection
   73      *
   74      * @var bool
   75      */
   76     private $_useMasterOnly = FALSE;
   77 
   78     /**
   79      * Member variable for a user defined error handler, if set this overrides the default
   80      * error handling
   81      *
   82      * @var null|callable
   83      */
   84     private $_errorHandler;
   85 
   86     /**
   87      * The owner is used later to determine which object has uses the database access
   88      * (for example in logging).
   89      *
   90      * @param string|null $readUri
   91      * @param string|null $writeUri
   92      */
   93     public function __construct($owner = NULL, $readUri = NULL, $writeUri = NULL) {
   94       $this->_owner = $owner;
   95       if (isset($readUri)) {
   96         \Papaya\Utility\Constraints::assertString($readUri);
   97       }
   98       if (isset($writeUri)) {
   99         \Papaya\Utility\Constraints::assertString($writeUri);
  100       }
  101       $this->_uriRead = $readUri;
  102       $this->_uriWrite = $writeUri;
  103     }
  104 
  105     /**
  106      * @return string[]
  107      */
  108     public function getDatabaseURIs() {
  109       return [$this->_uriRead, $this->_uriWrite];
  110     }
  111 
  112     /**
  113      * Get database connection (implicit create)
  114      *
  115      * @param string|null $connectTo connect to specified (read or write) connection
  116      * @return \Papaya\Database\Connector|NULL
  117      */
  118     public function getDatabaseConnector($connectTo = NULL) {
  119       if (NULL !== $this->_connector) {
  120         return $this->_connector;
  121       }
  122       if (!isset($this->papaya()->database)) {
  123         return NULL;
  124       }
  125       $databaseManager = $this->papaya()->database;
  126       if (
  127       $this->_connector = $databaseManager->getConnector($this->_uriRead, $this->_uriWrite)
  128       ) {
  129         if ($connectTo) {
  130           try {
  131             $this->_connector->connect($this->getConnectionMode());
  132           } catch (DatabaseException $exception) {
  133             $this->_handleDatabaseException($exception);
  134           }
  135           return NULL;
  136         }
  137         return $this->_connector;
  138       }
  139       $this->_handleDatabaseException(
  140         new DatabaseException\ConnectionFailed(
  141           \sprintf(
  142             'Database connector not available.'
  143           )
  144         )
  145       );
  146       return NULL;
  147     }
  148 
  149     /**
  150      * Set database connection
  151      *
  152      * @param \Papaya\Database\Connector $connector
  153      *
  154      */
  155     public function setDatabaseConnector(Connector $connector) {
  156       $this->_connector = $connector;
  157     }
  158 
  159     /**
  160      * Create a new prepared statement from an SQL string.
  161      *
  162      * @param string $sql
  163      * @return PreparedStatement
  164      */
  165     public function prepare($sql) {
  166       return new PreparedStatement($this, $sql);
  167     }
  168 
  169     /**
  170      * Get table name with prefix (if needed)
  171      *
  172      * @param string $tableName
  173      * @param bool $usePrefix
  174      *
  175      * @return bool
  176      */
  177     public function getTableName($tableName, $usePrefix = TRUE) {
  178       return $this->tables()->get($tableName, $usePrefix);
  179     }
  180 
  181     /**
  182      * Get a timestamp for create/modified fields. This method is basically here so you can mock
  183      * it for tests.
  184      *
  185      * @return int
  186      */
  187     public function getTimestamp() {
  188       return \time();
  189     }
  190 
  191     /**
  192      * Get table name mapper object
  193      *
  194      * @param ContentTables $tables
  195      *
  196      * @return ContentTables
  197      */
  198     public function tables(ContentTables $tables = NULL) {
  199       if (NULL !== $tables) {
  200         $this->_tables = $tables;
  201       } elseif (NULL === $this->_tables) {
  202         $this->_tables = new ContentTables();
  203       }
  204       return $this->_tables;
  205     }
  206 
  207     /**
  208      * set or read current master usage status
  209      *
  210      * @param bool|null $forObject optional, default value NULL
  211      * @param bool|null $forConnection optional, default value NULL
  212      *
  213      * @return bool use master connection only?
  214      */
  215     public function masterOnly($forObject = NULL, $forConnection = NULL) {
  216       if (NULL !== $forObject) {
  217         $this->_useMasterOnly = (bool)$forObject;
  218       }
  219       if (
  220         (NULL !== $forConnection) &&
  221         ($connector = $this->getDatabaseConnector())
  222       ) {
  223         $connector->masterOnly($forConnection);
  224       }
  225       if ($this->_useMasterOnly) {
  226         return TRUE;
  227       }
  228       return ($connector = $this->getDatabaseConnector()) ? $connector->masterOnly() : FALSE;
  229     }
  230 
  231     /**
  232      * Which connection (read or write) should be used
  233      *
  234      * @param string $requestedMode
  235      * @return string
  236      */
  237     public function getConnectionMode($requestedMode = Connector::MODE_READ) {
  238       if ($requestedMode === Connector::MODE_WRITE) {
  239         $this->setDataModified();
  240         return Connector::MODE_WRITE;
  241       }
  242       if ($this->masterOnly()) {
  243         return Connector::MODE_WRITE;
  244       }
  245       $switchOption = 0;
  246       if ($options = $this->papaya()->options) {
  247         $switchOption = $options->get('PAPAYA_DATABASE_CLUSTER_SWITCH', $switchOption);
  248       }
  249       switch ($switchOption) {
  250         /** @noinspection PhpMissingBreakStatementInspection */
  251       case 2 : // connection context
  252         if ($this->_dataModified) {
  253           return Connector::MODE_WRITE;
  254         }
  255         if ($connector = $this->getDatabaseConnector()) {
  256           return $connector->getConnectionMode();
  257         }
  258       case 1 : //object context
  259         return $this->_dataModified ? Connector::MODE_WRITE : Connector::MODE_READ;
  260       }
  261       return Connector::MODE_READ;
  262     }
  263 
  264     /**
  265      * Set data modified status (switch to write connection)
  266      */
  267     public function setDataModified() {
  268       $this->_dataModified = TRUE;
  269       if ($connector = $this->getDatabaseConnector()) {
  270         $connector->setDataModified();
  271       }
  272     }
  273 
  274     public function debugNextQuery($counter = 1) {
  275       if ($connector = $this->getDatabaseConnector()) {
  276         $connector->debugNextQuery($counter);
  277       }
  278     }
  279 
  280     public function enableAbsoluteCount() {
  281       if ($connector = $this->getDatabaseConnector()) {
  282         $connector->enableAbsoluteCount();
  283       }
  284     }
  285 
  286     public function getProtocol() {
  287       try {
  288         if ($connector = $this->getDatabaseConnector($mode = $this->getConnectionMode())) {
  289           return $connector->getProtocol($mode);
  290         }
  291       } catch (DatabaseException $exception) {
  292         $this->_handleDatabaseException($exception);
  293       }
  294       return '';
  295     }
  296 
  297     /**
  298      * Make it possible to define a different error callback. This will disable the default
  299      * error handling (dispatching log messages) and call the given callback. To remove the
  300      * callback and restore the default error handling set it to FALSE.
  301      *
  302      * @param callable|false $callback
  303      *
  304      * @return callable|null void
  305      * @throws \InvalidArgumentException
  306      *
  307      */
  308     public function errorHandler($callback = NULL) {
  309       if (NULL !== $callback) {
  310         if (FALSE === $callback) {
  311           $this->_errorHandler = NULL;
  312         } elseif (\is_callable($callback)) {
  313           $this->_errorHandler = $callback;
  314         } else {
  315           throw new \InvalidArgumentException('Given error callback is not callable.');
  316         }
  317       }
  318       return $this->_errorHandler;
  319     }
  320 
  321     /**
  322      * Call the given error handler callback or if none is defined dispatch a log message.
  323      *
  324      * @param Exception $exception
  325      */
  326     private function _handleDatabaseException(Exception $exception) {
  327       $errorHandler = $this->errorHandler();
  328       if (NULL !== $errorHandler) {
  329         $errorHandler($exception);
  330       } elseif (
  331         $messages = $this->papaya()->getObject('messages', TRUE)
  332       ) {
  333         $mapSeverity = [
  334           DatabaseException::SEVERITY_INFO => Message::SEVERITY_INFO,
  335           DatabaseException::SEVERITY_WARNING => Message::SEVERITY_WARNING,
  336           DatabaseException::SEVERITY_ERROR => Message::SEVERITY_ERROR,
  337         ];
  338         $logMsg = new Message\Log(
  339           Message\Logable::GROUP_DATABASE,
  340           $mapSeverity[$exception->getSeverity()],
  341           'Database #'.$exception->getCode().': '.$exception->getMessage()
  342         );
  343         $logMsg->context()->append(new Message\Context\Backtrace(3));
  344         if ($exception instanceof Exception\QueryFailed) {
  345           $logMsg->context()->append(new Message\Context\Text($exception->getStatement()));
  346         }
  347         $messages->dispatch($logMsg);
  348       }
  349     }
  350 
  351     /**
  352      * @param \Papaya\Database\Statement|string $statement
  353      * @param int $options
  354      * @return mixed
  355      */
  356     public function execute($statement, $options = 0) {
  357       try {
  358         if ($connector = $this->getDatabaseConnector($mode = $this->getConnectionMode())) {
  359           return $connector->execute($statement, $options, $mode);
  360         }
  361       } catch (DatabaseException $exception) {
  362         $this->_handleDatabaseException($exception);
  363       }
  364       return FALSE;
  365     }
  366 
  367     /**
  368      * @return \Papaya\Database\Schema
  369      * @throws \Papaya\Database\Exception\ConnectionFailed
  370      */
  371     public function schema() {
  372       $mode = $this->getConnectionMode(Connector::MODE_WRITE);
  373       if ($connector = $this->getDatabaseConnector($mode)) {
  374         return $connector->schema($mode);
  375       }
  376       throw new DatabaseException\ConnectionFailed(
  377         \sprintf(
  378           'Database connector not available.'
  379         )
  380       );
  381     }
  382 
  383     /**
  384      * @return \Papaya\Database\Syntax
  385      * @throws \Papaya\Database\Exception\ConnectionFailed
  386      */
  387     public function syntax() {
  388       $mode = $this->getConnectionMode(Connector::MODE_WRITE);
  389       if ($connector = $this->getDatabaseConnector($mode)) {
  390         return $connector->syntax($mode);
  391       }
  392       throw new DatabaseException\ConnectionFailed(
  393         \sprintf(
  394           'Database connector not available.'
  395         )
  396       );
  397     }
  398 
  399     public function isExtensionAvailable() {
  400       throw new \BadMethodCallException('General class, does not depend on extension.');
  401     }
  402 
  403     /**
  404      * @param string $mode
  405      * @return \Papaya\Database\Connection
  406      */
  407     public function connect($mode = Connector::MODE_READ) {
  408       $this->getDatabaseConnector($this->getConnectionMode($mode));
  409       return $this;
  410     }
  411 
  412     /**
  413      * Close database connection(s)
  414      */
  415     public function disconnect() {
  416       if ($connector = $this->getDatabaseConnector()) {
  417         $connector->disconnect();
  418       }
  419     }
  420 
  421     /**
  422      * Add close function alias for BC
  423      *
  424      * @deprecated
  425      */
  426     public function close() {
  427       $this->disconnect();
  428     }
  429 
  430     /**
  431      * @param string $name
  432      * @param callable $function
  433      * @return bool
  434      */
  435     public function registerFunction($name, callable $function) {
  436       try {
  437         if ($connector = $this->getDatabaseConnector()) {
  438           return $connector->connect()->registerFunction($name, $function);
  439         }
  440       } catch (DatabaseException $exception) {
  441         $this->_handleDatabaseException($exception);
  442       }
  443       return FALSE;
  444     }
  445 
  446     /**
  447      * @param string $literal
  448      * @return string
  449      */
  450     public function escapeString($literal) {
  451       try {
  452         $mode = $this->getConnectionMode();
  453         if ($connector = $this->getDatabaseConnector($mode)) {
  454           return $connector->escapeString($literal, $mode);
  455         }
  456       } catch (DatabaseException $exception) {
  457         $this->_handleDatabaseException($exception);
  458       }
  459       return '';
  460     }
  461 
  462     /**
  463      * @param string $literal
  464      * @return string
  465      */
  466     public function quoteString($literal) {
  467       try {
  468         $mode = $this->getConnectionMode();
  469         if ($connector = $this->getDatabaseConnector($mode)) {
  470           return $connector->quoteString($literal, $mode);
  471         }
  472       } catch (DatabaseException $exception) {
  473         $this->_handleDatabaseException($exception);
  474       }
  475       return '';
  476     }
  477 
  478     /**
  479      * @param string $name
  480      * @return string
  481      */
  482     public function quoteIdentifier($name) {
  483       try {
  484         $mode = $this->getConnectionMode();
  485         if ($connector = $this->getDatabaseConnector($mode)) {
  486           return $connector->quoteIdentifier($name, $mode);
  487         }
  488       } catch (DatabaseException $exception) {
  489         $this->_handleDatabaseException($exception);
  490       }
  491       if (\preg_match('((?:[a-zA-Z\\d_]\\.)?[a-zA-Z\\d_])', $name)) {
  492         return $name;
  493       }
  494       return '_invalid_identifier_';
  495     }
  496 
  497     /**
  498      * @param string $tableName
  499      * @param array $values
  500      * @return bool
  501      */
  502     public function insert($tableName, array $values) {
  503       try {
  504         $mode = $this->getConnectionMode(Connector::MODE_WRITE);
  505         if ($connector = $this->getDatabaseConnector($mode)) {
  506           return $connector->connect()->insert($tableName, $values);
  507         }
  508       } catch (DatabaseException $exception) {
  509         $this->_handleDatabaseException($exception);
  510       }
  511       return FALSE;
  512     }
  513 
  514     /**
  515      * @param string $tableName
  516      * @param string $idField
  517      * @return bool
  518      */
  519     public function lastInsertId($tableName, $idField) {
  520       try {
  521         $mode = $this->getConnectionMode(Connector::MODE_WRITE);
  522         if ($connector = $this->getDatabaseConnector($mode)) {
  523           return $connector->connect()->lastInsertId(
  524             $tableName, $idField
  525           );
  526         }
  527       } catch (DatabaseException $exception) {
  528         $this->_handleDatabaseException($exception);
  529       }
  530       return FALSE;
  531     }
  532 
  533     /**
  534      * @param string $sql
  535      * @param null $max
  536      * @param null $offset
  537      * @param bool $readOnly
  538      * @return bool|Result|int
  539      * @deprecated
  540      */
  541     public function query($sql, $max = NULL, $offset = NULL, $readOnly = TRUE) {
  542       return $this->execute(
  543         new LimitedStatement(
  544           $this, $sql instanceof Statement ? $sql : new SQLStatement($sql), $max, $offset
  545         ),
  546         $readOnly ? self::USE_WRITE_CONNECTION : self::EMPTY_OPTIONS
  547       );
  548     }
  549 
  550     /**
  551      * @param string $sql
  552      * @return bool|int
  553      * @deprecated
  554      */
  555     public function queryWrite($sql) {
  556       return $this->execute(
  557         $sql instanceof Statement ? $sql : new SQLStatement($sql),
  558         self::USE_WRITE_CONNECTION
  559       );
  560     }
  561 
  562     /**
  563      * @param string $sql
  564      * @param array|string|NULL $values
  565      * @param null|int $max
  566      * @param null|int $offset
  567      * @param bool $readOnly
  568      * @return bool|Result|int
  569      * @deprecated
  570      */
  571     public function queryFmt($sql, $values, $max = NULL, $offset = NULL, $readOnly = TRUE) {
  572       if (NULL === $values) {
  573         $values = [];
  574       } elseif (!is_array($values)) {
  575         $values = [$values];
  576       }
  577       return $this->execute(
  578         new LimitedStatement(
  579           $this, $sql instanceof Statement ? $sql : new FormattedStatement($this, $sql, $values), $max, $offset
  580         ),
  581         $readOnly ? self::USE_WRITE_CONNECTION : self::EMPTY_OPTIONS
  582       );
  583     }
  584 
  585     /**
  586      * @param string $sql
  587      * @param array|string|NULL $values
  588      * @return bool|int
  589      * @deprecated
  590      */
  591     public function queryFmtWrite($sql, $values) {
  592       return $this->queryFmt($sql, $values, NULL, NULL, FALSE);
  593     }
  594 
  595     /**
  596      * @param array $tableStructure
  597      * @param string $tablePrefix
  598      * @return bool
  599      * @deprecated
  600      */
  601     public function createTable(array $tableStructure, $tablePrefix = '') {
  602       try {
  603         return $this->schema()->createTable($tableStructure, $tablePrefix);
  604       } catch (DatabaseException $exception) {
  605         $this->_handleDatabaseException($exception);
  606       }
  607       return FALSE;
  608     }
  609 
  610     /**
  611      * @param string $tableName
  612      * @param array $fieldStructure
  613      * @return bool
  614      * @deprecated
  615      */
  616     public function addField($tableName, array $fieldStructure) {
  617       try {
  618         return $this->schema()->addField($tableName, $fieldStructure);
  619       } catch (DatabaseException $exception) {
  620         $this->_handleDatabaseException($exception);
  621       }
  622       return FALSE;
  623     }
  624 
  625     /**
  626      * @param string $tableName
  627      * @param array $indexStructure
  628      * @return bool
  629      * @deprecated
  630      */
  631     public function addIndex($tableName, array $indexStructure) {
  632       try {
  633         return $this->schema()->addIndex($tableName, $indexStructure);
  634       } catch (DatabaseException $exception) {
  635         $this->_handleDatabaseException($exception);
  636       }
  637       return FALSE;
  638     }
  639 
  640     /**
  641      * @param string $tableName
  642      * @param array $fieldStructure
  643      * @return bool
  644      * @deprecated
  645      */
  646     public function changeField($tableName, array $fieldStructure) {
  647       try {
  648         return $this->schema()->changeField($tableName, $fieldStructure);
  649       } catch (DatabaseException $exception) {
  650         $this->_handleDatabaseException($exception);
  651       }
  652       return FALSE;
  653     }
  654 
  655     /**
  656      * @param string $tableName
  657      * @param array $indexStructure
  658      * @return bool
  659      * @deprecated
  660      */
  661     public function changeIndex($tableName, array $indexStructure) {
  662       try {
  663         return $this->schema()->changeIndex($tableName, $indexStructure);
  664       } catch (DatabaseException $exception) {
  665         $this->_handleDatabaseException($exception);
  666       }
  667       return FALSE;
  668     }
  669 
  670     /**
  671      * @param array $expectedStructure
  672      * @param array $currentStructure
  673      * @return bool
  674      * @deprecated
  675      */
  676     public function compareFieldStructure(array $expectedStructure, array $currentStructure) {
  677       try {
  678         return $this->schema()->isFieldDifferent($expectedStructure, $currentStructure);
  679       } catch (DatabaseException $exception) {
  680         $this->_handleDatabaseException($exception);
  681       }
  682       return FALSE;
  683     }
  684 
  685     /**
  686      * @param array $expectedStructure
  687      * @param array $currentStructure
  688      * @return bool
  689      * @deprecated
  690      */
  691     public function compareKeyStructure(array $expectedStructure, array $currentStructure) {
  692       try {
  693         return $this->schema()->isIndexDifferent($expectedStructure, $currentStructure);
  694       } catch (DatabaseException $exception) {
  695         $this->_handleDatabaseException($exception);
  696       }
  697       return FALSE;
  698     }
  699 
  700     /**
  701      * @param $tableName
  702      * @param $fieldName
  703      * @return bool
  704      * @deprecated
  705      */
  706     public function dropField($tableName, $fieldName) {
  707       try {
  708         return $this->schema()->dropField($tableName, $fieldName);
  709       } catch (DatabaseException $exception) {
  710         $this->_handleDatabaseException($exception);
  711       }
  712       return FALSE;
  713     }
  714 
  715     /**
  716      * @param $tableName
  717      * @param $indexName
  718      * @return bool
  719      * @deprecated
  720      */
  721     public function dropIndex($tableName, $indexName) {
  722       try {
  723         return $this->schema()->dropIndex($tableName, $indexName);
  724       } catch (DatabaseException $exception) {
  725         $this->_handleDatabaseException($exception);
  726       }
  727       return FALSE;
  728     }
  729 
  730     /**
  731      * @return array
  732      * @deprecated
  733      */
  734     public function queryTableNames() {
  735       try {
  736         return $this->schema()->getTables();
  737       } catch (DatabaseException $exception) {
  738         $this->_handleDatabaseException($exception);
  739       }
  740       return [];
  741     }
  742 
  743     /**
  744      * @param $tableName
  745      * @return array|NULL
  746      * @deprecated
  747      */
  748     public function queryTableStructure($tableName) {
  749       try {
  750         return $this->schema()->describeTable($tableName);
  751       } catch (DatabaseException $exception) {
  752         $this->_handleDatabaseException($exception);
  753       }
  754       return NULL;
  755     }
  756 
  757     /**
  758      * @param $tableName
  759      * @param array $values
  760      * @return bool
  761      * @deprecated
  762      */
  763     public function insertRecords($tableName, array $values) {
  764       return $this->insert($tableName, $values);
  765     }
  766 
  767     /**
  768      * @param string $tableName
  769      * @return int
  770      * @deprecated
  771      */
  772     public function emptyTable($tableName) {
  773       $sql = sprintf(
  774         'DELETE FROM %s',
  775         $this->quoteIdentifier($tableName)
  776       );
  777       return $this->execute($sql, self::DISABLE_RESULT_CLEANUP | self::USE_WRITE_CONNECTION);
  778     }
  779 
  780     /**
  781      * @param string $tableName
  782      * @param array $fields
  783      * @param array|string $filter
  784      * @param mixed $value
  785      * @return array|FALSE
  786      * @deprecated
  787      */
  788     public function loadRecord($tableName, array $fields, $filter, $value = NULL) {
  789       $sql = 'SELECT ';
  790       if (count($fields) > 0) {
  791         foreach ($fields as $fieldName) {
  792           $sql .= $this->quoteIdentifier(trim($fieldName)).', ';
  793         }
  794         $sql = substr($sql, 0, -2);
  795       } else {
  796         $sql .= '*';
  797       }
  798       $sql .= ' FROM '.$this->quoteIdentifier($tableName);
  799       $sql .= ' WHERE '.$this->getSQLCondition($filter, $value);
  800       $statement = new LimitedStatement($this, new SQLStatement($sql), 1);
  801       if (
  802         ($result = $this->execute($statement)) &&
  803         ($row = $result->fetchAssoc())
  804       ) {
  805         return $row;
  806       }
  807       return FALSE;
  808     }
  809 
  810     /**
  811      * @param string $tableName
  812      * @param string|NULL $identifierField
  813      * @param array $values
  814      * @return bool|string
  815      * @deprecated
  816      */
  817     public function insertRecord($tableName, $identifierField, array $values) {
  818       try {
  819         $mode = $this->getConnectionMode(Connector::MODE_WRITE);
  820         if ($connector = $this->getDatabaseConnector($mode)) {
  821           return $connector->insertRecord(
  822             $tableName, $identifierField, $values
  823           );
  824         }
  825       } catch (DatabaseException $exception) {
  826         $this->_handleDatabaseException($exception);
  827       }
  828       return FALSE;
  829     }
  830 
  831     /**
  832      * Change database records
  833      *
  834      * @param string $tableName Table
  835      * @param array $values values
  836      * @param array|NULL|string $filter condition
  837      * @param mixed $value
  838      * @return \Papaya\Database\Result|boolean|integer
  839      * @deprecated
  840      */
  841     public function updateRecord($tableName, array $values, $filter, $value = NULL) {
  842       if (isset($values) && is_array($values) && count($values) > 0) {
  843         $sql = '';
  844         foreach ($values as $fieldName => $fieldValue) {
  845           $fieldName = trim($fieldName);
  846           if ($fieldValue === NULL) {
  847             $sql .= ' '.$this->quoteIdentifier($fieldName).' = NULL, ';
  848           } else {
  849             if (is_bool($fieldValue)) {
  850               $fieldValue = $fieldValue ? '1' : '0';
  851             }
  852             $sql .= ' '.$this->quoteIdentifier($fieldName).' = '.$this->quoteString($fieldValue).', ';
  853           }
  854         }
  855         if (!empty($sql)) {
  856           $condition = $this->getSQLCondition($filter, $value);
  857           if (!empty($filter) && trim($condition) === '') {
  858             throw new \UnexpectedValueException(
  859               'Filter argument could not be compiled to a valid atabase condition.'
  860             );
  861           }
  862           $sql = sprintf(
  863             'UPDATE %s SET %s %s',
  864             $this->quoteIdentifier($tableName),
  865             substr($sql, 0, -2),
  866             trim($condition) !== '' ? ' WHERE '.$condition : ''
  867           );
  868           return $this->execute($sql, self::USE_WRITE_CONNECTION);
  869         }
  870       }
  871       return FALSE;
  872     }
  873 
  874     /**
  875      * @param string $tableName
  876      * @param string|array $filter
  877      * @param mixed $value
  878      * @return int|FALSE
  879      * @deprecated
  880      */
  881     public function deleteRecord($tableName, $filter, $value = NULL) {
  882       $sql = sprintf(
  883         'DELETE FROM %s WHERE %s',
  884         $this->quoteIdentifier($tableName),
  885         $this->getSQLCondition($this->getConditionArray($filter, $value))
  886       );
  887       return $this->execute($sql, self::DISABLE_RESULT_CLEANUP | self::USE_WRITE_CONNECTION);
  888     }
  889 
  890     /**
  891      * @param $function
  892      * @param array $parameters [$parameter1, $escape = TRUE, $parameter1, $escape = TRUE, ...]
  893      * @return string|NULL
  894      * @deprecated
  895      */
  896     public function getSQLSource($function, ...$parameters) {
  897       try {
  898         $arguments = [];
  899         for ($i = 0, $c = count($parameters); $i < $c; $i += 2) {
  900           if (isset($parameters[$i + 1]) && !$parameters[$i + 1]) {
  901             $arguments[] = new SQLSource($parameters[$i]);
  902           } else {
  903             $arguments[] = (string)$parameters[$i];
  904           }
  905         }
  906         $call = [$this->syntax(), $function];
  907         $source = $call(...$arguments);
  908         return $source ?: NULL;
  909       } catch (DatabaseException $exception) {
  910         $this->_handleDatabaseException($exception);
  911       }
  912       return NULL;
  913     }
  914 
  915     /**
  916      * @param array|string $filter
  917      * @param mixed $value
  918      * @param string $operator
  919      * @return string|NULL
  920      * @deprecated
  921      */
  922     public function getSQLCondition($filter, $value = NULL, $operator = '=') {
  923       try {
  924         if (
  925           (is_scalar($filter) && (string)$filter === '1' && (string)$value === 1) ||
  926           (is_array($filter) && isset($filter['1']) && (string)$filter['1'] === '1')
  927         ) {
  928           return '(1 = 1)';
  929         }
  930         $mode = $this->getConnectionMode();
  931         if ($connector = $this->getDatabaseConnector($mode)) {
  932           return $connector->getSqlCondition($filter, $value, $operator, $mode);
  933         }
  934       } catch (DatabaseException $exception) {
  935         $this->_handleDatabaseException($exception);
  936       }
  937       return '(0 = 1)';
  938     }
  939 
  940     /**
  941      * Convert different $filter arguments to an array
  942      *
  943      * @param string|array|NULL $filter
  944      * @param mixed $value
  945      * @return array|NULL
  946      */
  947     private function getConditionArray($filter, $value = NULL) {
  948       if (empty($filter)) {
  949         return NULL;
  950       }
  951       if (is_string($filter)) {
  952         return [$filter => $value];
  953       }
  954       return $filter;
  955     }
  956   }
  957 }