Browse Source

Implemented real non-buffered Database statements.

The old implementation was secretly relying on PHP results buffering for
MySQL and it was impossible to turn off.

Also improved the Sqlite driver to only buffer results optionally as mysql
does.
Jose Lorenzo Rodriguez 11 years ago
parent
commit
a303ab4e96

+ 6 - 7
src/Database/Connection.php

@@ -269,8 +269,7 @@ class Connection
      */
     public function run(Query $query)
     {
-        $sql = $query->sql();
-        $statement = $this->prepare($sql);
+        $statement = $this->prepare($query);
         $query->valueBinder()->attachTo($statement);
         $statement->execute();
 
@@ -487,7 +486,7 @@ class Connection
      */
     public function createSavePoint($name)
     {
-        $this->execute($this->_driver->savePointSQL($name));
+        $this->execute($this->_driver->savePointSQL($name))->closeCursor();
     }
 
     /**
@@ -498,7 +497,7 @@ class Connection
      */
     public function releaseSavePoint($name)
     {
-        $this->execute($this->_driver->releaseSavePointSQL($name));
+        $this->execute($this->_driver->releaseSavePointSQL($name))->closeCursor();
     }
 
     /**
@@ -509,7 +508,7 @@ class Connection
      */
     public function rollbackSavepoint($name)
     {
-        $this->execute($this->_driver->rollbackSavePointSQL($name));
+        $this->execute($this->_driver->rollbackSavePointSQL($name))->closeCursor();
     }
 
     /**
@@ -519,7 +518,7 @@ class Connection
      */
     public function disableForeignKeys()
     {
-        $this->execute($this->_driver->disableForeignKeySql());
+        $this->execute($this->_driver->disableForeignKeySql())->closeCursor();
     }
 
     /**
@@ -529,7 +528,7 @@ class Connection
      */
     public function enableForeignKeys()
     {
-        $this->execute($this->_driver->enableForeignKeySql());
+        $this->execute($this->_driver->enableForeignKeySql())->closeCursor();
     }
 
     /**

+ 20 - 0
src/Database/Driver/Mysql.php

@@ -15,6 +15,8 @@
 namespace Cake\Database\Driver;
 
 use Cake\Database\Dialect\MysqlDialectTrait;
+use Cake\Database\Query;
+use Cake\Database\Statement\MysqlStatement;
 use PDO;
 
 class Mysql extends \Cake\Database\Driver
@@ -100,4 +102,22 @@ class Mysql extends \Cake\Database\Driver
     {
         return in_array('mysql', PDO::getAvailableDrivers());
     }
+
+    /**
+     * Prepares a sql statement to be executed
+     *
+     * @param string|\Cake\Database\Query $query The query to prepare.
+     * @return \Cake\Database\StatementInterface
+     */
+    public function prepare($query)
+    {
+        $this->connect();
+        $statement = $this->_connection->prepare((string)$query);
+        $result = new MysqlStatement($statement, $this);
+        if ($query instanceof Query && $query->bufferResults() === false) {
+            $result->bufferResults(false);
+        }
+        return $result;
+    }
+
 }

+ 6 - 1
src/Database/Driver/Sqlite.php

@@ -15,6 +15,7 @@
 namespace Cake\Database\Driver;
 
 use Cake\Database\Dialect\SqliteDialectTrait;
+use Cake\Database\Query;
 use Cake\Database\Statement\PDOStatement;
 use Cake\Database\Statement\SqliteStatement;
 use PDO;
@@ -87,6 +88,10 @@ class Sqlite extends \Cake\Database\Driver
     {
         $this->connect();
         $statement = $this->_connection->prepare((string)$query);
-        return new SqliteStatement(new PDOStatement($statement, $this), $this);
+        $result = new SqliteStatement(new PDOStatement($statement, $this), $this);
+        if ($query instanceof Query && $query->bufferResults() === false) {
+            $result->bufferResults(false);
+        }
+        return $result;
     }
 }

+ 58 - 0
src/Database/Statement/MysqlStatement.php

@@ -0,0 +1,58 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Database\Statement;
+
+use PDO;
+
+/**
+ * Statement class meant to be used by a Mysql PDO driver
+ *
+ * @internal
+ */
+class MysqlStatement extends PDOStatement
+{
+
+     /**
+     * Whether or not to buffer results in php
+     *
+     * @var bool
+     */
+    protected $_bufferResults = true;
+
+    /**
+     * {@inheritDoc}
+     *
+     */
+    public function execute($params = null)
+    {
+        $this->_driver->connection()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $this->_bufferResults);
+        $result = $this->_statement->execute($params);
+        $this->_driver->connection()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+        return $result;
+    }
+
+     /**
+     * Whether or not to buffer results in php
+     *
+     * @param bool $buffer Toggle buffering
+     * @return $this
+     */
+    public function bufferResults($buffer)
+    {
+        $this->_bufferResults = (bool)$buffer;
+        return $this;
+    }
+
+}

+ 38 - 1
src/Database/Statement/SqliteStatement.php

@@ -19,10 +19,35 @@ namespace Cake\Database\Statement;
  *
  * @internal
  */
-class SqliteStatement extends BufferedStatement
+class SqliteStatement extends StatementDecorator
 {
 
     /**
+     * Whether or not to buffer results in php
+     *
+     * @var bool
+     */
+    protected $_bufferResults = true;
+
+    /**
+     * {@inheritDoc}
+     *
+     */
+    public function execute($params = null)
+    {
+        if ($this->_statement instanceof BufferedStatement) {
+            $this->_statement = $this->_statement->getInnerStatement();
+        }
+
+        if ($this->_bufferResults) {
+            $this->_statement = new BufferedStatement($this->_statement, $this->_driver);
+        }
+
+        $result = $this->_statement->execute($params);
+        return $result;
+    }
+
+    /**
      * Returns the number of rows returned of affected by last execution
      *
      * @return int
@@ -38,4 +63,16 @@ class SqliteStatement extends BufferedStatement
         }
         return parent::rowCount();
     }
+
+     /**
+     * Whether or not to buffer results in php
+     *
+     * @param bool $buffer Toggle buffering
+     * @return $this
+     */
+    public function bufferResults($buffer)
+    {
+        $this->_bufferResults = (bool)$buffer;
+        return $this;
+    }
 }

+ 10 - 0
src/Database/Statement/StatementDecorator.php

@@ -238,6 +238,7 @@ class StatementDecorator implements StatementInterface, \Countable, \IteratorAgg
      */
     public function getIterator()
     {
+        $this->execute();
         return $this->_statement;
     }
 
@@ -297,4 +298,13 @@ class StatementDecorator implements StatementInterface, \Countable, \IteratorAgg
         }
         return $this->_driver->lastInsertId($table, $column);
     }
+
+    /**
+     * Returns the statement object that was decorated by this class.
+     *
+     * @return \Cake\Database\StatementInterface
+     */
+    public function getInnerStatement() {
+        return $this->_statement;
+    }
 }

+ 10 - 0
tests/TestCase/Database/ConnectionTest.php

@@ -274,6 +274,11 @@ class ConnectionTest extends TestCase
         $this->assertEquals(2, $result['total']);
         $total->closeCursor();
 
+        $total->execute();
+        $result = $total->fetch('assoc');
+        $this->assertEquals(2, $result['total']);
+        $total->closeCursor();
+
         $result = $this->connection->execute('SELECT title, body  FROM things');
         $row = $result->fetch('assoc');
         $this->assertEquals('a title', $row['title']);
@@ -283,6 +288,11 @@ class ConnectionTest extends TestCase
         $result->closeCursor();
         $this->assertEquals('another title', $row['title']);
         $this->assertEquals('another body', $row['body']);
+
+        $result->execute();
+        $row = $result->fetch('assoc');
+        $result->closeCursor();
+        $this->assertEquals('a title', $row['title']);
     }
 
     /**

+ 30 - 0
tests/TestCase/Database/QueryTest.php

@@ -3132,6 +3132,36 @@ class QueryTest extends TestCase
     }
 
     /**
+     * Shows that bufferResults(false) will prevent client-side results buffering
+     *
+     * @return void
+     */
+    public function testUnbufferedQuery() {
+        $query = new Query($this->connection);
+        $result = $query->select(['body', 'author_id'])
+            ->from('articles')
+            ->bufferResults(false)
+            ->execute();
+
+        $list = $result->fetchAll('assoc');
+        $this->assertCount(3, $list);
+
+        $list = $result->fetchAll('assoc');
+        $this->assertCount(0, $list);
+
+        $query = new Query($this->connection);
+        $result = $query->select(['body', 'author_id'])
+            ->from('articles')
+            ->execute();
+
+        $list = $result->fetchAll('assoc');
+        $this->assertCount(3, $list);
+
+        $list = $result->fetchAll('assoc');
+        $this->assertCount(3, $list);
+    }
+
+    /**
      * Assertion for comparing a table's contents with what is in it.
      *
      * @param string $table