Skip to content

Commit

Permalink
Fix database cache on PostgreSQL (#25530)
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir authored and taylorotwell committed Sep 10, 2018
1 parent 41221db commit b2fa4e7
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 4 deletions.
42 changes: 38 additions & 4 deletions src/Illuminate/Cache/DatabaseStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

use Closure;
use Exception;
use Illuminate\Support\Str;
use Illuminate\Contracts\Cache\Store;
use Illuminate\Support\InteractsWithTime;
use Illuminate\Database\PostgresConnection;
use Illuminate\Database\ConnectionInterface;

class DatabaseStore implements Store
Expand Down Expand Up @@ -78,7 +80,7 @@ public function get($key)
return;
}

return unserialize($cache->value);
return $this->unserialize($cache->value);
}

/**
Expand All @@ -93,7 +95,7 @@ public function put($key, $value, $minutes)
{
$key = $this->prefix.$key;

$value = serialize($value);
$value = $this->serialize($value);

$expiration = $this->getTime() + (int) ($minutes * 60);

Expand Down Expand Up @@ -157,7 +159,7 @@ protected function incrementOrDecrement($key, $value, Closure $callback)

$cache = is_array($cache) ? (object) $cache : $cache;

$current = unserialize($cache->value);
$current = $this->unserialize($cache->value);

// Here we'll call this callback function that was given to the function which
// is used to either increment or decrement the function. We use a callback
Expand All @@ -172,7 +174,7 @@ protected function incrementOrDecrement($key, $value, Closure $callback)
// since database cache values are encrypted by default with secure storage
// that can't be easily read. We will return the new value after storing.
$this->table()->where('key', $prefixed)->update([
'value' => serialize($new),
'value' => $this->serialize($new),
]);

return $new;
Expand Down Expand Up @@ -253,4 +255,36 @@ public function getPrefix()
{
return $this->prefix;
}

/**
* Serialize the given value.
*
* @param mixed $value
* @return string
*/
protected function serialize($value)
{
$result = serialize($value);

if ($this->connection instanceof PostgresConnection && Str::contains($result, "\0")) {
$result = base64_encode($result);
}

return $result;
}

/**
* Unserialize the given value.
*
* @param string $value
* @return mixed
*/
protected function unserialize($value)
{
if ($this->connection instanceof PostgresConnection && ! Str::contains($value, [':', ';'])) {
$value = base64_decode($value);
}

return unserialize($value);
}
}
32 changes: 32 additions & 0 deletions tests/Cache/CacheDatabaseStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ public function testDecryptedValueIsReturnedWhenItemIsValid()
$this->assertEquals('bar', $store->get('foo'));
}

public function testValueIsReturnedOnPostgres()
{
$store = $this->getPostgresStore();
$table = m::mock('stdClass');
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
$table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table);
$table->shouldReceive('first')->once()->andReturn((object) ['value' => base64_encode(serialize('bar')), 'expiration' => 999999999999999]);

$this->assertEquals('bar', $store->get('foo'));
}

public function testValueIsInsertedWhenNoExceptionsAreThrown()
{
$store = $this->getMockBuilder('Illuminate\Cache\DatabaseStore')->setMethods(['getTime'])->setConstructorArgs($this->getMocks())->getMock();
Expand All @@ -74,6 +85,17 @@ public function testValueIsUpdatedWhenInsertThrowsException()
$store->put('foo', 'bar', 1);
}

public function testValueIsInsertedOnPostgres()
{
$store = $this->getMockBuilder('Illuminate\Cache\DatabaseStore')->setMethods(['getTime'])->setConstructorArgs($this->getPostgresMocks())->getMock();
$table = m::mock('stdClass');
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
$store->expects($this->once())->method('getTime')->will($this->returnValue(1));
$table->shouldReceive('insert')->once()->with(['key' => 'prefixfoo', 'value' => base64_encode(serialize("\0")), 'expiration' => 61]);

$store->put('foo', "\0", 1);
}

public function testForeverCallsStoreItemWithReallyLongTime()
{
$store = $this->getMockBuilder('Illuminate\Cache\DatabaseStore')->setMethods(['put'])->setConstructorArgs($this->getMocks())->getMock();
Expand Down Expand Up @@ -186,8 +208,18 @@ protected function getStore()
return new DatabaseStore(m::mock('Illuminate\Database\Connection'), 'table', 'prefix');
}

protected function getPostgresStore()
{
return new DatabaseStore(m::mock('Illuminate\Database\PostgresConnection'), 'table', 'prefix');
}

protected function getMocks()
{
return [m::mock('Illuminate\Database\Connection'), 'table', 'prefix'];
}

protected function getPostgresMocks()
{
return [m::mock('Illuminate\Database\PostgresConnection'), 'table', 'prefix'];
}
}

0 comments on commit b2fa4e7

Please sign in to comment.