Skip to content

Commit

Permalink
Merge pull request #200 from bcit-ci/prepare
Browse files Browse the repository at this point in the history
Prepare
  • Loading branch information
lonnieezell authored Aug 9, 2016
2 parents e9f3d9d + dfc39cd commit 099cc8f
Show file tree
Hide file tree
Showing 11 changed files with 811 additions and 8 deletions.
104 changes: 99 additions & 5 deletions system/Database/BaseConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ abstract class BaseConnection implements ConnectionInterface
*/
protected $connectDuration;

/**
* If true, no queries will actually be
* ran against the database.
*
* @var bool
*/
protected $pretend = false;

//--------------------------------------------------------------------

/**
Expand Down Expand Up @@ -478,6 +486,26 @@ public function saveQueries($save = false)

//--------------------------------------------------------------------

/**
* Stores a new query with the object. This is primarily used by
* the prepared queries.
*
* @param \CodeIgniter\Database\Query $query
*
* @return $this
*/
public function addQuery(Query $query)
{
if ($this->saveQueries)
{
$this->queries[] = $query;
}

return $this;
}

//--------------------------------------------------------------------

/**
* Executes the query against the database.
*
Expand Down Expand Up @@ -523,14 +551,16 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da

$startTime = microtime(true);

// Run the query
if (false === ($this->resultID = $this->simpleQuery($query->getQuery())))


// Run the query for real
if (! $this->pretend && false === ($this->resultID = $this->simpleQuery($query->getQuery())))
{
$query->setDuration($startTime, $startTime);

// @todo deal with errors

if ($this->saveQueries)
if ($this->saveQueries && ! $this->pretend)
{
$this->queries[] = $query;
}
Expand All @@ -540,12 +570,17 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da

$query->setDuration($startTime);

if ($this->saveQueries)
if ($this->saveQueries && ! $this->pretend)
{
$this->queries[] = $query;
}

return new $resultClass($this->connID, $this->resultID);
// If $pretend is true, then we just want to return
// the actual query object here. There won't be
// any results to return.
return $this->pretend
? $query
: new $resultClass($this->connID, $this->resultID);
}

//--------------------------------------------------------------------
Expand Down Expand Up @@ -593,6 +628,46 @@ public function table($tableName)

//--------------------------------------------------------------------

/**
* Creates a prepared statement with the database that can then
* be used to execute multiple statements against. Within the
* closure, you would build the query in any normal way, though
* the Query Builder is the expected manner.
*
* Example:
* $stmt = $db->prepare(function($db)
* {
* return $db->table('users')
* ->where('id', 1)
* ->get();
* })
*
* @param \Closure $func
* @param array $options Passed to the prepare() method
*
* @return PreparedQueryInterface|null
*/
public function prepare(\Closure $func, array $options = [])
{
$this->pretend(true);

$sql = $func($this);

$this->pretend(false);

if ($sql instanceof QueryInterface)
{
$sql = $sql->getOriginalQuery();
}

$class = str_ireplace('Connection', 'PreparedQuery', get_class($this));
$class = new $class($this);

return $class->prepare($sql, $options);
}

//--------------------------------------------------------------------

/**
* Returns an array containing all of the
*
Expand Down Expand Up @@ -1289,6 +1364,25 @@ public function getFieldData(string $table)

//--------------------------------------------------------------------

/**
* Allows the engine to be set into a mode where queries are not
* actually executed, but they are still generated, timed, etc.
*
* This is primarily used by the prepared query functionality.
*
* @param bool $pretend
*
* @return $this
*/
public function pretend(bool $pretend = true)
{
$this->pretend = $pretend;

return $this;
}

//--------------------------------------------------------------------

/**
* Returns the last error code and message.
*
Expand Down
214 changes: 214 additions & 0 deletions system/Database/BasePreparedQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<?php namespace CodeIgniter\Database;

abstract class BasePreparedQuery implements PreparedQueryInterface
{
/**
* The prepared statement itself.
*
* @var
*/
protected $statement;

/**
* The error code, if any.
*
* @var int
*/
protected $errorCode;

/**
* The error message, if any.
*
* @var string
*/
protected $errorString;

/**
* Holds the prepared query object
* that is cloned during execute.
*
* @var Query
*/
protected $query;

/**
* A reference to the db connection to use.
*
* @var \CodeIgniter\Database\ConnectionInterface
*/
protected $db;

//--------------------------------------------------------------------

public function __construct(ConnectionInterface $db)
{
$this->db =& $db;
}

//--------------------------------------------------------------------

/**
* Prepares the query against the database, and saves the connection
* info necessary to execute the query later.
*
* NOTE: This version is based on SQL code. Child classes should
* override this method.
*
* @param string $sql
* @param array $options Passed to the connection's prepare statement.
*
* @return mixed
*/
public function prepare(string $sql, array $options = [], $queryClass = 'CodeIgniter\\Database\\Query')
{
// We only supports positional placeholders (?)
// in order to work with the execute method below, so we
// need to replace our named placeholders (:name)
$sql = preg_replace('/:[^\s,)]+/', '?', $sql);

$query = new $queryClass($this->db);

$query->setQuery($sql);

if (! empty($this->db->swapPre) && ! empty($this->db->DBPrefix))
{
$query->swapPrefix($this->db->DBPrefix, $this->db->swapPre);
}

$this->query = $query;

return $this->_prepare($query->getOriginalQuery(), $options);
}

//--------------------------------------------------------------------

/**
* The database-dependent portion of the prepare statement.
*
* @param string $sql
* @param array $options Passed to the connection's prepare statement.
*
* @return mixed
*/
abstract public function _prepare(string $sql, array $options = []);

//--------------------------------------------------------------------

/**
* Takes a new set of data and runs it against the currently
* prepared query. Upon success, will return a Results object.
*
* @param array $data
*
* @return ResultInterface
*/
public function execute(...$data)
{
// Execute the Query.
$startTime = microtime(true);

$this->_execute($data);

// Update our query object
$query = clone $this->query;
$query->setBinds($data);

$query->setDuration($startTime);

// Save it to the connection
$this->db->addQuery($query);

// Return a result object
$resultClass = str_replace('PreparedQuery', 'Result', get_class($this));

$resultID = $this->_getResult();

return new $resultClass($this->db->connID, $resultID);
}

//--------------------------------------------------------------------

/**
* The database dependant version of the execute method.
*
* @param array $data
*
* @return ResultInterface
*/
abstract public function _execute($data);

//--------------------------------------------------------------------

/**
* Returns the result object for the prepared query.
*
* @return mixed
*/
abstract public function _getResult();

//--------------------------------------------------------------------

/**
* Explicity closes the statement.
*/
public function close()
{
if (! is_object($this->statement))
{
return;
}

$this->statement->close();
}

//--------------------------------------------------------------------

/**
* Returns the SQL that has been prepared.
*
* @return string
*/
public function getQueryString(): string
{
return $this->sql;
}

//--------------------------------------------------------------------

/**
* A helper to determine if any error exists.
*
* @return bool
*/
public function hasError()
{
return ! empty($this->errorString);
}

//--------------------------------------------------------------------


/**
* Returns the error code created while executing this statement.
*
* @return string
*/
public function getErrorCode(): int
{
return $this->errorCode;
}

//--------------------------------------------------------------------

/**
* Returns the error message created while executing this statement.
*
* @return string
*/
public function getErrorMessage(): string
{
return $this->errorString;
}

//--------------------------------------------------------------------
}
5 changes: 3 additions & 2 deletions system/Database/MySQLi/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,17 @@ class Connection extends BaseConnection implements ConnectionInterface
*
* @var MySQLi
*/
protected $mysqli;
public $mysqli;

//--------------------------------------------------------------------

/**
* Connect to the database.
*
* @param bool $persistent
*
* @return mixed
* @throws \CodeIgniter\DatabaseException
*/
public function connect($persistent = false)
{
Expand Down Expand Up @@ -452,5 +454,4 @@ public function insertID()

//--------------------------------------------------------------------


}
Loading

1 comment on commit 099cc8f

@Portaflex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. I´ll try it. Thanks.

Please sign in to comment.