<commit_before><?php

namespace Phlite\Db\Backends\MySQL;

use Phlite\Db;
use Phlite\Db\Exception;
use Phlite\Db\Compile\SqlExecutor;
use Phlite\Db\Compile\Statement;

class MySQLiExecutor
implements SqlExecutor {

    var $stmt;
    var $fields = array();
    
    var $backend;
    var $conn;
    var $res;   // Server resource / cursor
    
    // Timing
    var $time_prepare;
    var $time_fetch;

    function __construct(Statement $stmt, Db\Backend $bk) {
        $this->stmt = $stmt;
        $this->backend = $bk;
    }
    function __destruct() {
        $this->close();
    }
    
    // Array of [count, model] values representing which fields in the
    // result set go with witch model.  Useful for handling select_related
    // queries
    function getMap() {
        return $this->stmt->map;
    }

    function execute() {
        // Lazy connect to the database
        if (!isset($this->conn))
            $this->conn = $this->backend->getConnection();
        
        // TODO: Detect server/client abort, pause and attempt reconnection
        
        $start = microtime(true);
        if (!($this->res = $this->conn->prepare($this->stmt->sql)))
            throw new Exception\InconsistentModel(
                'Unable to prepare query: '.$this->conn->error
                .': '.$this->stmt->sql);
        
        // TODO: Implement option to drop parameters
        
        if ($this->stmt->hasParameters())
            $this->_bind($this->stmt, $this->res);
        if (!$this->res->execute() || !$this->res->store_result()) {
            throw new Exception\DbError('Unable to execute query: ' . $this->res->error);
        }
        $this->_setup_output();
        
        $this->time_prepare = microtime(true) - $start;
        return true;
    }
    
    /**
     * Remove the parameters from the string and replace them with escaped
     * values. This is reportedly faster for MySQL and equally as safe.
     */
    function _unparameterize() {
        if (!isset($this->conn))
            $this->conn = $this->backend->getConnection();
        $conn = $this->conn;
        return $this->stmt->toString(function($i) use ($conn) {
            $q = $self->conn->real_escape($i);
            if (is_string($i))
                return "'$i'";
            return $i;            
        });
    }

    function _bind(Statement $stmt, $res) {
        $params = $stmt->getParameters();
        if (count($params) != $res->param_count)
            throw new Exception\OrmError('Parameter count does not match query');

        $types = '';
        $ps = array();
        foreach ($params as &$p) {
            if (is_int($p) || is_bool($p))
                $types .= 'i';
            elseif (is_float($p))
                $types .= 'd';
            elseif (is_string($p))
                $types .= 's';
            elseif ($p instanceof \DateTime) {
                // TODO: Detect database timezone and convert accordingly
                // for a non-naive DateTime instance
                $types .= 's';
                $p = $p->format('Y-m-d h:i:s');
            }
            // FIXME: Emit error if param is null
            $ps[] = &$p;
        }
        unset($p);
        array_unshift($ps, $types);
        call_user_func_array(array($res, 'bind_param'), $ps);
    }

    function _setup_output() {
        if (!($meta = $this->res->result_metadata()))
            throw new Exception\DbError(
                'Unable to fetch statment metadata: ', $this->res->error);
        $this->fields = $meta->fetch_fields();
        $meta->free_result();
    }

    // Iterator interface
    function rewind() {
        if (!isset($this->res))
            $this->execute();
        $this->res->data_seek(0);
    }

    function next() {
        $status = $this->res->fetch();
        if ($status === false)
            throw new Exception\DbError($this->res->error);
        elseif ($status === null) {
            $this->close();
            return false;
        }
        return true;
    }

    function fetchArray() {
        $output = array();
        $variables = array();

        if (!isset($this->res))
            $this->execute();<commit_msg>db: oops: MySQLi may return false for UPDATE stmts<commit_after><?php

namespace Phlite\Db\Backends\MySQL;

use Phlite\Db;
use Phlite\Db\Exception;
use Phlite\Db\Compile\SqlExecutor;
use Phlite\Db\Compile\Statement;

class MySQLiExecutor
implements SqlExecutor {

    var $stmt;
    var $fields = array();
    
    var $backend;
    var $conn;
    var $res;   // Server resource / cursor
    
    // Timing
    var $time_prepare;
    var $time_fetch;

    function __construct(Statement $stmt, Db\Backend $bk) {
        $this->stmt = $stmt;
        $this->backend = $bk;
    }
    function __destruct() {
        $this->close();
    }
    
    // Array of [count, model] values representing which fields in the
    // result set go with witch model.  Useful for handling select_related
    // queries
    function getMap() {
        return $this->stmt->map;
    }

    function execute() {
        // Lazy connect to the database
        if (!isset($this->conn))
            $this->conn = $this->backend->getConnection();
        
        // TODO: Detect server/client abort, pause and attempt reconnection
        
        $start = microtime(true);
        if (!($this->res = $this->conn->prepare($this->stmt->sql)))
            throw new Exception\InconsistentModel(
                'Unable to prepare query: '.$this->conn->error
                .': '.$this->stmt->sql);
        
        // TODO: Implement option to drop parameters
        
        if ($this->stmt->hasParameters())
            $this->_bind($this->stmt, $this->res);
        if (!$this->res->execute() || !$this->res->store_result()) {
            throw new Exception\DbError('Unable to execute query: ' . $this->res->error);
        }
        $this->_setup_output();
        
        $this->time_prepare = microtime(true) - $start;
        return true;
    }
    
    /**
     * Remove the parameters from the string and replace them with escaped
     * values. This is reportedly faster for MySQL and equally as safe.
     */
    function _unparameterize() {
        if (!isset($this->conn))
            $this->conn = $this->backend->getConnection();
        $conn = $this->conn;
        return $this->stmt->toString(function($i) use ($conn) {
            $q = $self->conn->real_escape($i);
            if (is_string($i))
                return "'$i'";
            return $i;            
        });
    }

    function _bind(Statement $stmt, $res) {
        $params = $stmt->getParameters();
        if (count($params) != $res->param_count)
            throw new Exception\OrmError('Parameter count does not match query');

        $types = '';
        $ps = array();
        foreach ($params as &$p) {
            if (is_int($p) || is_bool($p))
                $types .= 'i';
            elseif (is_float($p))
                $types .= 'd';
            elseif (is_string($p))
                $types .= 's';
            elseif ($p instanceof \DateTime) {
                // TODO: Detect database timezone and convert accordingly
                // for a non-naive DateTime instance
                $types .= 's';
                $p = $p->format('Y-m-d h:i:s');
            }
            // FIXME: Emit error if param is null
            $ps[] = &$p;
        }
        unset($p);
        array_unshift($ps, $types);
        call_user_func_array(array($res, 'bind_param'), $ps);
    }

    function _setup_output() {
        if (!($meta = $this->res->result_metadata()) && $this->res->errno)
            throw new Exception\DbError(
                'Unable to fetch statment metadata: '. $this->res->error);
        if ($meta) {
            $this->fields = $meta->fetch_fields();
            $meta->free_result();
        }
    }

    // Iterator interface
    function rewind() {
        if (!isset($this->res))
            $this->execute();
        $this->res->data_seek(0);
    }

    function next() {
        $status = $this->res->fetch();
        if ($status === false)
            throw new Exception\DbError($this->res->error);
        elseif ($status === null) {
            $this->close();
            return false;
        }
        return true;
    }

    function fetchArray() {
        $output = array();
        $variables = array();

        if (!isset($this->res))
            $this->execute();
{'fraction_non_alphanumeric': 0.14952167237510333, 'fraction_numerical': 0.0002362111727884729, 'mean_word_length': 1.7601043024771839, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 127, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1873358', 'n_tokens_mistral': 2446, 'n_tokens_neox': 2386, 'n_words': 604}