Skip to content

Commit

Permalink
auto slave support
Browse files Browse the repository at this point in the history
  • Loading branch information
ddliu committed May 26, 2015
1 parent f8ad324 commit 741c503
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 9 deletions.
40 changes: 39 additions & 1 deletion doc/connection.rst
Expand Up @@ -44,6 +44,7 @@ Database Options
The connection can be customized with the ``$options`` param. Commonly used options are listed below:

- ``prefix``: Table prefix, use {{table}} as table name, prefix will be prepended automatically.
- ``auto_slave``: Use slave connections when perform read only operations. (Note that it only works for query builder and model, not the connection it self)
- ``PDO::ATTR_ERRMODE`` Error reporting. Possible values:

- ``PDO::ERRMODE_SILENT``: Just set error codes.
Expand Down Expand Up @@ -78,4 +79,41 @@ Additionaly, it provide two important shortcut methods for you to work with Quer
<?php
$builder = $db->builder();
$factory = $db->factory('@user');
$factory = $db->factory('@user');
Multiple Connections
====================

You can add configurations for more than one connection, and switch between them.

.. code-block:: php
<?php
$db = new Connection($dsn, $username, $password, $options);
$db->addConnection('connection2', $dsn, $username, $password, $options);
$db->addConnection('connection3', $dsn, $username, $password, $options);
$db->switchConnection('connection2');
$db->switchConnection('connection3');
$db->switchConnection('default');
A useful usecase is "auto slave", you can setup replication for your database, and then use the `auto_slave` option
to improve performance.

.. code-block:: php
<?php
$db = new Connection($dsn, $username, $password, [
'auto_slave' => true
]);
$db->addConnection('slave', $dsnSlave, $username, $password);
// insert into master(default connection)
$db->builder()
->insert('user', $userdata);
// query is performed at slave database automatically
$db->builder()
->select('name')
->from('user')
->queryValue();
27 changes: 19 additions & 8 deletions src/Connection.php
Expand Up @@ -12,7 +12,11 @@
*/
class Connection
{
protected $current;
/**
* Current connection, null means select automatically, default means the default connection
* @var string
*/
protected $current = null;
protected $connections = [];
protected $configs = [];

Expand All @@ -24,18 +28,25 @@ class Connection
* @param array $options
*/
public function __construct($dsn = null, $username = null, $password = null, $options = array()){
$this->current = 'default';
$this->addConnection($this->current, $dsn, $username, $password, $options);
$this->addConnection('default', $dsn, $username, $password, $options);
}

/**
* Is connection manager in auto slave mode: it has `auto_slave` config, and connection is in automatic mode
* @return boolean
*/
public function isAutoSlave() {
return (!empty($this->configs['default']['options']['auto_slave'])) && isset($this->configs['slave']) && ($this->current === null);
}

/**
* Get connection config by name
* @param string $name Connection name
* @return array
*/
public function getConfig($name = null){
if(null === $name){
$name = $this->current;
$name = $this->current?:'default';
}
return $this->configs[$name];
}
Expand All @@ -47,14 +58,14 @@ public function getConfig($name = null){
*/
public function getPDO($name = null){
if(null === $name){
$name = $this->current;
$name = $this->current?:'default';
}
if(!isset($this->connections[$name])){
$config = $this->configs[$name];
$this->connections[$name] = new \PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
}

return $this->connections[$this->current];
return $this->connections[$name];
}

/**
Expand Down Expand Up @@ -96,7 +107,7 @@ public function addConnection($name, $dsn, $username = null, $password = null, $
* @param string $name
* @return \Ark\Database\Connection
*/
public function switchConnection($name = 'default'){
public function switchConnection($name = null){
$this->current = $name;

return $this;
Expand Down Expand Up @@ -280,7 +291,7 @@ public function buildLimitOffset($sql, $limit, $offset = 0){
}

public function getPrefix(){
return $this->configs[$this->current]['prefix'];
return $this->configs[$this->current?:'default']['prefix'];
}

public function fixPrefix($sql){
Expand Down
9 changes: 9 additions & 0 deletions src/QueryBuilder.php
Expand Up @@ -549,8 +549,17 @@ public function getSql(){
* Prepare statement before query
*/
protected function beginQuery(){
$autoSlave = $this->db->isAutoSlave();
if ($autoSlave) {
$this->db->switchConnection('slave');
}

$this->prepare();

if ($autoSlave) {
$this->db->switchConnection();
}

if(false === $this->statement->execute($this->params?$this->params:$this->positionParams)){
$info = $this->statement->errorInfo();
throw new Exception(sprintf('Statement error #%s: %s', $info[0], $info[2]));
Expand Down
84 changes: 84 additions & 0 deletions tests/AutoSlaveTest.php
@@ -0,0 +1,84 @@
<?php
/**
* ark.database
* @copyright 2015 Liu Dong <ddliuhb@gmail.com>
* @license MIT
*/

class AutoSlaveTest extends PHPUnit_Framework_TestCase{
protected $db;

protected function getSampleUser($key = 1){
return array(
'name' => 'user'.$key,
'email' => 'user'.$key.'@example.com',
'point' => $key
);
}

protected function setup(){
$this->db = new \Ark\Database\Connection('sqlite::memory:', '', '', [
'auto_slave' => true,
]);

$this->db->exec("
CREATE TABLE IF NOT EXISTS contact (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT,
point INTEGER DEFAULT 0
)
");

$this->db->addConnection('slave', 'sqlite::memory:', '', '');

$this->db->switchConnection('slave');
$this->db->exec("
CREATE TABLE IF NOT EXISTS contact (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT,
point INTEGER DEFAULT 0
)
");

$this->db->switchConnection();
}

protected function getAllByConnection($connection) {
$this->db->switchConnection($connection);
$result = [];
foreach ($this->db->query('SELECT * FROM contact') as $row) {
$result[] = $row['name'];
}

// switch to automatic mode
$this->db->switchConnection();

return $result;
}

public function testAutoSlave() {
// insert into master
$rst = $this->db->builder()
->insert('contact', [
'name' => 'user1',
'email' => 'user1@test.com',
]);

$this->assertTrue($rst > 0);

// the slave is not updated in our test case
$rst = $this->db->builder()
->select('name')
->from('contact')
->where('name = ?', ['user1'])
->queryValue();

$this->assertFalse($rst);

// confirm above tests
$this->assertContains('user1', $this->getAllByConnection('default'));
$this->assertNotContains('user1', $this->getAllByConnection('slave'));
}
}
File renamed without changes.

0 comments on commit 741c503

Please sign in to comment.