Extended TimeSeries support by implementing TS.CREATE command (#1197)

* Added support for new arguments for BITPOS, BITCOUNT commands (#1045)

* Added support for new arguments for EXPIRE, EXPIREAT commands (#1046)

* Extended core support by implementing SORT_RO command (#1044)

* Added support for SORT_RO command

* Codestyle fixes

* Added command description

---------

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>

* fix deprecated call

* Added support for container commands (#1049)

* Added support for container commands FUNCTION LOAD, FUNCTION DELETE and FCALL

* Changed ContainerInterface and AbstractContainer

* Re-implement logic of abstract methods

---------

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>

* Added stream commands to KeyPrefixProcessor (#1051)

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>

* Fix return type of ReplicationInterface::getSlaves (#1111)

* Codestyle fixes

* Changed return annotation

* Added support for Time Series commands, TSCREATE command

* Fixed param annotation

---------

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
Co-authored-by: Till Krüss <till@kruss.io>
Co-authored-by: Stephan <glaubinix@users.noreply.github.com>
This commit is contained in:
Vladyslav Vildanov
2023-03-23 09:41:35 +02:00
committed by GitHub
parent 4fefb9bb76
commit c88d8ebce3
11 changed files with 415 additions and 3 deletions
@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.CREATE command usage:
// Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retention(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$response = $client->tscreate('temperature:2:32', $arguments);
echo "Time series creation status: {$response}";
+1
View File
@@ -26,6 +26,7 @@ class ClientConfiguration
['name' => 'TDigest', 'commandPrefix' => 'TDIGEST'],
['name' => 'TopK', 'commandPrefix' => 'TOPK'],
['name' => 'Search', 'commandPrefix' => 'FT'],
['name' => 'TimeSeries', 'commandPrefix' => 'TS'],
],
];
+3
View File
@@ -18,6 +18,7 @@ use Predis\Command\Argument\Search\AggregateArguments;
use Predis\Command\Argument\Search\AlterArguments;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\DropArguments;
use Predis\Command\Argument\Search\ExplainArguments;
use Predis\Command\Argument\Search\ProfileArguments;
use Predis\Command\Argument\Search\SchemaFields\FieldInterface;
use Predis\Command\Argument\Search\SearchArguments;
@@ -26,6 +27,7 @@ use Predis\Command\Argument\Search\SugGetArguments;
use Predis\Command\Argument\Search\SynUpdateArguments;
use Predis\Command\Argument\Server\LimitOffsetCount;
use Predis\Command\Argument\Server\To;
use Predis\Command\Argument\TimeSeries\CreateArguments as TSCreateArguments;
use Predis\Command\CommandInterface;
use Predis\Command\Container\FUNCTIONS;
use Predis\Command\Container\Json\JSONDEBUG;
@@ -231,6 +233,7 @@ use Predis\Command\Container\Search\FTCONFIG;
* @method $this topklist(string $key, bool $withCount = false)
* @method $this topkquery(string $key, ...$items)
* @method $this topkreserve(string $key, int $topK, int $width = 8, int $depth = 7, float $decay = 0.9)
* @method $this tscreate(string $key, ?TSCreateArguments $arguments = null)
* @method $this zadd($key, array $membersAndScoresDictionary)
* @method $this zcard($key)
* @method $this zcount($key, $min, $max)
+2
View File
@@ -27,6 +27,7 @@ use Predis\Command\Argument\Search\SugGetArguments;
use Predis\Command\Argument\Search\SynUpdateArguments;
use Predis\Command\Argument\Server\LimitOffsetCount;
use Predis\Command\Argument\Server\To;
use Predis\Command\Argument\TimeSeries\CreateArguments as TSCreateArguments;
use Predis\Command\CommandInterface;
use Predis\Command\Container\FUNCTIONS;
use Predis\Command\Container\Json\JSONDEBUG;
@@ -242,6 +243,7 @@ use Predis\Response\Status;
* @method array topklist(string $key, bool $withCount = false)
* @method array topkquery(string $key, ...$items)
* @method Status topkreserve(string $key, int $topK, int $width = 8, int $depth = 7, float $decay = 0.9)
* @method Status tscreate(string $key, ?TSCreateArguments $arguments = null)
* @method string xadd(string $key, array $dictionary, string $id = '*', array $options = null)
* @method int xdel(string $key, string ...$id)
* @method int xlen(string $key)
@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Argument\TimeSeries;
use Predis\Command\Argument\ArrayableArgument;
class CommonArguments implements ArrayableArgument
{
public const POLICY_BLOCK = 'BLOCK';
public const POLICY_FIRST = 'FIRST';
public const POLICY_LAST = 'LAST';
public const POLICY_MIN = 'MIN';
public const POLICY_MAX = 'MAX';
public const POLICY_SUM = 'SUM';
/**
* @var array
*/
protected $arguments = [];
/**
* Is maximum age for samples compared to the highest reported timestamp, in milliseconds.
*
* @param int $retentionPeriod
* @return $this
*/
public function retention(int $retentionPeriod): self
{
array_push($this->arguments, 'RETENTION', $retentionPeriod);
return $this;
}
/**
* Is initial allocation size, in bytes, for the data part of each new chunk.
*
* @param int $size
* @return $this
*/
public function chunkSize(int $size): self
{
array_push($this->arguments, 'CHUNK_SIZE', $size);
return $this;
}
/**
* Is policy for handling insertion of multiple samples with identical timestamps.
*
* @param string $policy
* @return $this
*/
public function duplicatePolicy(string $policy = self::POLICY_BLOCK): self
{
array_push($this->arguments, 'DUPLICATE_POLICY', $policy);
return $this;
}
/**
* Is set of label-value pairs that represent metadata labels of the key and serve as a secondary index.
*
* @param mixed ...$labelValuePair
* @return $this
*/
public function labels(...$labelValuePair): self
{
array_push($this->arguments, 'LABELS', ...$labelValuePair);
return $this;
}
/**
* {@inheritDoc}
*/
public function toArray(): array
{
return $this->arguments;
}
}
@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Argument\TimeSeries;
class CreateArguments extends CommonArguments
{
public const ENCODING_UNCOMPRESSED = 'UNCOMPRESSED';
public const ENCODING_COMPRESSED = 'COMPRESSED';
/**
* Specifies the series samples encoding format.
*
* @param string $encoding
* @return $this
*/
public function encoding(string $encoding = self::ENCODING_COMPRESSED): self
{
array_push($this->arguments, 'ENCODING', $encoding);
return $this;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis\TimeSeries;
use Predis\Command\Command as RedisCommand;
/**
* @see https://redis.io/commands/ts.create/
*
* Create a new time series.
*/
class TSCREATE extends RedisCommand
{
public function getId()
{
return 'TS.CREATE';
}
public function setArguments(array $arguments)
{
[$key] = $arguments;
$commandArguments = (!empty($arguments[1])) ? $arguments[1]->toArray() : [];
parent::setArguments(array_merge(
[$key],
$commandArguments
));
}
}
+5 -3
View File
@@ -32,6 +32,7 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
'json' => ['annotation' => 'requiresRedisJsonVersion', 'name' => 'ReJSON'],
'bloomFilter' => ['annotation' => 'requiresRedisBfVersion', 'name' => 'bf'],
'search' => ['annotation' => 'requiresRediSearchVersion', 'name' => 'search'],
'timeSeries' => ['annotation' => 'requiresRedisTimeSeriesVersion', 'name' => 'timeseries'],
];
/**
@@ -47,9 +48,10 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
protected function setUp(): void
{
$this->checkRequiredRedisServerVersion();
$this->checkRequiredRedisModuleVersion('json');
$this->checkRequiredRedisModuleVersion('bloomFilter');
$this->checkRequiredRedisModuleVersion('search');
foreach ($this->modulesMapping as $module => $config) {
$this->checkRequiredRedisModuleVersion($module);
}
}
/**
@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Argument\TimeSeries;
use PHPUnit\Framework\TestCase;
class CommonArgumentsTest extends TestCase
{
/**
* @var CommonArguments
*/
private $arguments;
protected function setUp(): void
{
$this->arguments = new CommonArguments();
}
/**
* @return void
*/
public function testCreatesArgumentsWithRetentionModifier(): void
{
$this->arguments->retention(10);
$this->assertSame(['RETENTION', 10], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithChunkSizeModifier(): void
{
$this->arguments->chunkSize(100);
$this->assertSame(['CHUNK_SIZE', 100], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithDuplicatePolicyModifier(): void
{
$this->arguments->duplicatePolicy(CommonArguments::POLICY_FIRST);
$this->assertSame(['DUPLICATE_POLICY', CommonArguments::POLICY_FIRST], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithLabelsModifier(): void
{
$this->arguments->labels('label1', 1, 'label2', 2);
$this->assertSame(['LABELS', 'label1', 1, 'label2', 2], $this->arguments->toArray());
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Argument\TimeSeries;
use PHPUnit\Framework\TestCase;
class CreateArgumentsTest extends TestCase
{
/**
* @var CreateArguments
*/
private $arguments;
protected function setUp(): void
{
$this->arguments = new CreateArguments();
}
/**
* @return void
*/
public function testCreatesArgumentsWithEncodingModifier(): void
{
$this->arguments->encoding(CreateArguments::ENCODING_UNCOMPRESSED);
$this->assertSame(['ENCODING', CreateArguments::ENCODING_UNCOMPRESSED], $this->arguments->toArray());
}
}
@@ -0,0 +1,106 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis\TimeSeries;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Redis\PredisCommandTestCase;
class TSCREATE_Test extends PredisCommandTestCase
{
/**
* {@inheritDoc}
*/
protected function getExpectedCommand(): string
{
return TSCREATE::class;
}
/**
* {@inheritDoc}
*/
protected function getExpectedId(): string
{
return 'TSCREATE';
}
/**
* @group disconnected
* @dataProvider argumentsProvider
*/
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
{
$command = $this->getCommand();
$command->setArguments($actualArguments);
$this->assertSameValues($expectedArguments, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse(): void
{
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group connected
* @return void
* @requiresRedisTimeSeriesVersion >= 1.0.0
*/
public function testCreatesTimeSeriesWithGivenArguments(): void
{
$redis = $this->getClient();
$arguments = (new CreateArguments())
->retention(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$this->assertEquals(
'OK',
$redis->tscreate('temperature:2:32', $arguments)
);
}
public function argumentsProvider(): array
{
return [
'with default arguments' => [
['key'],
['key'],
],
'with RETENTION modifier' => [
['key', (new CreateArguments())->retention(100)],
['key', 'RETENTION', 100],
],
'with ENCODING modifier' => [
['key', (new CreateArguments())->encoding(CreateArguments::ENCODING_UNCOMPRESSED)],
['key', 'ENCODING', CreateArguments::ENCODING_UNCOMPRESSED],
],
'with CHUNK_SIZE modifier' => [
['key', (new CreateArguments())->chunkSize(100)],
['key', 'CHUNK_SIZE', 100],
],
'with DUPLICATE_POLICY modifier' => [
['key', (new CreateArguments())->duplicatePolicy(CommonArguments::POLICY_FIRST)],
['key', 'DUPLICATE_POLICY', CommonArguments::POLICY_FIRST],
],
'with all modifiers' => [
['key', (new CreateArguments())->retention(100)->encoding(CreateArguments::ENCODING_UNCOMPRESSED)->chunkSize(100)->duplicatePolicy(CommonArguments::POLICY_FIRST)],
['key', 'RETENTION', 100, 'ENCODING', CreateArguments::ENCODING_UNCOMPRESSED, 'CHUNK_SIZE', 100, 'DUPLICATE_POLICY', CommonArguments::POLICY_FIRST],
],
];
}
}