diff --git a/examples/Commands/fcall_ro.php b/examples/Commands/fcall_ro.php new file mode 100644 index 00000000..74046597 --- /dev/null +++ b/examples/Commands/fcall_ro.php @@ -0,0 +1,43 @@ +set('foo', 'bar'); + +echo "Set key 'foo' with value 'bar'\n"; + +// 2. Load redis function with 'no-writes' flag +$client->function->load( + "#!lua name=mylib + redis.register_function{ + function_name='myfunc', + callback=function(keys, args) return redis.call('GET', keys[1]) end, + flags={'no-writes'} + }" +); + +echo 'Loaded custom function that perform GET command against provided key.' . "\n"; + +// 3. Call function above with given key +$response = $client->fcall_ro('myfunc', ['foo']); + +echo "Function returned value against provided key 'foo' is '{$response}'"; + +// 4. Delete test library +$client->function->delete('mylib'); diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index c241e83e..c45700f4 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -57,6 +57,7 @@ use Predis\Command\Redis\Container\FunctionContainer; * @method $this decrby($key, $decrement) * @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1) * @method $this fcall(string $function, array $keys, ...$args) + * @method $this fcall_ro(string $function, array $keys, ...$args) * @method $this get($key) * @method $this getbit($key, $offset) * @method $this getex(string $key, $modifier = '', $value = false) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index f04f1a9f..a084c990 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -66,6 +66,7 @@ use Predis\Response\Status; * @method int decrby(string $key, int $decrement) * @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1) * @method mixed fcall(string $function, array $keys, ...$args) + * @method mixed fcall_ro(string $function, array $keys, ...$args) * @method string|null get(string $key) * @method int getbit(string $key, $offset) * @method int|null getex(string $key, $modifier = '', $value = false) diff --git a/src/Command/Redis/FCALL_RO.php b/src/Command/Redis/FCALL_RO.php new file mode 100644 index 00000000..8680e705 --- /dev/null +++ b/src/Command/Redis/FCALL_RO.php @@ -0,0 +1,41 @@ + 2) { + for ($i = 2, $iMax = count($arguments); $i < $iMax; $i++) { + $processedArguments[] = $arguments[$i]; + } + } + + parent::setArguments($processedArguments); + } +} diff --git a/tests/Predis/Command/Redis/FCALL_RO_Test.php b/tests/Predis/Command/Redis/FCALL_RO_Test.php new file mode 100644 index 00000000..a90695f3 --- /dev/null +++ b/tests/Predis/Command/Redis/FCALL_RO_Test.php @@ -0,0 +1,141 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testInvokeGivenReadOnlyFunction(): void + { + $redis = $this->getClient(); + + $this->assertEquals('OK', $redis->set('key', 'value')); + + $this->assertSame( + self::LIB_NAME, + $redis->function->load( + "#!lua name=mylib\n redis.register_function{function_name='myfunc',callback=function(keys, args) return redis.call('GET', keys[1]) end,flags={'no-writes'}}" + ) + ); + + $actualResponse = $redis->fcall_ro('myfunc', ['key']); + $this->assertSame('value', $actualResponse); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testThrowsExceptionOnWriteContextFunction(): void + { + $redis = $this->getClient(); + + $this->assertEquals('OK', $redis->set('key', 'value')); + + $this->assertSame( + self::LIB_NAME, + $redis->function->load( + "#!lua name=mylib \n redis.register_function('myfunc',function(keys, args) return redis.call('GET', keys[1]) end)" + ) + ); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage('ERR Can not execute a script with write flag using *_ro command.'); + + $redis->fcall_ro('myfunc', ['key']); + } + + protected function tearDown(): void + { + $annotations = TestUtil::parseTestMethodAnnotations( + get_class($this), + $this->getName(false) + ); + + if ( + isset($annotations['method']['group']) && + in_array('connected', $annotations['method']['group'], true) + ) { + $redis = $this->getClient(); + $redis->function->delete(self::LIB_NAME); + } + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['function', []], + ['function', 0], + ], + 'with provided keys' => [ + ['function', ['key1', 'key2']], + ['function', 2, 'key1', 'key2'], + ], + 'with provided keys and arguments' => [ + ['function', ['key1', 'key2'], 'arg1', 'arg2'], + ['function', 2, 'key1', 'key2', 'arg1', 'arg2'], + ], + ]; + } +}