From 971e47672d1fa8c09677faefc342d1e55ec392a1 Mon Sep 17 00:00:00 2001
From: Vladyslav Vildanov <117659936+vladvildanov@users.noreply.github.com>
Date: Tue, 11 Apr 2023 18:09:14 +0300
Subject: [PATCH] Added Redis stack support (JSON, Search, TimeSeries, Bloom)
(#1253)
---
.codespellrc | 2 +-
.github/workflows/stack.yml | 62 ++++
.github/workflows/tests.yml | 14 +-
CHANGELOG.md | 6 +
examples/Commands/Json/json_debug_memory.php | 28 ++
examples/Commands/Search/ft_aggregate.php | 52 +++
examples/Commands/Search/ft_alter.php | 41 +++
examples/Commands/Search/ft_config_help.php | 25 ++
.../Commands/Search/ft_config_set_get.php | 29 ++
examples/Commands/Search/ft_cursor_del.php | 54 ++++
examples/Commands/Search/ft_cursor_read.php | 62 ++++
examples/Commands/Search/ft_explain.php | 32 ++
examples/Commands/Search/ft_profile.php | 38 +++
examples/Commands/Search/ft_spellcheck.php | 40 +++
.../Search/ft_sug_add_get_del_len.php | 39 +++
examples/Commands/Search/ft_syndump.php | 35 ++
examples/Commands/Search/ft_synupdate.php | 32 ++
examples/Commands/Search/ft_tagvals.php | 37 +++
examples/Commands/TimeSeries/ts_add.php | 38 +++
examples/Commands/TimeSeries/ts_alter.php | 41 +++
examples/Commands/TimeSeries/ts_create.php | 31 ++
.../Commands/TimeSeries/ts_createrule.php | 33 ++
examples/Commands/TimeSeries/ts_decrby.php | 45 +++
examples/Commands/TimeSeries/ts_del.php | 47 +++
.../Commands/TimeSeries/ts_deleterule.php | 37 +++
examples/Commands/TimeSeries/ts_get.php | 38 +++
examples/Commands/TimeSeries/ts_incrby.php | 45 +++
examples/Commands/TimeSeries/ts_info.php | 32 ++
examples/Commands/TimeSeries/ts_madd.php | 36 +++
examples/Commands/TimeSeries/ts_mget.php | 41 +++
examples/Commands/TimeSeries/ts_mrange.php | 48 +++
examples/Commands/TimeSeries/ts_mrevrange.php | 48 +++
.../Commands/TimeSeries/ts_queryindex.php | 36 +++
examples/Commands/TimeSeries/ts_range.php | 40 +++
examples/Commands/TimeSeries/ts_revrange.php | 40 +++
phpunit.xml.dist | 2 +-
src/ClientConfiguration.php | 42 +++
src/ClientContextInterface.php | 135 +++++++-
src/ClientInterface.php | 133 ++++++++
.../Argument/Search/AggregateArguments.php | 161 +++++++++
.../Argument/Search/AlterArguments.php | 17 +
.../Argument/Search/CommonArguments.php | 182 +++++++++++
.../Argument/Search/CreateArguments.php | 191 +++++++++++
.../Argument/Search/CursorArguments.php | 44 +++
src/Command/Argument/Search/DropArguments.php | 43 +++
.../Argument/Search/ExplainArguments.php | 17 +
.../Argument/Search/ProfileArguments.php | 81 +++++
.../Search/SchemaFields/AbstractField.php | 69 ++++
.../Search/SchemaFields/FieldInterface.php | 22 ++
.../Argument/Search/SchemaFields/GeoField.php | 31 ++
.../Search/SchemaFields/NumericField.php | 31 ++
.../Argument/Search/SchemaFields/TagField.php | 44 +++
.../Search/SchemaFields/TextField.php | 57 ++++
.../Search/SchemaFields/VectorField.php | 47 +++
.../Argument/Search/SearchArguments.php | 306 ++++++++++++++++++
.../Argument/Search/SpellcheckArguments.php | 59 ++++
.../Argument/Search/SugAddArguments.php | 28 ++
.../Argument/Search/SugGetArguments.php | 41 +++
.../Argument/Search/SynUpdateArguments.php | 17 +
.../Argument/TimeSeries/AddArguments.php | 30 ++
.../Argument/TimeSeries/AlterArguments.php | 17 +
.../Argument/TimeSeries/CommonArguments.php | 143 ++++++++
.../Argument/TimeSeries/CreateArguments.php | 17 +
.../Argument/TimeSeries/DecrByArguments.php | 17 +
.../Argument/TimeSeries/GetArguments.php | 17 +
.../Argument/TimeSeries/IncrByArguments.php | 41 +++
.../Argument/TimeSeries/InfoArguments.php | 43 +++
.../Argument/TimeSeries/MGetArguments.php | 17 +
.../Argument/TimeSeries/MRangeArguments.php | 44 +++
.../Argument/TimeSeries/RangeArguments.php | 85 +++++
src/Command/Redis/BloomFilter/BFADD.php | 29 ++
src/Command/Redis/BloomFilter/BFEXISTS.php | 28 ++
src/Command/Redis/BloomFilter/BFINFO.php | 79 +++++
src/Command/Redis/BloomFilter/BFINSERT.php | 72 +++++
src/Command/Redis/BloomFilter/BFLOADCHUNK.php | 28 ++
src/Command/Redis/BloomFilter/BFMADD.php | 29 ++
src/Command/Redis/BloomFilter/BFMEXISTS.php | 28 ++
src/Command/Redis/BloomFilter/BFRESERVE.php | 49 +++
src/Command/Redis/BloomFilter/BFSCANDUMP.php | 29 ++
.../Redis/Container/ContainerFactory.php | 29 +-
.../Redis/Container/FunctionContainer.php | 2 +-
.../Redis/Container/Json/JSONDEBUG.php | 27 ++
.../Redis/Container/Search/FTCONFIG.php | 29 ++
.../Redis/Container/Search/FTCURSOR.php | 29 ++
.../Redis/CountMinSketch/CMSINCRBY.php | 29 ++
src/Command/Redis/CountMinSketch/CMSINFO.php | 45 +++
.../Redis/CountMinSketch/CMSINITBYDIM.php | 28 ++
.../Redis/CountMinSketch/CMSINITBYPROB.php | 28 ++
src/Command/Redis/CountMinSketch/CMSMERGE.php | 42 +++
src/Command/Redis/CountMinSketch/CMSQUERY.php | 28 ++
src/Command/Redis/CuckooFilter/CFADD.php | 28 ++
src/Command/Redis/CuckooFilter/CFADDNX.php | 28 ++
src/Command/Redis/CuckooFilter/CFCOUNT.php | 29 ++
src/Command/Redis/CuckooFilter/CFDEL.php | 30 ++
src/Command/Redis/CuckooFilter/CFEXISTS.php | 28 ++
src/Command/Redis/CuckooFilter/CFINFO.php | 45 +++
src/Command/Redis/CuckooFilter/CFINSERT.php | 52 +++
src/Command/Redis/CuckooFilter/CFINSERTNX.php | 27 ++
.../Redis/CuckooFilter/CFLOADCHUNK.php | 29 ++
src/Command/Redis/CuckooFilter/CFMEXISTS.php | 28 ++
src/Command/Redis/CuckooFilter/CFRESERVE.php | 52 +++
src/Command/Redis/CuckooFilter/CFSCANDUMP.php | 29 ++
src/Command/Redis/INFO.php | 30 ++
src/Command/Redis/Json/JSONARRAPPEND.php | 28 ++
src/Command/Redis/Json/JSONARRINDEX.php | 28 ++
src/Command/Redis/Json/JSONARRINSERT.php | 28 ++
src/Command/Redis/Json/JSONARRLEN.php | 28 ++
src/Command/Redis/Json/JSONARRPOP.php | 28 ++
src/Command/Redis/Json/JSONARRTRIM.php | 28 ++
src/Command/Redis/Json/JSONCLEAR.php | 28 ++
src/Command/Redis/Json/JSONDEBUG.php | 28 ++
src/Command/Redis/Json/JSONDEL.php | 28 ++
src/Command/Redis/Json/JSONFORGET.php | 28 ++
src/Command/Redis/Json/JSONGET.php | 57 ++++
src/Command/Redis/Json/JSONMGET.php | 36 +++
src/Command/Redis/Json/JSONNUMINCRBY.php | 28 ++
src/Command/Redis/Json/JSONOBJKEYS.php | 28 ++
src/Command/Redis/Json/JSONOBJLEN.php | 28 ++
src/Command/Redis/Json/JSONRESP.php | 28 ++
src/Command/Redis/Json/JSONSET.php | 41 +++
src/Command/Redis/Json/JSONSTRAPPEND.php | 28 ++
src/Command/Redis/Json/JSONSTRLEN.php | 28 ++
src/Command/Redis/Json/JSONTOGGLE.php | 28 ++
src/Command/Redis/Json/JSONTYPE.php | 28 ++
src/Command/Redis/Search/FTAGGREGATE.php | 40 +++
src/Command/Redis/Search/FTALIASADD.php | 28 ++
src/Command/Redis/Search/FTALIASDEL.php | 28 ++
src/Command/Redis/Search/FTALIASUPDATE.php | 29 ++
src/Command/Redis/Search/FTALTER.php | 42 +++
src/Command/Redis/Search/FTCONFIG.php | 30 ++
src/Command/Redis/Search/FTCREATE.php | 47 +++
src/Command/Redis/Search/FTCURSOR.php | 34 ++
src/Command/Redis/Search/FTDICTADD.php | 28 ++
src/Command/Redis/Search/FTDICTDEL.php | 28 ++
src/Command/Redis/Search/FTDICTDUMP.php | 28 ++
src/Command/Redis/Search/FTDROPINDEX.php | 38 +++
src/Command/Redis/Search/FTEXPLAIN.php | 43 +++
src/Command/Redis/Search/FTINFO.php | 28 ++
src/Command/Redis/Search/FTPROFILE.php | 38 +++
src/Command/Redis/Search/FTSEARCH.php | 39 +++
src/Command/Redis/Search/FTSPELLCHECK.php | 38 +++
src/Command/Redis/Search/FTSUGADD.php | 39 +++
src/Command/Redis/Search/FTSUGDEL.php | 28 ++
src/Command/Redis/Search/FTSUGGET.php | 39 +++
src/Command/Redis/Search/FTSUGLEN.php | 28 ++
src/Command/Redis/Search/FTSYNDUMP.php | 28 ++
src/Command/Redis/Search/FTSYNUPDATE.php | 46 +++
src/Command/Redis/Search/FTTAGVALS.php | 28 ++
src/Command/Redis/TDigest/TDIGESTADD.php | 28 ++
src/Command/Redis/TDigest/TDIGESTBYRANK.php | 28 ++
.../Redis/TDigest/TDIGESTBYREVRANK.php | 28 ++
src/Command/Redis/TDigest/TDIGESTCDF.php | 30 ++
src/Command/Redis/TDigest/TDIGESTCREATE.php | 40 +++
src/Command/Redis/TDigest/TDIGESTINFO.php | 41 +++
src/Command/Redis/TDigest/TDIGESTMAX.php | 28 ++
src/Command/Redis/TDigest/TDIGESTMERGE.php | 43 +++
src/Command/Redis/TDigest/TDIGESTMIN.php | 28 ++
src/Command/Redis/TDigest/TDIGESTQUANTILE.php | 28 ++
src/Command/Redis/TDigest/TDIGESTRANK.php | 30 ++
src/Command/Redis/TDigest/TDIGESTRESET.php | 28 ++
src/Command/Redis/TDigest/TDIGESTREVRANK.php | 30 ++
.../Redis/TDigest/TDIGESTTRIMMED_MEAN.php | 29 ++
src/Command/Redis/TimeSeries/TSADD.php | 39 +++
src/Command/Redis/TimeSeries/TSALTER.php | 39 +++
src/Command/Redis/TimeSeries/TSCREATE.php | 39 +++
src/Command/Redis/TimeSeries/TSCREATERULE.php | 40 +++
src/Command/Redis/TimeSeries/TSDECRBY.php | 41 +++
src/Command/Redis/TimeSeries/TSDEL.php | 28 ++
src/Command/Redis/TimeSeries/TSDELETERULE.php | 28 ++
src/Command/Redis/TimeSeries/TSGET.php | 39 +++
src/Command/Redis/TimeSeries/TSINCRBY.php | 41 +++
src/Command/Redis/TimeSeries/TSINFO.php | 39 +++
src/Command/Redis/TimeSeries/TSMADD.php | 28 ++
src/Command/Redis/TimeSeries/TSMGET.php | 37 +++
src/Command/Redis/TimeSeries/TSMRANGE.php | 39 +++
src/Command/Redis/TimeSeries/TSMREVRANGE.php | 26 ++
src/Command/Redis/TimeSeries/TSQUERYINDEX.php | 28 ++
src/Command/Redis/TimeSeries/TSRANGE.php | 39 +++
src/Command/Redis/TimeSeries/TSREVRANGE.php | 26 ++
src/Command/Redis/TopK/TOPKADD.php | 31 ++
src/Command/Redis/TopK/TOPKINCRBY.php | 30 ++
src/Command/Redis/TopK/TOPKINFO.php | 41 +++
src/Command/Redis/TopK/TOPKLIST.php | 68 ++++
src/Command/Redis/TopK/TOPKQUERY.php | 29 ++
src/Command/Redis/TopK/TOPKRESERVE.php | 47 +++
src/Command/RedisFactory.php | 51 ++-
src/Command/Traits/BitByte.php | 6 +
.../Traits/BloomFilters/BucketSize.php | 57 ++++
src/Command/Traits/BloomFilters/Capacity.php | 57 ++++
src/Command/Traits/BloomFilters/Error.php | 57 ++++
src/Command/Traits/BloomFilters/Expansion.php | 53 +++
src/Command/Traits/BloomFilters/Items.php | 45 +++
.../Traits/BloomFilters/MaxIterations.php | 57 ++++
src/Command/Traits/BloomFilters/NoCreate.php | 49 +++
src/Command/Traits/Expire/ExpireOptions.php | 6 +
src/Command/Traits/Json/Indent.php | 54 ++++
src/Command/Traits/Json/Newline.php | 54 ++++
src/Command/Traits/Json/NxXxArgument.php | 64 ++++
src/Command/Traits/Json/Space.php | 54 ++++
src/Connection/StreamConnection.php | 5 +-
tests/.editorconfig | 2 +
tests/PHPUnit/PredisCommandTestCase.php | 3 +-
tests/PHPUnit/PredisTestCase.php | 136 +++++++-
.../Search/AggregateArgumentsTest.php | 189 +++++++++++
.../Argument/Search/CommonArgumentsTest.php | 138 ++++++++
.../Argument/Search/CreateArgumentsTest.php | 175 ++++++++++
.../Argument/Search/CursorArgumentsTest.php | 38 +++
.../Argument/Search/DropArgumentsTest.php | 38 +++
.../Argument/Search/ProfileArgumentsTest.php | 68 ++++
.../Search/SchemaFields/GeoFieldTest.php | 57 ++++
.../Search/SchemaFields/NumericFieldTest.php | 57 ++++
.../Search/SchemaFields/TagFieldTest.php | 57 ++++
.../Search/SchemaFields/TextFieldTest.php | 57 ++++
.../Search/SchemaFields/VectorFieldTest.php | 28 ++
.../Argument/Search/SearchArgumentsTest.php | 297 +++++++++++++++++
.../Search/SpellcheckArgumentsTest.php | 77 +++++
.../Argument/Search/SugAddArgumentsTest.php | 38 +++
.../Argument/Search/SugGetArgumentsTest.php | 48 +++
.../Argument/TimeSeries/AddArgumentsTest.php | 38 +++
.../TimeSeries/CommonArgumentsTest.php | 108 +++++++
.../TimeSeries/CreateArgumentsTest.php | 38 +++
.../TimeSeries/IncrByArgumentsTest.php | 48 +++
.../Argument/TimeSeries/InfoArgumentsTest.php | 38 +++
.../TimeSeries/MRangeArgumentsTest.php | 48 +++
.../TimeSeries/RangeArgumentsTest.php | 97 ++++++
.../Command/Redis/BloomFilter/BFADD_Test.php | 90 ++++++
.../Redis/BloomFilter/BFEXISTS_Test.php | 75 +++++
.../Command/Redis/BloomFilter/BFINFO_Test.php | 226 +++++++++++++
.../Redis/BloomFilter/BFINSERT_Test.php | 272 ++++++++++++++++
.../Redis/BloomFilter/BFLOADCHUNK_Test.php | 112 +++++++
.../Command/Redis/BloomFilter/BFMADD_Test.php | 90 ++++++
.../Redis/BloomFilter/BFMEXISTS_Test.php | 73 +++++
.../Redis/BloomFilter/BFRESERVE_Test.php | 159 +++++++++
.../Redis/BloomFilter/BFSCANDUMP_Test.php | 93 ++++++
.../Redis/Container/ContainerFactoryTest.php | 54 +++-
.../Redis/CountMinSketch/CMSINCRBY_Test.php | 121 +++++++
.../Redis/CountMinSketch/CMSINFO_Test.php | 95 ++++++
.../CountMinSketch/CMSINITBYDIM_Test.php | 93 ++++++
.../CountMinSketch/CMSINITBYPROB_Test.php | 93 ++++++
.../Redis/CountMinSketch/CMSMERGE_Test.php | 180 +++++++++++
.../Redis/CountMinSketch/CMSQUERY_Test.php | 109 +++++++
.../Redis/CuckooFilter/CFADDNX_Test.php | 94 ++++++
.../Command/Redis/CuckooFilter/CFADD_Test.php | 90 ++++++
.../Redis/CuckooFilter/CFCOUNT_Test.php | 84 +++++
.../Command/Redis/CuckooFilter/CFDEL_Test.php | 102 ++++++
.../Redis/CuckooFilter/CFEXISTS_Test.php | 75 +++++
.../Redis/CuckooFilter/CFINFO_Test.php | 141 ++++++++
.../Redis/CuckooFilter/CFINSERTNX_Test.php | 168 ++++++++++
.../Redis/CuckooFilter/CFINSERT_Test.php | 186 +++++++++++
.../Redis/CuckooFilter/CFLOADCHUNK_Test.php | 112 +++++++
.../Redis/CuckooFilter/CFMEXISTS_Test.php | 77 +++++
.../Redis/CuckooFilter/CFRESERVE_Test.php | 164 ++++++++++
.../Redis/CuckooFilter/CFSCANDUMP_Test.php | 93 ++++++
tests/Predis/Command/Redis/EXPIRE_Test.php | 10 +-
.../Command/Redis/Json/JSONARRAPPEND_Test.php | 136 ++++++++
.../Command/Redis/Json/JSONARRINDEX_Test.php | 148 +++++++++
.../Command/Redis/Json/JSONARRINSERT_Test.php | 143 ++++++++
.../Command/Redis/Json/JSONARRLEN_Test.php | 113 +++++++
.../Command/Redis/Json/JSONARRPOP_Test.php | 127 ++++++++
.../Command/Redis/Json/JSONARRTRIM_Test.php | 133 ++++++++
.../Command/Redis/Json/JSONCLEAR_Test.php | 128 ++++++++
.../Command/Redis/Json/JSONDEBUG_Test.php | 123 +++++++
.../Command/Redis/Json/JSONDEL_Test.php | 121 +++++++
.../Command/Redis/Json/JSONFORGET_Test.php | 121 +++++++
.../Command/Redis/Json/JSONGET_Test.php | 213 ++++++++++++
.../Command/Redis/Json/JSONMGET_Test.php | 120 +++++++
.../Command/Redis/Json/JSONNUMINCRBY_Test.php | 119 +++++++
.../Command/Redis/Json/JSONOBJKEYS_Test.php | 113 +++++++
.../Command/Redis/Json/JSONOBJLEN_Test.php | 113 +++++++
.../Command/Redis/Json/JSONRESP_Test.php | 126 ++++++++
.../Command/Redis/Json/JSONSET_Test.php | 190 +++++++++++
.../Command/Redis/Json/JSONSTRAPPEND_Test.php | 127 ++++++++
.../Command/Redis/Json/JSONSTRLEN_Test.php | 113 +++++++
.../Command/Redis/Json/JSONTOGGLE_Test.php | 128 ++++++++
.../Command/Redis/Json/JSONTYPE_Test.php | 125 +++++++
tests/Predis/Command/Redis/MOVE_Test.php | 2 +-
tests/Predis/Command/Redis/SELECT_Test.php | 2 +-
.../Command/Redis/Search/FTAGGREGATE_Test.php | 203 ++++++++++++
.../Command/Redis/Search/FTALIASADD_Test.php | 127 ++++++++
.../Command/Redis/Search/FTALIASDEL_Test.php | 93 ++++++
.../Redis/Search/FTALIASUPDATE_Test.php | 114 +++++++
.../Command/Redis/Search/FTALTER_Test.php | 108 +++++++
.../Command/Redis/Search/FTCONFIG_Test.php | 151 +++++++++
.../Command/Redis/Search/FTCREATE_Test.php | 169 ++++++++++
.../Command/Redis/Search/FTCURSOR_Test.php | 218 +++++++++++++
.../Command/Redis/Search/FTDICTADD_Test.php | 74 +++++
.../Command/Redis/Search/FTDICTDEL_Test.php | 102 ++++++
.../Command/Redis/Search/FTDICTDUMP_Test.php | 90 ++++++
.../Command/Redis/Search/FTDROPINDEX_Test.php | 105 ++++++
.../Command/Redis/Search/FTEXPLAIN_Test.php | 135 ++++++++
.../Command/Redis/Search/FTINFO_Test.php | 100 ++++++
.../Command/Redis/Search/FTPROFILE_Test.php | 126 ++++++++
.../Command/Redis/Search/FTSEARCH_Test.php | 221 +++++++++++++
.../Redis/Search/FTSPELLCHECK_Test.php | 156 +++++++++
.../Command/Redis/Search/FTSUGADD_Test.php | 93 ++++++
.../Command/Redis/Search/FTSUGDEL_Test.php | 85 +++++
.../Command/Redis/Search/FTSUGGET_Test.php | 169 ++++++++++
.../Command/Redis/Search/FTSUGLEN_Test.php | 71 ++++
.../Command/Redis/Search/FTSYNDUMP_Test.php | 100 ++++++
.../Command/Redis/Search/FTSYNUPDATE_Test.php | 140 ++++++++
.../Command/Redis/Search/FTTAGVALS_Test.php | 110 +++++++
.../Command/Redis/TDigest/TDIGESTADD_Test.php | 94 ++++++
.../Redis/TDigest/TDIGESTBYRANK_Test.php | 97 ++++++
.../Redis/TDigest/TDIGESTBYREVRANK_Test.php | 97 ++++++
.../Command/Redis/TDigest/TDIGESTCDF_Test.php | 97 ++++++
.../Redis/TDigest/TDIGESTCREATE_Test.php | 134 ++++++++
.../Redis/TDigest/TDIGESTINFO_Test.php | 117 +++++++
.../Command/Redis/TDigest/TDIGESTMAX_Test.php | 96 ++++++
.../Redis/TDigest/TDIGESTMERGE_Test.php | 254 +++++++++++++++
.../Command/Redis/TDigest/TDIGESTMIN_Test.php | 96 ++++++
.../Redis/TDigest/TDIGESTQUANTILE_Test.php | 97 ++++++
.../Redis/TDigest/TDIGESTRANK_Test.php | 97 ++++++
.../Redis/TDigest/TDIGESTRESET_Test.php | 104 ++++++
.../Redis/TDigest/TDIGESTREVRANK_Test.php | 97 ++++++
.../TDigest/TDIGESTTRIMMED_MEAN_Test.php | 147 +++++++++
.../Command/Redis/TimeSeries/TSADD_Test.php | 115 +++++++
.../Command/Redis/TimeSeries/TSALTER_Test.php | 127 ++++++++
.../Redis/TimeSeries/TSCREATERULE_Test.php | 126 ++++++++
.../Redis/TimeSeries/TSCREATE_Test.php | 122 +++++++
.../Redis/TimeSeries/TSDECRBY_Test.php | 184 +++++++++++
.../Redis/TimeSeries/TSDELETERULE_Test.php | 97 ++++++
.../Command/Redis/TimeSeries/TSDEL_Test.php | 116 +++++++
.../Command/Redis/TimeSeries/TSGET_Test.php | 124 +++++++
.../Redis/TimeSeries/TSINCRBY_Test.php | 184 +++++++++++
.../Command/Redis/TimeSeries/TSINFO_Test.php | 96 ++++++
.../Command/Redis/TimeSeries/TSMADD_Test.php | 104 ++++++
.../Command/Redis/TimeSeries/TSMGET_Test.php | 120 +++++++
.../Redis/TimeSeries/TSMRANGE_Test.php | 163 ++++++++++
.../Redis/TimeSeries/TSMREVRANGE_Test.php | 163 ++++++++++
.../Redis/TimeSeries/TSQUERYINDEX_Test.php | 87 +++++
.../Command/Redis/TimeSeries/TSRANGE_Test.php | 142 ++++++++
.../Redis/TimeSeries/TSREVRANGE_Test.php | 142 ++++++++
.../Command/Redis/TopK/TOPKADD_Test.php | 114 +++++++
.../Command/Redis/TopK/TOPKINCRBY_Test.php | 94 ++++++
.../Command/Redis/TopK/TOPKINFO_Test.php | 96 ++++++
.../Command/Redis/TopK/TOPKLIST_Test.php | 156 +++++++++
.../Command/Redis/TopK/TOPKQUERY_Test.php | 91 ++++++
.../Command/Redis/TopK/TOPKRESERVE_Test.php | 152 +++++++++
tests/Predis/Command/RedisFactoryTest.php | 89 +++++
.../Traits/BloomFilters/BucketSizeTest.php | 86 +++++
.../Traits/BloomFilters/CapacityTest.php | 86 +++++
.../Command/Traits/BloomFilters/ErrorTest.php | 86 +++++
.../Traits/BloomFilters/ExpansionTest.php | 72 +++++
.../Command/Traits/BloomFilters/ItemsTest.php | 69 ++++
.../Traits/BloomFilters/MaxIterationsTest.php | 86 +++++
.../Traits/BloomFilters/NoCreateTest.php | 93 ++++++
.../Predis/Command/Traits/Json/IndentTest.php | 87 +++++
.../Command/Traits/Json/NewlineTest.php | 87 +++++
.../Command/Traits/Json/NxXxArgumentTest.php | 92 ++++++
.../Predis/Command/Traits/Json/SpaceTest.php | 87 +++++
tests/Predis/Transaction/MultiExecTest.php | 3 +-
351 files changed, 24718 insertions(+), 44 deletions(-)
create mode 100644 .github/workflows/stack.yml
create mode 100644 examples/Commands/Json/json_debug_memory.php
create mode 100644 examples/Commands/Search/ft_aggregate.php
create mode 100644 examples/Commands/Search/ft_alter.php
create mode 100644 examples/Commands/Search/ft_config_help.php
create mode 100644 examples/Commands/Search/ft_config_set_get.php
create mode 100644 examples/Commands/Search/ft_cursor_del.php
create mode 100644 examples/Commands/Search/ft_cursor_read.php
create mode 100644 examples/Commands/Search/ft_explain.php
create mode 100644 examples/Commands/Search/ft_profile.php
create mode 100644 examples/Commands/Search/ft_spellcheck.php
create mode 100644 examples/Commands/Search/ft_sug_add_get_del_len.php
create mode 100644 examples/Commands/Search/ft_syndump.php
create mode 100644 examples/Commands/Search/ft_synupdate.php
create mode 100644 examples/Commands/Search/ft_tagvals.php
create mode 100644 examples/Commands/TimeSeries/ts_add.php
create mode 100644 examples/Commands/TimeSeries/ts_alter.php
create mode 100644 examples/Commands/TimeSeries/ts_create.php
create mode 100644 examples/Commands/TimeSeries/ts_createrule.php
create mode 100644 examples/Commands/TimeSeries/ts_decrby.php
create mode 100644 examples/Commands/TimeSeries/ts_del.php
create mode 100644 examples/Commands/TimeSeries/ts_deleterule.php
create mode 100644 examples/Commands/TimeSeries/ts_get.php
create mode 100644 examples/Commands/TimeSeries/ts_incrby.php
create mode 100644 examples/Commands/TimeSeries/ts_info.php
create mode 100644 examples/Commands/TimeSeries/ts_madd.php
create mode 100644 examples/Commands/TimeSeries/ts_mget.php
create mode 100644 examples/Commands/TimeSeries/ts_mrange.php
create mode 100644 examples/Commands/TimeSeries/ts_mrevrange.php
create mode 100644 examples/Commands/TimeSeries/ts_queryindex.php
create mode 100644 examples/Commands/TimeSeries/ts_range.php
create mode 100644 examples/Commands/TimeSeries/ts_revrange.php
create mode 100644 src/ClientConfiguration.php
create mode 100644 src/Command/Argument/Search/AggregateArguments.php
create mode 100644 src/Command/Argument/Search/AlterArguments.php
create mode 100644 src/Command/Argument/Search/CommonArguments.php
create mode 100644 src/Command/Argument/Search/CreateArguments.php
create mode 100644 src/Command/Argument/Search/CursorArguments.php
create mode 100644 src/Command/Argument/Search/DropArguments.php
create mode 100644 src/Command/Argument/Search/ExplainArguments.php
create mode 100644 src/Command/Argument/Search/ProfileArguments.php
create mode 100644 src/Command/Argument/Search/SchemaFields/AbstractField.php
create mode 100644 src/Command/Argument/Search/SchemaFields/FieldInterface.php
create mode 100644 src/Command/Argument/Search/SchemaFields/GeoField.php
create mode 100644 src/Command/Argument/Search/SchemaFields/NumericField.php
create mode 100644 src/Command/Argument/Search/SchemaFields/TagField.php
create mode 100644 src/Command/Argument/Search/SchemaFields/TextField.php
create mode 100644 src/Command/Argument/Search/SchemaFields/VectorField.php
create mode 100644 src/Command/Argument/Search/SearchArguments.php
create mode 100644 src/Command/Argument/Search/SpellcheckArguments.php
create mode 100644 src/Command/Argument/Search/SugAddArguments.php
create mode 100644 src/Command/Argument/Search/SugGetArguments.php
create mode 100644 src/Command/Argument/Search/SynUpdateArguments.php
create mode 100644 src/Command/Argument/TimeSeries/AddArguments.php
create mode 100644 src/Command/Argument/TimeSeries/AlterArguments.php
create mode 100644 src/Command/Argument/TimeSeries/CommonArguments.php
create mode 100644 src/Command/Argument/TimeSeries/CreateArguments.php
create mode 100644 src/Command/Argument/TimeSeries/DecrByArguments.php
create mode 100644 src/Command/Argument/TimeSeries/GetArguments.php
create mode 100644 src/Command/Argument/TimeSeries/IncrByArguments.php
create mode 100644 src/Command/Argument/TimeSeries/InfoArguments.php
create mode 100644 src/Command/Argument/TimeSeries/MGetArguments.php
create mode 100644 src/Command/Argument/TimeSeries/MRangeArguments.php
create mode 100644 src/Command/Argument/TimeSeries/RangeArguments.php
create mode 100644 src/Command/Redis/BloomFilter/BFADD.php
create mode 100644 src/Command/Redis/BloomFilter/BFEXISTS.php
create mode 100644 src/Command/Redis/BloomFilter/BFINFO.php
create mode 100644 src/Command/Redis/BloomFilter/BFINSERT.php
create mode 100644 src/Command/Redis/BloomFilter/BFLOADCHUNK.php
create mode 100644 src/Command/Redis/BloomFilter/BFMADD.php
create mode 100644 src/Command/Redis/BloomFilter/BFMEXISTS.php
create mode 100644 src/Command/Redis/BloomFilter/BFRESERVE.php
create mode 100644 src/Command/Redis/BloomFilter/BFSCANDUMP.php
create mode 100644 src/Command/Redis/Container/Json/JSONDEBUG.php
create mode 100644 src/Command/Redis/Container/Search/FTCONFIG.php
create mode 100644 src/Command/Redis/Container/Search/FTCURSOR.php
create mode 100644 src/Command/Redis/CountMinSketch/CMSINCRBY.php
create mode 100644 src/Command/Redis/CountMinSketch/CMSINFO.php
create mode 100644 src/Command/Redis/CountMinSketch/CMSINITBYDIM.php
create mode 100644 src/Command/Redis/CountMinSketch/CMSINITBYPROB.php
create mode 100644 src/Command/Redis/CountMinSketch/CMSMERGE.php
create mode 100644 src/Command/Redis/CountMinSketch/CMSQUERY.php
create mode 100644 src/Command/Redis/CuckooFilter/CFADD.php
create mode 100644 src/Command/Redis/CuckooFilter/CFADDNX.php
create mode 100644 src/Command/Redis/CuckooFilter/CFCOUNT.php
create mode 100644 src/Command/Redis/CuckooFilter/CFDEL.php
create mode 100644 src/Command/Redis/CuckooFilter/CFEXISTS.php
create mode 100644 src/Command/Redis/CuckooFilter/CFINFO.php
create mode 100644 src/Command/Redis/CuckooFilter/CFINSERT.php
create mode 100644 src/Command/Redis/CuckooFilter/CFINSERTNX.php
create mode 100644 src/Command/Redis/CuckooFilter/CFLOADCHUNK.php
create mode 100644 src/Command/Redis/CuckooFilter/CFMEXISTS.php
create mode 100644 src/Command/Redis/CuckooFilter/CFRESERVE.php
create mode 100644 src/Command/Redis/CuckooFilter/CFSCANDUMP.php
create mode 100644 src/Command/Redis/Json/JSONARRAPPEND.php
create mode 100644 src/Command/Redis/Json/JSONARRINDEX.php
create mode 100644 src/Command/Redis/Json/JSONARRINSERT.php
create mode 100644 src/Command/Redis/Json/JSONARRLEN.php
create mode 100644 src/Command/Redis/Json/JSONARRPOP.php
create mode 100644 src/Command/Redis/Json/JSONARRTRIM.php
create mode 100644 src/Command/Redis/Json/JSONCLEAR.php
create mode 100644 src/Command/Redis/Json/JSONDEBUG.php
create mode 100644 src/Command/Redis/Json/JSONDEL.php
create mode 100644 src/Command/Redis/Json/JSONFORGET.php
create mode 100644 src/Command/Redis/Json/JSONGET.php
create mode 100644 src/Command/Redis/Json/JSONMGET.php
create mode 100644 src/Command/Redis/Json/JSONNUMINCRBY.php
create mode 100644 src/Command/Redis/Json/JSONOBJKEYS.php
create mode 100644 src/Command/Redis/Json/JSONOBJLEN.php
create mode 100644 src/Command/Redis/Json/JSONRESP.php
create mode 100644 src/Command/Redis/Json/JSONSET.php
create mode 100644 src/Command/Redis/Json/JSONSTRAPPEND.php
create mode 100644 src/Command/Redis/Json/JSONSTRLEN.php
create mode 100644 src/Command/Redis/Json/JSONTOGGLE.php
create mode 100644 src/Command/Redis/Json/JSONTYPE.php
create mode 100644 src/Command/Redis/Search/FTAGGREGATE.php
create mode 100644 src/Command/Redis/Search/FTALIASADD.php
create mode 100644 src/Command/Redis/Search/FTALIASDEL.php
create mode 100644 src/Command/Redis/Search/FTALIASUPDATE.php
create mode 100644 src/Command/Redis/Search/FTALTER.php
create mode 100644 src/Command/Redis/Search/FTCONFIG.php
create mode 100644 src/Command/Redis/Search/FTCREATE.php
create mode 100644 src/Command/Redis/Search/FTCURSOR.php
create mode 100644 src/Command/Redis/Search/FTDICTADD.php
create mode 100644 src/Command/Redis/Search/FTDICTDEL.php
create mode 100644 src/Command/Redis/Search/FTDICTDUMP.php
create mode 100644 src/Command/Redis/Search/FTDROPINDEX.php
create mode 100644 src/Command/Redis/Search/FTEXPLAIN.php
create mode 100644 src/Command/Redis/Search/FTINFO.php
create mode 100644 src/Command/Redis/Search/FTPROFILE.php
create mode 100644 src/Command/Redis/Search/FTSEARCH.php
create mode 100644 src/Command/Redis/Search/FTSPELLCHECK.php
create mode 100644 src/Command/Redis/Search/FTSUGADD.php
create mode 100644 src/Command/Redis/Search/FTSUGDEL.php
create mode 100644 src/Command/Redis/Search/FTSUGGET.php
create mode 100644 src/Command/Redis/Search/FTSUGLEN.php
create mode 100644 src/Command/Redis/Search/FTSYNDUMP.php
create mode 100644 src/Command/Redis/Search/FTSYNUPDATE.php
create mode 100644 src/Command/Redis/Search/FTTAGVALS.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTADD.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTBYRANK.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTBYREVRANK.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTCDF.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTCREATE.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTINFO.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTMAX.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTMERGE.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTMIN.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTQUANTILE.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTRANK.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTRESET.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTREVRANK.php
create mode 100644 src/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN.php
create mode 100644 src/Command/Redis/TimeSeries/TSADD.php
create mode 100644 src/Command/Redis/TimeSeries/TSALTER.php
create mode 100644 src/Command/Redis/TimeSeries/TSCREATE.php
create mode 100644 src/Command/Redis/TimeSeries/TSCREATERULE.php
create mode 100644 src/Command/Redis/TimeSeries/TSDECRBY.php
create mode 100644 src/Command/Redis/TimeSeries/TSDEL.php
create mode 100644 src/Command/Redis/TimeSeries/TSDELETERULE.php
create mode 100644 src/Command/Redis/TimeSeries/TSGET.php
create mode 100644 src/Command/Redis/TimeSeries/TSINCRBY.php
create mode 100644 src/Command/Redis/TimeSeries/TSINFO.php
create mode 100644 src/Command/Redis/TimeSeries/TSMADD.php
create mode 100644 src/Command/Redis/TimeSeries/TSMGET.php
create mode 100644 src/Command/Redis/TimeSeries/TSMRANGE.php
create mode 100644 src/Command/Redis/TimeSeries/TSMREVRANGE.php
create mode 100644 src/Command/Redis/TimeSeries/TSQUERYINDEX.php
create mode 100644 src/Command/Redis/TimeSeries/TSRANGE.php
create mode 100644 src/Command/Redis/TimeSeries/TSREVRANGE.php
create mode 100644 src/Command/Redis/TopK/TOPKADD.php
create mode 100644 src/Command/Redis/TopK/TOPKINCRBY.php
create mode 100644 src/Command/Redis/TopK/TOPKINFO.php
create mode 100644 src/Command/Redis/TopK/TOPKLIST.php
create mode 100644 src/Command/Redis/TopK/TOPKQUERY.php
create mode 100644 src/Command/Redis/TopK/TOPKRESERVE.php
create mode 100644 src/Command/Traits/BloomFilters/BucketSize.php
create mode 100644 src/Command/Traits/BloomFilters/Capacity.php
create mode 100644 src/Command/Traits/BloomFilters/Error.php
create mode 100644 src/Command/Traits/BloomFilters/Expansion.php
create mode 100644 src/Command/Traits/BloomFilters/Items.php
create mode 100644 src/Command/Traits/BloomFilters/MaxIterations.php
create mode 100644 src/Command/Traits/BloomFilters/NoCreate.php
create mode 100644 src/Command/Traits/Json/Indent.php
create mode 100644 src/Command/Traits/Json/Newline.php
create mode 100644 src/Command/Traits/Json/NxXxArgument.php
create mode 100644 src/Command/Traits/Json/Space.php
create mode 100644 tests/.editorconfig
create mode 100644 tests/Predis/Command/Argument/Search/AggregateArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/CommonArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/CreateArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/CursorArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/DropArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/ProfileArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SchemaFields/VectorFieldTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SearchArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SpellcheckArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SugAddArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/Search/SugGetArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/TimeSeries/AddArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/TimeSeries/CommonArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/TimeSeries/CreateArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/TimeSeries/IncrByArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/TimeSeries/InfoArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/TimeSeries/MRangeArgumentsTest.php
create mode 100644 tests/Predis/Command/Argument/TimeSeries/RangeArgumentsTest.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFADD_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFEXISTS_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFINFO_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFINSERT_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFLOADCHUNK_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFMADD_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFMEXISTS_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFRESERVE_Test.php
create mode 100644 tests/Predis/Command/Redis/BloomFilter/BFSCANDUMP_Test.php
create mode 100644 tests/Predis/Command/Redis/CountMinSketch/CMSINCRBY_Test.php
create mode 100644 tests/Predis/Command/Redis/CountMinSketch/CMSINFO_Test.php
create mode 100644 tests/Predis/Command/Redis/CountMinSketch/CMSINITBYDIM_Test.php
create mode 100644 tests/Predis/Command/Redis/CountMinSketch/CMSINITBYPROB_Test.php
create mode 100644 tests/Predis/Command/Redis/CountMinSketch/CMSMERGE_Test.php
create mode 100644 tests/Predis/Command/Redis/CountMinSketch/CMSQUERY_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFADDNX_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFADD_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFCOUNT_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFDEL_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFEXISTS_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFINFO_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFINSERTNX_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFLOADCHUNK_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFMEXISTS_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFRESERVE_Test.php
create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFSCANDUMP_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONARRAPPEND_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONARRINDEX_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONARRINSERT_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONARRLEN_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONARRPOP_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONARRTRIM_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONCLEAR_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONDEBUG_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONDEL_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONFORGET_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONGET_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONMGET_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONNUMINCRBY_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONOBJKEYS_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONOBJLEN_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONRESP_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONSET_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONSTRAPPEND_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONSTRLEN_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONTOGGLE_Test.php
create mode 100644 tests/Predis/Command/Redis/Json/JSONTYPE_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTALIASADD_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTALIASDEL_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTALIASUPDATE_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTALTER_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTCONFIG_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTCREATE_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTCURSOR_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTDICTADD_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTDICTDEL_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTDICTDUMP_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTDROPINDEX_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTINFO_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTPROFILE_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSEARCH_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSUGADD_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSUGDEL_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSUGGET_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSYNDUMP_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTSYNUPDATE_Test.php
create mode 100644 tests/Predis/Command/Redis/Search/FTTAGVALS_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTADD_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTCREATE_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTINFO_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTRANK_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTREVRANK_Test.php
create mode 100644 tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php
create mode 100644 tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php
create mode 100644 tests/Predis/Command/Redis/TopK/TOPKADD_Test.php
create mode 100644 tests/Predis/Command/Redis/TopK/TOPKINCRBY_Test.php
create mode 100644 tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php
create mode 100644 tests/Predis/Command/Redis/TopK/TOPKLIST_Test.php
create mode 100644 tests/Predis/Command/Redis/TopK/TOPKQUERY_Test.php
create mode 100644 tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php
create mode 100644 tests/Predis/Command/Traits/BloomFilters/BucketSizeTest.php
create mode 100644 tests/Predis/Command/Traits/BloomFilters/CapacityTest.php
create mode 100644 tests/Predis/Command/Traits/BloomFilters/ErrorTest.php
create mode 100644 tests/Predis/Command/Traits/BloomFilters/ExpansionTest.php
create mode 100644 tests/Predis/Command/Traits/BloomFilters/ItemsTest.php
create mode 100644 tests/Predis/Command/Traits/BloomFilters/MaxIterationsTest.php
create mode 100644 tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php
create mode 100644 tests/Predis/Command/Traits/Json/IndentTest.php
create mode 100644 tests/Predis/Command/Traits/Json/NewlineTest.php
create mode 100644 tests/Predis/Command/Traits/Json/NxXxArgumentTest.php
create mode 100644 tests/Predis/Command/Traits/Json/SpaceTest.php
diff --git a/.codespellrc b/.codespellrc
index fa081759..0e95e105 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -3,4 +3,4 @@ skip=./.git
check-hidden=
check-filenames=
builtin=clear,rare,informal,usage,code,names
-ignore-words-list=master,masters,slave,slaves,whitelist,cas,exat,smove
+ignore-words-list=master,masters,slave,slaves,whitelist,cas,exat,smove,SUGGET,sugget
diff --git a/.github/workflows/stack.yml b/.github/workflows/stack.yml
new file mode 100644
index 00000000..df25cac9
--- /dev/null
+++ b/.github/workflows/stack.yml
@@ -0,0 +1,62 @@
+name: Stack
+
+on:
+ push:
+ branches:
+ - main
+ - v2.**
+ pull_request:
+
+jobs:
+
+ predis:
+ name: PHP ${{ matrix.php }} (Redis Stack ${{ matrix.redis }})
+ runs-on: ubuntu-latest
+
+ services:
+ redis:
+ image: redis/redis-stack-server:${{ matrix.redis }}
+ options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ ports:
+ - 6379:6379
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php:
+ - '7.2'
+ - '7.3'
+ - '7.4'
+ - '8.0'
+ - '8.1'
+ - '8.2'
+ redis:
+ - latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Setup PHP with Composer and extensions
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "directory=$(composer config cache-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v3
+ with:
+ path: ${{ steps.composer-cache.outputs.directory }}
+ key: tests-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: tests-php-${{ matrix.php }}-composer
+
+ - name: Install Composer dependencies
+ env:
+ PHP_VERSION: ${{ matrix.php }}
+ run: composer install --ansi --no-progress --prefer-dist
+
+ - name: Run PHPUnit tests
+ run: vendor/bin/phpunit
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 35b38266..647a0029 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -28,11 +28,11 @@ jobs:
- '8.1'
- '8.2'
redis:
- - '3'
- - '4'
- - '5'
- - '6'
- - '7'
+ - 3
+ - 4
+ - 5
+ - 6
+ - 7
services:
redis:
@@ -60,11 +60,11 @@ jobs:
- name: Run PHPUnit tests
if: ${{ matrix.php != '8.1' || matrix.redis != '7' }}
- run: vendor/bin/phpunit --verbose
+ run: vendor/bin/phpunit --verbose --exclude-group realm-stack
- name: Run PHPUnit tests with coverage
if: ${{ matrix.php == '8.1' && matrix.redis == '7' }}
- run: vendor/bin/phpunit --verbose --coverage-clover build/logs/clover.xml --coverage-filter ./src
+ run: vendor/bin/phpunit --verbose --exclude-group realm-stack --coverage-clover build/logs/clover.xml --coverage-filter ./src
- name: Send coverage to Coveralls
env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6fa2861..ceb2660a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,9 +5,15 @@
### Added
- Added support for `ACL SETUSER, GETUSER, DRYRUN` commands
- Added support for `FCALL_RO` command
+- Added support for `Redis JSON` module
+- Added support for `Redis Bloom` module
+- Added support for `Redis Search` module
+- Added support for `Redis TimeSeries` module
### Fixed
- Fixed prefixes for `XTRIM` and `XREVRANGE` commands
+- Fix `fclose()` being called on invalid stream resource (#1199)
+- Fix `BitByte` and `ExpireOptions` traits skip processing on null values (#1169)
## v2.1.2 (2023-03-02)
diff --git a/examples/Commands/Json/json_debug_memory.php b/examples/Commands/Json/json_debug_memory.php
new file mode 100644
index 00000000..9746b00f
--- /dev/null
+++ b/examples/Commands/Json/json_debug_memory.php
@@ -0,0 +1,28 @@
+jsonset('key', '$', '{"key1":"value1","key2":"value2"}');
+
+// 2. Dump information about json memory usage in bytes
+$response = $client->jsondebug->memory('key', '$');
+
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_aggregate.php b/examples/Commands/Search/ft_aggregate.php
new file mode 100644
index 00000000..c273147b
--- /dev/null
+++ b/examples/Commands/Search/ft_aggregate.php
@@ -0,0 +1,52 @@
+prefix(['user:']);
+$schema = [
+ new TextField('name'),
+ new TextField('country'),
+ new NumericField('dob', '', AbstractField::SORTABLE),
+];
+
+$client->ftcreate('idx', $schema, $ftCreateArguments);
+
+// 2. Add documents
+$client->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
+$client->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600);
+$client->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
+
+// 3. Execute aggregation query
+$ftAggregateArguments = (new AggregateArguments())
+ ->apply('year(@dob)', 'birth')
+ ->groupBy('@country', '@birth')
+ ->reduce('COUNT', true, 'country_birth_Vlad_count')
+ ->sortBy(0, '@birth', 'DESC');
+
+$response = $client->ftaggregate('idx', '@name: "Vlad"', $ftAggregateArguments);
+
+// Response grouped by user country and birth year, with users count in each group, sorted by birth year from DESC.
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_alter.php b/examples/Commands/Search/ft_alter.php
new file mode 100644
index 00000000..9fb4402d
--- /dev/null
+++ b/examples/Commands/Search/ft_alter.php
@@ -0,0 +1,41 @@
+ftcreate('index_alter', $schema);
+
+echo 'Default index attributes:' . "\n";
+$defaultAttributes = $client->ftinfo('index_alter');
+print_r($defaultAttributes[7]);
+
+// 2. Add additional attribute to existing index
+$schema = [
+ new TextField('new_field_name'),
+];
+
+$client->ftalter('index_alter', $schema);
+
+echo 'Updated index attributes:' . "\n";
+$updatedAttributes = $client->ftinfo('index_alter');
+print_r($updatedAttributes[7]);
diff --git a/examples/Commands/Search/ft_config_help.php b/examples/Commands/Search/ft_config_help.php
new file mode 100644
index 00000000..0498590c
--- /dev/null
+++ b/examples/Commands/Search/ft_config_help.php
@@ -0,0 +1,25 @@
+ftconfig->help('MAXEXPANSIONS')
+);
diff --git a/examples/Commands/Search/ft_config_set_get.php b/examples/Commands/Search/ft_config_set_get.php
new file mode 100644
index 00000000..af2a008c
--- /dev/null
+++ b/examples/Commands/Search/ft_config_set_get.php
@@ -0,0 +1,29 @@
+ftconfig->set('TIMEOUT', 42)
+);
+print_r(
+ $client->ftconfig->get('TIMEOUT')
+);
diff --git a/examples/Commands/Search/ft_cursor_del.php b/examples/Commands/Search/ft_cursor_del.php
new file mode 100644
index 00000000..7d517b3e
--- /dev/null
+++ b/examples/Commands/Search/ft_cursor_del.php
@@ -0,0 +1,54 @@
+prefix(['user:']);
+$schema = [
+ new TextField('name'),
+ new TextField('country'),
+ new NumericField('dob', '', AbstractField::SORTABLE),
+];
+
+$client->ftcreate('index_cursor_del', $schema, $ftCreateArguments);
+
+// 2. Add documents
+$client->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
+$client->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600);
+$client->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
+
+// 3. Execute aggregation query
+$ftAggregateArguments = (new AggregateArguments())
+ ->apply('year(@dob)', 'birth')
+ ->groupBy('@country', '@birth')
+ ->reduce('COUNT', true, 'country_birth_Vlad_count')
+ ->sortBy(0, '@birth', 'DESC')
+ ->withCursor(1);
+
+[$_, $cursor] = $client->ftaggregate('index_cursor_del', '@name: "Vlad"', $ftAggregateArguments);
+
+// 4. Explicitly remove returned cursor
+$client->ftcursor->del('index_cursor_del', $cursor);
+
+echo "Cursor with cursorId - {$cursor} was removed";
diff --git a/examples/Commands/Search/ft_cursor_read.php b/examples/Commands/Search/ft_cursor_read.php
new file mode 100644
index 00000000..d3829b64
--- /dev/null
+++ b/examples/Commands/Search/ft_cursor_read.php
@@ -0,0 +1,62 @@
+prefix(['user:']);
+$schema = [
+ new TextField('name'),
+ new TextField('country'),
+ new NumericField('dob', '', AbstractField::SORTABLE),
+];
+
+$client->ftcreate('index_cursor_read', $schema, $ftCreateArguments);
+
+// 2. Add documents
+$client->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
+$client->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600);
+$client->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
+
+// 3. Execute aggregation query
+$ftAggregateArguments = (new AggregateArguments())
+ ->apply('year(@dob)', 'birth')
+ ->groupBy('@country', '@birth')
+ ->reduce('COUNT', true, 'country_birth_Vlad_count')
+ ->sortBy(0, '@birth', 'DESC')
+ ->withCursor(1);
+
+[$response, $cursor] = $client->ftaggregate('index_cursor_read', '@name: "Vlad"', $ftAggregateArguments);
+
+// 4. Processing response in loop until cursorId exists
+$actualResponse = [];
+$cursors = [];
+
+while ($cursor) {
+ $actualResponse[] = $response[1];
+ $cursors[] = $cursor;
+ [$response, $cursor] = $client->ftcursor->read('index_cursor_read', $cursor);
+}
+
+echo "Response: \n";
+print_r($actualResponse);
diff --git a/examples/Commands/Search/ft_explain.php b/examples/Commands/Search/ft_explain.php
new file mode 100644
index 00000000..8ebb2667
--- /dev/null
+++ b/examples/Commands/Search/ft_explain.php
@@ -0,0 +1,32 @@
+ftcreate('index_explain', $schema);
+
+// 2. Run query explanations
+$response = $client->ftexplain('index_explain', '(foo bar)|(hello world) @date:[100 200]|@date:[500 +inf]');
+
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_profile.php b/examples/Commands/Search/ft_profile.php
new file mode 100644
index 00000000..17c72267
--- /dev/null
+++ b/examples/Commands/Search/ft_profile.php
@@ -0,0 +1,38 @@
+ftcreate('index_profile', $schema);
+
+// 2. Create FT.PROFILE command arguments
+$arguments = (new ProfileArguments())
+ ->search()
+ ->query('query');
+
+// 3. Run profile query
+$response = $client->ftprofile('index_profile', $arguments);
+
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_spellcheck.php b/examples/Commands/Search/ft_spellcheck.php
new file mode 100644
index 00000000..fc7de0e4
--- /dev/null
+++ b/examples/Commands/Search/ft_spellcheck.php
@@ -0,0 +1,40 @@
+ftcreate('index_spellcheck', $schema);
+
+// 2. Add dictionary with terms
+$client->ftdictadd('dict', 'hello', 'help');
+
+// 3. Perform spelling correction query
+$response = $client->ftspellcheck(
+ 'index_spellcheck',
+ 'held',
+ (new SpellcheckArguments())->distance(2)->terms('dict')
+);
+
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_sug_add_get_del_len.php b/examples/Commands/Search/ft_sug_add_get_del_len.php
new file mode 100644
index 00000000..67576986
--- /dev/null
+++ b/examples/Commands/Search/ft_sug_add_get_del_len.php
@@ -0,0 +1,39 @@
+ftsugadd('key', 'hello', 2, (new SugAddArguments())->payload('payload'));
+
+echo 'Suggestions dictionary length: ' . $client->ftsuglen('key') . "\n";
+
+// 2. Perform fuzzy search by prefix to get previous suggestion with payload
+$response = $client->ftsugget('key', 'hellp', (new SugGetArguments())->fuzzy()->withPayloads());
+
+echo 'Suggestion for "hellp" prefix:' . "\n";
+print_r($response);
+
+// 3. Removes previous suggestion from key
+$client->ftsugdel('key', 'hello');
+$response = $client->ftsugget('key', 'hello');
+
+echo 'Suggestions, after removing "hello" suggestion:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_syndump.php b/examples/Commands/Search/ft_syndump.php
new file mode 100644
index 00000000..8a18058b
--- /dev/null
+++ b/examples/Commands/Search/ft_syndump.php
@@ -0,0 +1,35 @@
+ftcreate('index_syndump', $schema);
+
+// 2. Add synonyms group with terms
+$client->ftsynupdate('index_syndump', 'synonym1', null, 'term1', 'term2');
+
+// 3. Dump terms with synonyms
+$response = $client->ftsyndump('index_syndump');
+
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_synupdate.php b/examples/Commands/Search/ft_synupdate.php
new file mode 100644
index 00000000..49e3824e
--- /dev/null
+++ b/examples/Commands/Search/ft_synupdate.php
@@ -0,0 +1,32 @@
+ftcreate('index_synupdate', $schema);
+
+// 2. Add synonyms into synonym group
+$response = $client->ftsynupdate('index_synupdate', 'synonym1', null, 'term1', 'term2');
+
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/Search/ft_tagvals.php b/examples/Commands/Search/ft_tagvals.php
new file mode 100644
index 00000000..83d0cab5
--- /dev/null
+++ b/examples/Commands/Search/ft_tagvals.php
@@ -0,0 +1,37 @@
+ftcreate('index_tagvals', $schema, (new CreateArguments())->prefix(['prefix:']));
+
+// 2. Add indexed tags
+$client->hset('prefix:1', 'tag_field', 'Hello, World');
+$client->hset('prefix:2', 'tag_field', 'Hey, World');
+
+// 3. Unique tags value query
+$response = $client->fttagvals('index_tagvals', 'tag_field');
+
+echo 'Response:' . "\n";
+print_r($response);
diff --git a/examples/Commands/TimeSeries/ts_add.php b/examples/Commands/TimeSeries/ts_add.php
new file mode 100644
index 00000000..0f7e28aa
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_add.php
@@ -0,0 +1,38 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+
+// 2. Add sample into newly created time series
+$addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+$response = $client->tsadd('temperature:2:32', 123123123123, 27, $addArguments);
+
+echo "Timeseries was added with timestamp: {$response}";
diff --git a/examples/Commands/TimeSeries/ts_alter.php b/examples/Commands/TimeSeries/ts_alter.php
new file mode 100644
index 00000000..5e06c152
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_alter.php
@@ -0,0 +1,41 @@
+retentionMsecs(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}\n";
+
+// 2. Update Duplicate policy for time series above
+$arguments = (new AlterArguments())
+ ->duplicatePolicy(CommonArguments::POLICY_FIRST);
+
+$response = $client->tsalter('temperature:2:32', $arguments);
+$output = ($response == 'OK') ? 'Duplicate policy was successfully updated' : $response;
+
+echo $output;
diff --git a/examples/Commands/TimeSeries/ts_create.php b/examples/Commands/TimeSeries/ts_create.php
new file mode 100644
index 00000000..989d1e13
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_create.php
@@ -0,0 +1,31 @@
+retentionMsecs(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}";
diff --git a/examples/Commands/TimeSeries/ts_createrule.php b/examples/Commands/TimeSeries/ts_createrule.php
new file mode 100644
index 00000000..ed313793
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_createrule.php
@@ -0,0 +1,33 @@
+labels('type', 'temp', 'location', 'TLV');
+
+$createResponse = $client->tscreate('temp:TLV', $arguments);
+echo "Original time series creation status: {$createResponse}\n";
+
+$createResponse = $client->tscreate('dailyAvgTemp:TLV', $arguments);
+echo "Compacted time series creation status: {$createResponse}\n";
+
+$createRuleResponse = $client->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'avg', 1000);
+echo "Compacted rule for compacted time series creation status: {$createRuleResponse}\n";
diff --git a/examples/Commands/TimeSeries/ts_decrby.php b/examples/Commands/TimeSeries/ts_decrby.php
new file mode 100644
index 00000000..a3e85d4e
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_decrby.php
@@ -0,0 +1,45 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+
+// 2. Add sample into newly created time series
+$addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+$response = $client->tsadd('temperature:2:32', 123123123123, 27, $addArguments);
+
+echo "Timeseries was added with timestamp: {$response}\n";
+
+// 3. Increasing value and timestamp
+$client->tsdecrby('temperature:2:32', 1, (new DecrByArguments())->timestamp(123123123124));
+$response = $client->tsget('temperature:2:32');
+
+echo "Decreased value to - {$response[1]} and timestamp to {$response[0]}";
diff --git a/examples/Commands/TimeSeries/ts_del.php b/examples/Commands/TimeSeries/ts_del.php
new file mode 100644
index 00000000..009ab57c
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_del.php
@@ -0,0 +1,47 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+
+// 2. Add samples into time series
+$client->tsadd('temperature:2:32', 123123123123, 27);
+$client->tsadd('temperature:2:32', 123123123124, 28);
+$client->tsadd('temperature:2:32', 123123123125, 29);
+
+$response = $client->tsget('temperature:2:32');
+
+echo "Sample with highest timestamp - {$response[0]} and value {$response[1]}\n";
+
+// 3. Removes 2 samples with the highest timestamps
+$response = $client->tsdel('temperature:2:32', 123123123124, 123123123125);
+
+echo "Removed {$response} samples from timeseries.\n";
+
+// 3. Retrieve a timestamp with the highest timestamp
+$response = $client->tsget('temperature:2:32');
+
+echo "New sample with highest timestamp - {$response[0]} and value {$response[1]}";
diff --git a/examples/Commands/TimeSeries/ts_deleterule.php b/examples/Commands/TimeSeries/ts_deleterule.php
new file mode 100644
index 00000000..9b26941c
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_deleterule.php
@@ -0,0 +1,37 @@
+labels('type', 'temp', 'location', 'TLV');
+
+$createResponse = $client->tscreate('temp:TLV', $arguments);
+echo "Original time series creation status: {$createResponse}\n";
+
+$createResponse = $client->tscreate('dailyAvgTemp:TLV', $arguments);
+echo "Compacted time series creation status: {$createResponse}\n";
+
+$createRuleResponse = $client->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'avg', 1000);
+echo "Compacted rule for compacted time series creation status: {$createRuleResponse}\n";
+
+// 2. Remove compaction rule
+$deleteRuleResponse = $client->tsdeleterule('temp:TLV', 'dailyAvgTemp:TLV');
+echo "Compacted rule for compacted time series deletion status: {$deleteRuleResponse}\n";
diff --git a/examples/Commands/TimeSeries/ts_get.php b/examples/Commands/TimeSeries/ts_get.php
new file mode 100644
index 00000000..737b984f
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_get.php
@@ -0,0 +1,38 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+
+// 2. Add samples into time series
+$client->tsadd('temperature:2:32', 123123123123, 27);
+$client->tsadd('temperature:2:32', 123123123124, 28);
+$client->tsadd('temperature:2:32', 123123123125, 29);
+
+// 3. Retrieve a timestamp with highest timestamp
+$response = $client->tsget('temperature:2:32');
+
+echo "Sample with highest timestamp - {$response[0]} and value - {$response[1]}";
diff --git a/examples/Commands/TimeSeries/ts_incrby.php b/examples/Commands/TimeSeries/ts_incrby.php
new file mode 100644
index 00000000..2905d124
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_incrby.php
@@ -0,0 +1,45 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+
+// 2. Add sample into newly created time series
+$addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+$response = $client->tsadd('temperature:2:32', 123123123123, 27, $addArguments);
+
+echo "Timeseries was added with timestamp: {$response}\n";
+
+// 3. Increasing value and timestamp
+$client->tsincrby('temperature:2:32', 1, (new IncrByArguments())->timestamp(123123123124));
+$response = $client->tsget('temperature:2:32');
+
+echo "Increased value to - {$response[1]} and timestamp to {$response[0]}";
diff --git a/examples/Commands/TimeSeries/ts_info.php b/examples/Commands/TimeSeries/ts_info.php
new file mode 100644
index 00000000..66c52b23
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_info.php
@@ -0,0 +1,32 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+
+// 2. Show info about given time series
+print_r($client->tsinfo('temperature:2:32'));
diff --git a/examples/Commands/TimeSeries/ts_madd.php b/examples/Commands/TimeSeries/ts_madd.php
new file mode 100644
index 00000000..23a0336a
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_madd.php
@@ -0,0 +1,36 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+$client->tscreate('temperature:2:33', $arguments);
+
+// 2. Add samples into few time series
+$response = $client->tsmadd('temperature:2:32', 123123123123, 27, 'temperature:2:33', 123123123124, 28);
+$stringResponse = implode(', ', $response);
+
+echo "Samples was added to time series - timestamps: {$stringResponse}";
diff --git a/examples/Commands/TimeSeries/ts_mget.php b/examples/Commands/TimeSeries/ts_mget.php
new file mode 100644
index 00000000..eb3720d7
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_mget.php
@@ -0,0 +1,41 @@
+retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('type', 'temp', 'sensor_id', 2, 'area_id', 32);
+
+$client->tscreate('temperature:2:32', $arguments);
+$client->tscreate('temperature:2:33', $arguments);
+
+// 2. Add samples into time series
+$client->tsadd('temperature:2:32', 123123123123, 27);
+$client->tsadd('temperature:2:33', 123123123124, 27);
+
+// 3. Get sample from multiple time series matching given filter expression, with selected labels only
+$response = $client->tsmget((new MGetArguments())->selectedLabels('type'), 'type=temp');
+
+echo "Sample from time series, with label = 'type':\n";
+print_r($response);
diff --git a/examples/Commands/TimeSeries/ts_mrange.php b/examples/Commands/TimeSeries/ts_mrange.php
new file mode 100644
index 00000000..e35f83b1
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_mrange.php
@@ -0,0 +1,48 @@
+tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A'));
+echo "Time series A creation status: {$response}\n";
+
+$response = $client->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B'));
+echo "Time series B creation status: {$response}\n";
+
+// 2. Add samples into both time series
+$response = $client->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120);
+$stringResponse = implode(', ', $response);
+echo "Added samples into time series A with following timestamps: {$stringResponse}\n";
+
+$response = $client->tsmadd('stock:B', 1000, 120, 'stock:B', 1010, 110, 'stock:B', 1020, 100);
+$stringResponse = implode(', ', $response);
+echo "Added samples into time series B with following timestamps: {$stringResponse}\n";
+
+// 3. Query range across both time series filtered by "type" and grouped by max type
+$mrangeArguments = (new MRangeArguments())
+ ->withLabels()
+ ->filter('type=stock')
+ ->groupBy('type', 'max');
+
+$response = $client->tsmrange('-', '+', $mrangeArguments);
+
+echo "Response:\n";
+print_r($response);
diff --git a/examples/Commands/TimeSeries/ts_mrevrange.php b/examples/Commands/TimeSeries/ts_mrevrange.php
new file mode 100644
index 00000000..13fc189d
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_mrevrange.php
@@ -0,0 +1,48 @@
+tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A'));
+echo "Time series A creation status: {$response}\n";
+
+$response = $client->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B'));
+echo "Time series B creation status: {$response}\n";
+
+// 2. Add samples into both time series
+$response = $client->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120);
+$stringResponse = implode(', ', $response);
+echo "Added samples into time series A with following timestamps: {$stringResponse}\n";
+
+$response = $client->tsmadd('stock:B', 1000, 120, 'stock:B', 1010, 110, 'stock:B', 1020, 100);
+$stringResponse = implode(', ', $response);
+echo "Added samples into time series B with following timestamps: {$stringResponse}\n";
+
+// 3. Query range across both time series filtered by "type" and grouped by max type
+$mrangeArguments = (new MRangeArguments())
+ ->withLabels()
+ ->filter('type=stock')
+ ->groupBy('type', 'max');
+
+$response = $client->tsmrevrange('-', '+', $mrangeArguments);
+
+echo "Response:\n";
+print_r($response);
diff --git a/examples/Commands/TimeSeries/ts_queryindex.php b/examples/Commands/TimeSeries/ts_queryindex.php
new file mode 100644
index 00000000..96115099
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_queryindex.php
@@ -0,0 +1,36 @@
+labels('type', 'temp', 'location', 'TLV');
+
+$createResponse = $client->tscreate('temp:TLV', $arguments);
+echo "Time series with location TLV creation status: {$createResponse}\n";
+
+$anotherArguments = (new CreateArguments())
+ ->labels('type', 'temp', 'location', 'JER');
+
+$createResponse = $client->tscreate('temp:JER', $anotherArguments);
+echo "Time series with location JER creation status: {$createResponse}\n";
+
+echo "Returns all keys with location=TLV:\n";
+print_r($client->tsqueryindex('location=TLV'));
diff --git a/examples/Commands/TimeSeries/ts_range.php b/examples/Commands/TimeSeries/ts_range.php
new file mode 100644
index 00000000..6ac5055b
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_range.php
@@ -0,0 +1,40 @@
+labels('type', 'temp', 'location', 'TLV');
+$createResponse = $client->tscreate('temp:TLV', $createArguments);
+
+echo "Time series creation status: {$createResponse}\n";
+
+// 2. Add samples into time series
+$maddResponse = $client->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 9999, 'temp:TLV', 1030, 40);
+$stringResponse = implode(', ', $maddResponse);
+
+echo "Samples was added with following timestamps: {$stringResponse}\n";
+
+// 3. Query samples by values in the given range
+$rangeArguments = (new RangeArguments())->filterByValue(-100, 100);
+$rangeResponse = $client->tsrange('temp:TLV', '-', '+', $rangeArguments);
+
+echo "Samples with temperature in range -100 to 100 degrees:\n";
+print_r($rangeResponse);
diff --git a/examples/Commands/TimeSeries/ts_revrange.php b/examples/Commands/TimeSeries/ts_revrange.php
new file mode 100644
index 00000000..3892beaf
--- /dev/null
+++ b/examples/Commands/TimeSeries/ts_revrange.php
@@ -0,0 +1,40 @@
+labels('type', 'temp', 'location', 'TLV');
+$createResponse = $client->tscreate('temp:TLV', $createArguments);
+
+echo "Time series creation status: {$createResponse}\n";
+
+// 2. Add samples into time series
+$maddResponse = $client->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 9999, 'temp:TLV', 1030, 40);
+$stringResponse = implode(', ', $maddResponse);
+
+echo "Samples was added with following timestamps: {$stringResponse}\n";
+
+// 3. Query samples by values in the given range
+$rangeArguments = (new RangeArguments())->filterByValue(-100, 100);
+$rangeResponse = $client->tsrevrange('temp:TLV', '-', '+', $rangeArguments);
+
+echo "Samples with temperature in range -100 to 100 degrees in reverse order:\n";
+print_r($rangeResponse);
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 1bbd2dd9..dff6900b 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -48,7 +48,7 @@
-
+
diff --git a/src/ClientConfiguration.php b/src/ClientConfiguration.php
new file mode 100644
index 00000000..c70cd61c
--- /dev/null
+++ b/src/ClientConfiguration.php
@@ -0,0 +1,42 @@
+ [
+ ['name' => 'Json', 'commandPrefix' => 'JSON'],
+ ['name' => 'BloomFilter', 'commandPrefix' => 'BF'],
+ ['name' => 'CuckooFilter', 'commandPrefix' => 'CF'],
+ ['name' => 'CountMinSketch', 'commandPrefix' => 'CMS'],
+ ['name' => 'TDigest', 'commandPrefix' => 'TDIGEST'],
+ ['name' => 'TopK', 'commandPrefix' => 'TOPK'],
+ ['name' => 'Search', 'commandPrefix' => 'FT'],
+ ['name' => 'TimeSeries', 'commandPrefix' => 'TS'],
+ ],
+ ];
+
+ /**
+ * Returns available modules with configuration.
+ *
+ * @return array|string[][]
+ */
+ public static function getModules(): array
+ {
+ return self::$config['modules'];
+ }
+}
diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php
index e3581b30..0de20a32 100644
--- a/src/ClientContextInterface.php
+++ b/src/ClientContextInterface.php
@@ -14,11 +14,35 @@ namespace Predis;
use Predis\Command\Argument\Geospatial\ByInterface;
use Predis\Command\Argument\Geospatial\FromInterface;
+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;
+use Predis\Command\Argument\Search\SugAddArguments;
+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\AddArguments;
+use Predis\Command\Argument\TimeSeries\AlterArguments as TSAlterArguments;
+use Predis\Command\Argument\TimeSeries\CreateArguments as TSCreateArguments;
+use Predis\Command\Argument\TimeSeries\DecrByArguments;
+use Predis\Command\Argument\TimeSeries\GetArguments;
+use Predis\Command\Argument\TimeSeries\IncrByArguments;
+use Predis\Command\Argument\TimeSeries\InfoArguments;
+use Predis\Command\Argument\TimeSeries\MGetArguments;
+use Predis\Command\Argument\TimeSeries\MRangeArguments;
+use Predis\Command\Argument\TimeSeries\RangeArguments;
use Predis\Command\CommandInterface;
use Predis\Command\Redis\Container\ACL;
use Predis\Command\Redis\Container\FunctionContainer;
+use Predis\Command\Redis\Container\Json\JSONDEBUG;
+use Predis\Command\Redis\Container\Search\FTCONFIG;
+use Predis\Command\Redis\Container\Search\FTCURSOR;
/**
* Interface defining a client-side context such as a pipeline or transaction.
@@ -46,7 +70,16 @@ use Predis\Command\Redis\Container\FunctionContainer;
* @method $this ttl($key)
* @method $this type($key)
* @method $this append($key, $value)
- * @method $this bitcount($key, $start = null, $end = null, string $index = 'byte')
+ * @method $this bfadd(string $key, $item)
+ * @method $this bfexists(string $key, $item)
+ * @method $this bfinfo(string $key, string $modifier = '')
+ * @method $this bfinsert(string $key, int $capacity = -1, float $error = -1, int $expansion = -1, bool $noCreate = false, bool $nonScaling = false, string ...$item)
+ * @method $this bfloadchunk(string $key, int $iterator, $data)
+ * @method $this bfmadd(string $key, ...$item)
+ * @method $this bfmexists(string $key, ...$item)
+ * @method $this bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false)
+ * @method $this bfscandump(string $key, int $iterator)
+ * @method $this bitcount(string $key, $start = null, $end = null, string $index = 'byte')
* @method $this bitop($operation, $destkey, $key)
* @method $this bitfield($key, $subcommand, ...$subcommandArg)
* @method $this bitpos($key, $bit, $start = null, $end = null, string $index = 'byte')
@@ -54,11 +87,51 @@ use Predis\Command\Redis\Container\FunctionContainer;
* @method $this bzpopmax(array $keys, int $timeout)
* @method $this bzpopmin(array $keys, int $timeout)
* @method $this bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
+ * @method $this cfadd(string $key, $item)
+ * @method $this cfaddnx(string $key, $item)
+ * @method $this cfcount(string $key, $item)
+ * @method $this cfdel(string $key, $item)
+ * @method $this cfexists(string $key, $item)
+ * @method $this cfloadchunk(string $key, int $iterator, $data)
+ * @method $this cfmexists(string $key, ...$item)
+ * @method $this cfinfo(string $key)
+ * @method $this cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
+ * @method $this cfinsertnx(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
+ * @method $this cfreserve(string $key, int $capacity, int $bucketSize = -1, int $maxIterations = -1, int $expansion = -1)
+ * @method $this cfscandump(string $key, int $iterator)
+ * @method $this cmsincrby(string $key, string|int...$itemIncrementDictionary)
+ * @method $this cmsinfo(string $key)
+ * @method $this cmsinitbydim(string $key, int $width, int $depth)
+ * @method $this cmsinitbyprob(string $key, float $errorRate, float $probability)
+ * @method $this cmsmerge(string $destination, array $sources, array $weights = [])
+ * @method $this cmsquery(string $key, string ...$item)
* @method $this decr($key)
* @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 ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
+ * @method $this ftaliasadd(string $alias, string $index)
+ * @method $this ftaliasdel(string $alias)
+ * @method $this ftaliasupdate(string $alias, string $index)
+ * @method $this ftalter(string $index, FieldInterface[] $schema, ?AlterArguments $arguments = null)
+ * @method $this ftcreate(string $index, FieldInterface[] $schema, ?CreateArguments $arguments = null)
+ * @method $this ftdictadd(string $dict, ...$term)
+ * @method $this ftdictdel(string $dict, ...$term)
+ * @method $this ftdictdump(string $dict)
+ * @method $this ftdropindex(string $index, ?DropArguments $arguments = null)
+ * @method $this ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
+ * @method $this ftinfo(string $index)
+ * @method $this ftprofile(string $index, ProfileArguments $arguments)
+ * @method $this ftsearch(string $index, string $query, ?SearchArguments $arguments = null)
+ * @method $this ftspellcheck(string $index, string $query, ?SearchArguments $arguments = null)
+ * @method $this ftsugadd(string $key, string $string, float $score, ?SugAddArguments $arguments = null)
+ * @method $this ftsugdel(string $key, string $string)
+ * @method $this ftsugget(string $key, string $prefix, ?SugGetArguments $arguments = null)
+ * @method $this ftsuglen(string $key)
+ * @method $this ftsyndump(string $index)
+ * @method $this ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
+ * @method $this fttagvals(string $index, string $fieldName)
* @method $this get($key)
* @method $this getbit($key, $offset)
* @method $this getex(string $key, $modifier = '', $value = false)
@@ -94,6 +167,26 @@ use Predis\Command\Redis\Container\FunctionContainer;
* @method $this hsetnx($key, $field, $value)
* @method $this hvals($key)
* @method $this hstrlen($key, $field)
+ * @method $this jsonarrappend(string $key, string $path = '$', ...$value)
+ * @method $this jsonarrindex(string $key, string $path, string $value, int $start = 0, int $stop = 0)
+ * @method $this jsonarrinsert(string $key, string $path, int $index, string ...$value)
+ * @method $this jsonarrlen(string $key, string $path = '$')
+ * @method $this jsonarrpop(string $key, string $path = '$', int $index = -1)
+ * @method $this jsonarrtrim(string $key, string $path, int $start, int $stop)
+ * @method $this jsonclear(string $key, string $path = '$')
+ * @method $this jsondel(string $key, string $path = '$')
+ * @method $this jsonforget(string $key, string $path = '$')
+ * @method $this jsonget(string $key, string $indent = '', string $newline = '', string $space = '', string ...$paths)
+ * @method $this jsonnumincrby(string $key, string $path, int $value)
+ * @method $this jsonmget(array $keys, string $path)
+ * @method $this jsonobjkeys(string $key, string $path = '$')
+ * @method $this jsonobjlen(string $key, string $path = '$')
+ * @method $this jsonresp(string $key, string $path = '$')
+ * @method $this jsonset(string $key, string $path, string $value, ?string $subcommand = null)
+ * @method $this jsonstrappend(string $key, string $path, string $value)
+ * @method $this jsonstrlen(string $key, string $path = '$')
+ * @method $this jsontoggle(string $key, string $path)
+ * @method $this jsontype(string $key, string $path = '$')
* @method $this blmove(string $source, string $destination, string $where, string $to, int $timeout)
* @method $this blpop(array|string $keys, $timeout)
* @method $this brpop(array|string $keys, $timeout)
@@ -132,6 +225,43 @@ use Predis\Command\Redis\Container\FunctionContainer;
* @method $this sscan($key, $cursor, array $options = null)
* @method $this sunion(array|string $keys)
* @method $this sunionstore($destination, array|string $keys)
+ * @method $this tdigestadd(string $key, float ...$value)
+ * @method $this tdigestbyrank(string $key, int ...$rank)
+ * @method $this tdigestbyrevrank(string $key, int ...$reverseRank)
+ * @method $this tdigestcdf(string $key, int ...$value)
+ * @method $this tdigestcreate(string $key, int $compression = 0)
+ * @method $this tdigestinfo(string $key)
+ * @method $this tdigestmax(string $key)
+ * @method $this tdigestmerge(string $destinationKey, array $sourceKeys, int $compression = 0, bool $override = false)
+ * @method $this tdigestquantile(string $key, float ...$quantile)
+ * @method $this tdigestmin(string $key)
+ * @method $this tdigestrank(string $key, ...$value)
+ * @method $this tdigestreset(string $key)
+ * @method $this tdigestrevrank(string $key, float ...$value)
+ * @method $this tdigesttrimmed_mean(string $key, float $lowCutQuantile, float $highCutQuantile)
+ * @method $this topkadd(string $key, ...$items)
+ * @method $this topkincrby(string $key, ...$itemIncrement)
+ * @method $this topkinfo(string $key)
+ * @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 tsadd(string $key, int $timestamp, float $value, ?AddArguments $arguments = null)
+ * @method $this tsalter(string $key, ?TSAlterArguments $arguments = null)
+ * @method $this tscreate(string $key, ?TSCreateArguments $arguments = null)
+ * @method $this tscreaterule(string $sourceKey, string $destKey, string $aggregator, int $bucketDuration, int $alignTimestamp = 0)
+ * @method $this tsdecrby(string $key, float $value, ?DecrByArguments $arguments = null)
+ * @method $this tsdel(string $key, int $fromTimestamp, int $toTimestamp)
+ * @method $this tsdeleterule(string $sourceKey, string $destKey)
+ * @method $this tsget(string $key, GetArguments $arguments = null)
+ * @method $this tsincrby(string $key, float $value, ?IncrByArguments $arguments = null)
+ * @method $this tsinfo(string $key, ?InfoArguments $arguments = null)
+ * @method $this tsmadd(mixed ...$keyTimestampValue)
+ * @method $this tsmget(MGetArguments $arguments, string ...$filterExpression)
+ * @method $this tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
+ * @method $this tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
+ * @method $this tsqueryindex(string ...$filterExpression)
+ * @method $this tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
+ * @method $this tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method $this zadd($key, array $membersAndScoresDictionary)
* @method $this zcard($key)
* @method $this zcount($key, $min, $max)
@@ -207,6 +337,9 @@ use Predis\Command\Redis\Container\FunctionContainer;
*
* Container commands
* @property FunctionContainer $function
+ * @property FTCONFIG $ftconfig
+ * @property FTCURSOR $ftcursor
+ * @property JSONDEBUG $jsondebug
* @property ACL $acl
*/
interface ClientContextInterface
diff --git a/src/ClientInterface.php b/src/ClientInterface.php
index b86952af..e46209df 100644
--- a/src/ClientInterface.php
+++ b/src/ClientInterface.php
@@ -14,12 +14,36 @@ namespace Predis;
use Predis\Command\Argument\Geospatial\ByInterface;
use Predis\Command\Argument\Geospatial\FromInterface;
+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;
+use Predis\Command\Argument\Search\SugAddArguments;
+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\AddArguments;
+use Predis\Command\Argument\TimeSeries\AlterArguments as TSAlterArguments;
+use Predis\Command\Argument\TimeSeries\CreateArguments as TSCreateArguments;
+use Predis\Command\Argument\TimeSeries\DecrByArguments;
+use Predis\Command\Argument\TimeSeries\GetArguments;
+use Predis\Command\Argument\TimeSeries\IncrByArguments;
+use Predis\Command\Argument\TimeSeries\InfoArguments;
+use Predis\Command\Argument\TimeSeries\MGetArguments;
+use Predis\Command\Argument\TimeSeries\MRangeArguments;
+use Predis\Command\Argument\TimeSeries\RangeArguments;
use Predis\Command\CommandInterface;
use Predis\Command\FactoryInterface;
use Predis\Command\Redis\Container\ACL;
use Predis\Command\Redis\Container\FunctionContainer;
+use Predis\Command\Redis\Container\Json\JSONDEBUG;
+use Predis\Command\Redis\Container\Search\FTCONFIG;
+use Predis\Command\Redis\Container\Search\FTCURSOR;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Response\Status;
@@ -55,6 +79,15 @@ use Predis\Response\Status;
* @method int ttl(string $key)
* @method mixed type(string $key)
* @method int append(string $key, $value)
+ * @method int bfadd(string $key, $item)
+ * @method int bfexists(string $key, $item)
+ * @method array bfinfo(string $key, string $modifier = '')
+ * @method array bfinsert(string $key, int $capacity = -1, float $error = -1, int $expansion = -1, bool $noCreate = false, bool $nonScaling = false, string ...$item)
+ * @method Status bfloadchunk(string $key, int $iterator, $data)
+ * @method array bfmadd(string $key, ...$item)
+ * @method array bfmexists(string $key, ...$item)
+ * @method Status bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false)
+ * @method array bfscandump(string $key, int $iterator)
* @method int bitcount(string $key, $start = null, $end = null, string $index = 'byte')
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
@@ -63,11 +96,51 @@ use Predis\Response\Status;
* @method array bzpopmax(array $keys, int $timeout)
* @method array bzpopmin(array $keys, int $timeout)
* @method array bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
+ * @method int cfadd(string $key, $item)
+ * @method int cfaddnx(string $key, $item)
+ * @method int cfcount(string $key, $item)
+ * @method int cfdel(string $key, $item)
+ * @method int cfexists(string $key, $item)
+ * @method Status cfloadchunk(string $key, int $iterator, $data)
+ * @method int cfmexists(string $key, ...$item)
+ * @method array cfinfo(string $key)
+ * @method array cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
+ * @method array cfinsertnx(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
+ * @method Status cfreserve(string $key, int $capacity, int $bucketSize = -1, int $maxIterations = -1, int $expansion = -1)
+ * @method array cfscandump(string $key, int $iterator)
+ * @method array cmsincrby(string $key, string|int...$itemIncrementDictionary)
+ * @method array cmsinfo(string $key)
+ * @method Status cmsinitbydim(string $key, int $width, int $depth)
+ * @method Status cmsinitbyprob(string $key, float $errorRate, float $probability)
+ * @method Status cmsmerge(string $destination, array $sources, array $weights = [])
+ * @method array cmsquery(string $key, string ...$item)
* @method int decr(string $key)
* @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 array ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
+ * @method Status ftaliasadd(string $alias, string $index)
+ * @method Status ftaliasdel(string $alias)
+ * @method Status ftaliasupdate(string $alias, string $index)
+ * @method Status ftalter(string $index, FieldInterface[] $schema, ?AlterArguments $arguments = null)
+ * @method Status ftcreate(string $index, FieldInterface[] $schema, ?CreateArguments $arguments = null)
+ * @method int ftdictadd(string $dict, ...$term)
+ * @method int ftdictdel(string $dict, ...$term)
+ * @method array ftdictdump(string $dict)
+ * @method Status ftdropindex(string $index, ?DropArguments $arguments = null)
+ * @method string ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
+ * @method array ftinfo(string $index)
+ * @method array ftprofile(string $index, ProfileArguments $arguments)
+ * @method array ftsearch(string $index, string $query, ?SearchArguments $arguments = null)
+ * @method array ftspellcheck(string $index, string $query, ?SearchArguments $arguments = null)
+ * @method int ftsugadd(string $key, string $string, float $score, ?SugAddArguments $arguments = null)
+ * @method int ftsugdel(string $key, string $string)
+ * @method array ftsugget(string $key, string $prefix, ?SugGetArguments $arguments = null)
+ * @method int ftsuglen(string $key)
+ * @method array ftsyndump(string $index)
+ * @method Status ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
+ * @method array fttagvals(string $index, string $fieldName)
* @method string|null get(string $key)
* @method int getbit(string $key, $offset)
* @method int|null getex(string $key, $modifier = '', $value = false)
@@ -103,6 +176,26 @@ use Predis\Response\Status;
* @method int hsetnx(string $key, string $field, string $value)
* @method array hvals(string $key)
* @method int hstrlen(string $key, string $field)
+ * @method array jsonarrappend(string $key, string $path = '$', ...$value)
+ * @method array jsonarrindex(string $key, string $path, string $value, int $start = 0, int $stop = 0)
+ * @method array jsonarrinsert(string $key, string $path, int $index, string ...$value)
+ * @method array jsonarrlen(string $key, string $path = '$')
+ * @method array jsonarrpop(string $key, string $path = '$', int $index = -1)
+ * @method int jsonclear(string $key, string $path = '$')
+ * @method array jsonarrtrim(string $key, string $path, int $start, int $stop)
+ * @method int jsondel(string $key, string $path = '$')
+ * @method int jsonforget(string $key, string $path = '$')
+ * @method string jsonget(string $key, string $indent = '', string $newline = '', string $space = '', string ...$paths)
+ * @method string jsonnumincrby(string $key, string $path, int $value)
+ * @method array jsonmget(array $keys, string $path)
+ * @method array jsonobjkeys(string $key, string $path = '$')
+ * @method array jsonobjlen(string $key, string $path = '$')
+ * @method array jsonresp(string $key, string $path = '$')
+ * @method string jsonset(string $key, string $path, string $value, ?string $subcommand = null)
+ * @method array jsonstrappend(string $key, string $path, string $value)
+ * @method array jsonstrlen(string $key, string $path = '$')
+ * @method array jsontoggle(string $key, string $path)
+ * @method array jsontype(string $key, string $path = '$')
* @method string blmove(string $source, string $destination, string $where, string $to, int $timeout)
* @method array|null blpop(array|string $keys, int|float $timeout)
* @method array|null brpop(array|string $keys, int|float $timeout)
@@ -142,6 +235,43 @@ use Predis\Response\Status;
* @method string[] sunion(array|string $keys)
* @method int sunionstore(string $destination, array|string $keys)
* @method int touch(string[]|string $keyOrKeys, string ...$keys = null)
+ * @method Status tdigestadd(string $key, float ...$value)
+ * @method array tdigestbyrank(string $key, int ...$rank)
+ * @method array tdigestbyrevrank(string $key, int ...$reverseRank)
+ * @method array tdigestcdf(string $key, int ...$value)
+ * @method Status tdigestcreate(string $key, int $compression = 0)
+ * @method array tdigestinfo(string $key)
+ * @method string tdigestmax(string $key)
+ * @method Status tdigestmerge(string $destinationKey, array $sourceKeys, int $compression = 0, bool $override = false)
+ * @method string[] tdigestquantile(string $key, float ...$quantile)
+ * @method string tdigestmin(string $key)
+ * @method array tdigestrank(string $key, float ...$value)
+ * @method Status tdigestreset(string $key)
+ * @method array tdigestrevrank(string $key, float ...$value)
+ * @method string tdigesttrimmed_mean(string $key, float $lowCutQuantile, float $highCutQuantile)
+ * @method array topkadd(string $key, ...$items)
+ * @method array topkincrby(string $key, ...$itemIncrement)
+ * @method array topkinfo(string $key)
+ * @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 int tsadd(string $key, int $timestamp, float $value, ?AddArguments $arguments = null)
+ * @method Status tsalter(string $key, ?TSAlterArguments $arguments = null)
+ * @method Status tscreate(string $key, ?TSCreateArguments $arguments = null)
+ * @method Status tscreaterule(string $sourceKey, string $destKey, string $aggregator, int $bucketDuration, int $alignTimestamp = 0)
+ * @method int tsdecrby(string $key, float $value, ?DecrByArguments $arguments = null)
+ * @method int tsdel(string $key, int $fromTimestamp, int $toTimestamp)
+ * @method Status tsdeleterule(string $sourceKey, string $destKey)
+ * @method array tsget(string $key, GetArguments $arguments = null)
+ * @method int tsincrby(string $key, float $value, ?IncrByArguments $arguments = null)
+ * @method array tsinfo(string $key, ?InfoArguments $arguments = null)
+ * @method array tsmadd(mixed ...$keyTimestampValue)
+ * @method array tsmget(MGetArguments $arguments, string ...$filterExpression)
+ * @method array tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
+ * @method array tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
+ * @method array tsqueryindex(string ...$filterExpression)
+ * @method array tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
+ * @method array tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $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)
@@ -225,6 +355,9 @@ use Predis\Response\Status;
*
* Container commands
* @property FunctionContainer $function
+ * @property FTCONFIG $ftconfig
+ * @property FTCURSOR $ftcursor
+ * @property JSONDEBUG $jsondebug
* @property ACL $acl
*/
interface ClientInterface
diff --git a/src/Command/Argument/Search/AggregateArguments.php b/src/Command/Argument/Search/AggregateArguments.php
new file mode 100644
index 00000000..95096707
--- /dev/null
+++ b/src/Command/Argument/Search/AggregateArguments.php
@@ -0,0 +1,161 @@
+ 'ASC',
+ 'desc' => 'DESC',
+ ];
+
+ /**
+ * Loads document attributes from the source document.
+ *
+ * @param string ...$fields Could be just '*' to load all fields
+ * @return $this
+ */
+ public function load(string ...$fields): self
+ {
+ $arguments = func_get_args();
+
+ $this->arguments[] = 'LOAD';
+
+ if ($arguments[0] === '*') {
+ $this->arguments[] = '*';
+
+ return $this;
+ }
+
+ $this->arguments[] = count($arguments);
+ $this->arguments = array_merge($this->arguments, $arguments);
+
+ return $this;
+ }
+
+ /**
+ * Loads document attributes from the source document.
+ *
+ * @param string ...$properties
+ * @return $this
+ */
+ public function groupBy(string ...$properties): self
+ {
+ $arguments = func_get_args();
+
+ array_push($this->arguments, 'GROUPBY', count($arguments));
+ $this->arguments = array_merge($this->arguments, $arguments);
+
+ return $this;
+ }
+
+ /**
+ * Groups the results in the pipeline based on one or more properties.
+ *
+ * If you want to add alias property to your argument just add "true" value in arguments enumeration,
+ * next value will be considered as alias to previous one.
+ *
+ * Example: 'argument', true, 'name' => 'argument' AS 'name'
+ *
+ * @param string $function
+ * @param string|bool ...$argument
+ * @return $this
+ */
+ public function reduce(string $function, ...$argument): self
+ {
+ $arguments = func_get_args();
+ $functionValue = array_shift($arguments);
+ $argumentsCounter = 0;
+
+ for ($i = 0, $iMax = count($arguments); $i < $iMax; $i++) {
+ if (true === $arguments[$i]) {
+ $arguments[$i] = 'AS';
+ $i++;
+ continue;
+ }
+
+ $argumentsCounter++;
+ }
+
+ array_push($this->arguments, 'REDUCE', $functionValue);
+ $this->arguments = array_merge($this->arguments, [$argumentsCounter], $arguments);
+
+ return $this;
+ }
+
+ /**
+ * Sorts the pipeline up until the point of SORTBY, using a list of properties.
+ *
+ * @param int $max
+ * @param string ...$properties Enumeration of properties, including sorting direction (ASC, DESC)
+ * @return $this
+ */
+ public function sortBy(int $max = 0, ...$properties): self
+ {
+ $arguments = func_get_args();
+ $maxValue = array_shift($arguments);
+
+ $this->arguments[] = 'SORTBY';
+ $this->arguments = array_merge($this->arguments, [count($arguments)], $arguments);
+
+ if ($maxValue !== 0) {
+ array_push($this->arguments, 'MAX', $maxValue);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Applies a 1-to-1 transformation on one or more properties and either stores the result
+ * as a new property down the pipeline or replaces any property using this transformation.
+ *
+ * @param string $expression
+ * @param string $as
+ * @return $this
+ */
+ public function apply(string $expression, string $as = ''): self
+ {
+ array_push($this->arguments, 'APPLY', $expression);
+
+ if ($as !== '') {
+ array_push($this->arguments, 'AS', $as);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Scan part of the results with a quicker alternative than LIMIT.
+ *
+ * @param int $readSize
+ * @param int $idleTime
+ * @return $this
+ */
+ public function withCursor(int $readSize = 0, int $idleTime = 0): self
+ {
+ $this->arguments[] = 'WITHCURSOR';
+
+ if ($readSize !== 0) {
+ array_push($this->arguments, 'COUNT', $readSize);
+ }
+
+ if ($idleTime !== 0) {
+ array_push($this->arguments, 'MAXIDLE', $idleTime);
+ }
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/Search/AlterArguments.php b/src/Command/Argument/Search/AlterArguments.php
new file mode 100644
index 00000000..5acd2fe1
--- /dev/null
+++ b/src/Command/Argument/Search/AlterArguments.php
@@ -0,0 +1,17 @@
+arguments[] = 'LANGUAGE';
+ $this->arguments[] = $defaultLanguage;
+
+ return $this;
+ }
+
+ /**
+ * Selects the dialect version under which to execute the query.
+ * If not specified, the query will execute under the default dialect version
+ * set during module initial loading or via FT.CONFIG SET command.
+ *
+ * @param string $dialect
+ * @return $this
+ */
+ public function dialect(string $dialect): self
+ {
+ $this->arguments[] = 'DIALECT';
+ $this->arguments[] = $dialect;
+
+ return $this;
+ }
+
+ /**
+ * If set, does not scan and index.
+ *
+ * @return $this
+ */
+ public function skipInitialScan(): self
+ {
+ $this->arguments[] = 'SKIPINITIALSCAN';
+
+ return $this;
+ }
+
+ /**
+ * Adds an arbitrary, binary safe payload that is exposed to custom scoring functions.
+ *
+ * @param string $payload
+ * @return $this
+ */
+ public function payload(string $payload): self
+ {
+ $this->arguments[] = 'PAYLOAD';
+ $this->arguments[] = $payload;
+
+ return $this;
+ }
+
+ /**
+ * Also returns the relative internal score of each document.
+ *
+ * @return $this
+ */
+ public function withScores(): self
+ {
+ $this->arguments[] = 'WITHSCORES';
+
+ return $this;
+ }
+
+ /**
+ * Retrieves optional document payloads.
+ *
+ * @return $this
+ */
+ public function withPayloads(): self
+ {
+ $this->arguments[] = 'WITHPAYLOADS';
+
+ return $this;
+ }
+
+ /**
+ * Does not try to use stemming for query expansion but searches the query terms verbatim.
+ *
+ * @return $this
+ */
+ public function verbatim(): self
+ {
+ $this->arguments[] = 'VERBATIM';
+
+ return $this;
+ }
+
+ /**
+ * Overrides the timeout parameter of the module.
+ *
+ * @param int $timeout
+ * @return $this
+ */
+ public function timeout(int $timeout): self
+ {
+ $this->arguments[] = 'TIMEOUT';
+ $this->arguments[] = $timeout;
+
+ return $this;
+ }
+
+ /**
+ * Adds an arbitrary, binary safe payload that is exposed to custom scoring functions.
+ *
+ * @param int $offset
+ * @param int $num
+ * @return $this
+ */
+ public function limit(int $offset, int $num): self
+ {
+ array_push($this->arguments, 'LIMIT', $offset, $num);
+
+ return $this;
+ }
+
+ /**
+ * Adds filter expression into index.
+ *
+ * @param string $filter
+ * @return $this
+ */
+ public function filter(string $filter): self
+ {
+ $this->arguments[] = 'FILTER';
+ $this->arguments[] = $filter;
+
+ return $this;
+ }
+
+ /**
+ * Defines one or more value parameters. Each parameter has a name and a value.
+ *
+ * Example: ['name1', 'value1', 'name2', 'value2'...]
+ *
+ * @param array $nameValuesDictionary
+ * @return $this
+ */
+ public function params(array $nameValuesDictionary): self
+ {
+ $this->arguments[] = 'PARAMS';
+ $this->arguments[] = count($nameValuesDictionary);
+ $this->arguments = array_merge($this->arguments, $nameValuesDictionary);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray(): array
+ {
+ return $this->arguments;
+ }
+}
diff --git a/src/Command/Argument/Search/CreateArguments.php b/src/Command/Argument/Search/CreateArguments.php
new file mode 100644
index 00000000..b8e0176b
--- /dev/null
+++ b/src/Command/Argument/Search/CreateArguments.php
@@ -0,0 +1,191 @@
+ 'HASH',
+ 'json' => 'JSON',
+ ];
+
+ /**
+ * Specify data type for given index. To index JSON you must have the RedisJSON module to be installed.
+ *
+ * @param string $modifier
+ * @return $this
+ */
+ public function on(string $modifier = 'HASH'): self
+ {
+ if (in_array(strtoupper($modifier), $this->supportedDataTypesEnum)) {
+ $this->arguments[] = 'ON';
+ $this->arguments[] = $this->supportedDataTypesEnum[strtolower($modifier)];
+
+ return $this;
+ }
+
+ $enumValues = implode(', ', array_values($this->supportedDataTypesEnum));
+ throw new InvalidArgumentException("Wrong modifier value given. Currently supports: {$enumValues}");
+ }
+
+ /**
+ * Adds one or more prefixes into index.
+ *
+ * @param array $prefixes
+ * @return $this
+ */
+ public function prefix(array $prefixes): self
+ {
+ $this->arguments[] = 'PREFIX';
+ $this->arguments[] = count($prefixes);
+ $this->arguments = array_merge($this->arguments, $prefixes);
+
+ return $this;
+ }
+
+ /**
+ * Document attribute set as document language.
+ *
+ * @param string $languageAttribute
+ * @return $this
+ */
+ public function languageField(string $languageAttribute): self
+ {
+ $this->arguments[] = 'LANGUAGE_FIELD';
+ $this->arguments[] = $languageAttribute;
+
+ return $this;
+ }
+
+ /**
+ * Default score for documents in the index.
+ *
+ * @param float $defaultScore
+ * @return $this
+ */
+ public function score(float $defaultScore = 1.0): self
+ {
+ $this->arguments[] = 'SCORE';
+ $this->arguments[] = $defaultScore;
+
+ return $this;
+ }
+
+ /**
+ * Document attribute that used as the document rank based on the user ranking.
+ *
+ * @param string $scoreAttribute
+ * @return $this
+ */
+ public function scoreField(string $scoreAttribute): self
+ {
+ $this->arguments[] = 'SCORE_FIELD';
+ $this->arguments[] = $scoreAttribute;
+
+ return $this;
+ }
+
+ /**
+ * Forces RediSearch to encode indexes as if there were more than 32 text attributes.
+ *
+ * @return $this
+ */
+ public function maxTextFields(): self
+ {
+ $this->arguments[] = 'MAXTEXTFIELDS';
+
+ return $this;
+ }
+
+ /**
+ * Does not store term offsets for documents.
+ *
+ * @return $this
+ */
+ public function noOffsets(): self
+ {
+ $this->arguments[] = 'NOOFFSETS';
+
+ return $this;
+ }
+
+ /**
+ * Creates a lightweight temporary index that expires after a specified period of inactivity, in seconds.
+ *
+ * @param int $seconds
+ * @return $this
+ */
+ public function temporary(int $seconds): self
+ {
+ $this->arguments[] = 'TEMPORARY';
+ $this->arguments[] = $seconds;
+
+ return $this;
+ }
+
+ /**
+ * Conserves storage space and memory by disabling highlighting support.
+ *
+ * @return $this
+ */
+ public function noHl(): self
+ {
+ $this->arguments[] = 'NOHL';
+
+ return $this;
+ }
+
+ /**
+ * Does not store attribute bits for each term.
+ *
+ * @return $this
+ */
+ public function noFields(): self
+ {
+ $this->arguments[] = 'NOFIELDS';
+
+ return $this;
+ }
+
+ /**
+ * Avoids saving the term frequencies in the index.
+ *
+ * @return $this
+ */
+ public function noFreqs(): self
+ {
+ $this->arguments[] = 'NOFREQS';
+
+ return $this;
+ }
+
+ /**
+ * Sets the index with a custom stopword list, to be ignored during indexing and search time.
+ *
+ * @param array $stopWords
+ * @return $this
+ */
+ public function stopWords(array $stopWords): self
+ {
+ $this->arguments[] = 'STOPWORDS';
+ $this->arguments[] = count($stopWords);
+ $this->arguments = array_merge($this->arguments, $stopWords);
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/Search/CursorArguments.php b/src/Command/Argument/Search/CursorArguments.php
new file mode 100644
index 00000000..a8bd6b56
--- /dev/null
+++ b/src/Command/Argument/Search/CursorArguments.php
@@ -0,0 +1,44 @@
+arguments, 'COUNT', $readSize);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray(): array
+ {
+ return $this->arguments;
+ }
+}
diff --git a/src/Command/Argument/Search/DropArguments.php b/src/Command/Argument/Search/DropArguments.php
new file mode 100644
index 00000000..0c631320
--- /dev/null
+++ b/src/Command/Argument/Search/DropArguments.php
@@ -0,0 +1,43 @@
+arguments[] = 'DD';
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray(): array
+ {
+ return $this->arguments;
+ }
+}
diff --git a/src/Command/Argument/Search/ExplainArguments.php b/src/Command/Argument/Search/ExplainArguments.php
new file mode 100644
index 00000000..b4bd235b
--- /dev/null
+++ b/src/Command/Argument/Search/ExplainArguments.php
@@ -0,0 +1,17 @@
+arguments[] = 'SEARCH';
+
+ return $this;
+ }
+
+ /**
+ * Adds aggregate context.
+ *
+ * @return $this
+ */
+ public function aggregate(): self
+ {
+ $this->arguments[] = 'AGGREGATE';
+
+ return $this;
+ }
+
+ /**
+ * Removes details of reader iterator.
+ *
+ * @return $this
+ */
+ public function limited(): self
+ {
+ $this->arguments[] = 'LIMITED';
+
+ return $this;
+ }
+
+ /**
+ * Is query string, as if sent to FT.SEARCH.
+ *
+ * @param string $query
+ * @return $this
+ */
+ public function query(string $query): self
+ {
+ $this->arguments[] = 'QUERY';
+ $this->arguments[] = $query;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray(): array
+ {
+ return $this->arguments;
+ }
+}
diff --git a/src/Command/Argument/Search/SchemaFields/AbstractField.php b/src/Command/Argument/Search/SchemaFields/AbstractField.php
new file mode 100644
index 00000000..eb49f099
--- /dev/null
+++ b/src/Command/Argument/Search/SchemaFields/AbstractField.php
@@ -0,0 +1,69 @@
+fieldArguments[] = $identifier;
+
+ if ($alias !== '') {
+ $this->fieldArguments[] = 'AS';
+ $this->fieldArguments[] = $alias;
+ }
+
+ $this->fieldArguments[] = $fieldType;
+
+ if ($sortable === self::SORTABLE) {
+ $this->fieldArguments[] = 'SORTABLE';
+ } elseif ($sortable === self::SORTABLE_UNF) {
+ $this->fieldArguments[] = 'SORTABLE';
+ $this->fieldArguments[] = 'UNF';
+ }
+
+ if ($noIndex) {
+ $this->fieldArguments[] = 'NOINDEX';
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray(): array
+ {
+ return $this->fieldArguments;
+ }
+}
diff --git a/src/Command/Argument/Search/SchemaFields/FieldInterface.php b/src/Command/Argument/Search/SchemaFields/FieldInterface.php
new file mode 100644
index 00000000..80e57eba
--- /dev/null
+++ b/src/Command/Argument/Search/SchemaFields/FieldInterface.php
@@ -0,0 +1,22 @@
+setCommonOptions('GEO', $identifier, $alias, $sortable, $noIndex);
+ }
+}
diff --git a/src/Command/Argument/Search/SchemaFields/NumericField.php b/src/Command/Argument/Search/SchemaFields/NumericField.php
new file mode 100644
index 00000000..758b4e9c
--- /dev/null
+++ b/src/Command/Argument/Search/SchemaFields/NumericField.php
@@ -0,0 +1,31 @@
+setCommonOptions('NUMERIC', $identifier, $alias, $sortable, $noIndex);
+ }
+}
diff --git a/src/Command/Argument/Search/SchemaFields/TagField.php b/src/Command/Argument/Search/SchemaFields/TagField.php
new file mode 100644
index 00000000..358b3090
--- /dev/null
+++ b/src/Command/Argument/Search/SchemaFields/TagField.php
@@ -0,0 +1,44 @@
+setCommonOptions('TAG', $identifier, $alias, $sortable, $noIndex);
+
+ if ($separator !== ',') {
+ $this->fieldArguments[] = 'SEPARATOR';
+ $this->fieldArguments[] = $separator;
+ }
+
+ if ($caseSensitive) {
+ $this->fieldArguments[] = 'CASESENSITIVE';
+ }
+ }
+}
diff --git a/src/Command/Argument/Search/SchemaFields/TextField.php b/src/Command/Argument/Search/SchemaFields/TextField.php
new file mode 100644
index 00000000..d72c6238
--- /dev/null
+++ b/src/Command/Argument/Search/SchemaFields/TextField.php
@@ -0,0 +1,57 @@
+setCommonOptions('TEXT', $identifier, $alias, $sortable, $noIndex);
+
+ if ($noStem) {
+ $this->fieldArguments[] = 'NOSTEM';
+ }
+
+ if ($phonetic !== '') {
+ $this->fieldArguments[] = 'PHONETIC';
+ $this->fieldArguments[] = $phonetic;
+ }
+
+ if ($weight !== 1) {
+ $this->fieldArguments[] = 'WEIGHT';
+ $this->fieldArguments[] = $weight;
+ }
+
+ if ($withSuffixTrie) {
+ $this->fieldArguments[] = 'WITHSUFFIXTRIE';
+ }
+ }
+}
diff --git a/src/Command/Argument/Search/SchemaFields/VectorField.php b/src/Command/Argument/Search/SchemaFields/VectorField.php
new file mode 100644
index 00000000..5228c225
--- /dev/null
+++ b/src/Command/Argument/Search/SchemaFields/VectorField.php
@@ -0,0 +1,47 @@
+setCommonOptions('VECTOR', $fieldName, $alias);
+
+ array_push($this->fieldArguments, $algorithm, count($attributeNameValueDictionary));
+ $this->fieldArguments = array_merge($this->fieldArguments, $attributeNameValueDictionary);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray(): array
+ {
+ return $this->fieldArguments;
+ }
+}
diff --git a/src/Command/Argument/Search/SearchArguments.php b/src/Command/Argument/Search/SearchArguments.php
new file mode 100644
index 00000000..d1eb7058
--- /dev/null
+++ b/src/Command/Argument/Search/SearchArguments.php
@@ -0,0 +1,306 @@
+ 'ASC',
+ 'desc' => 'DESC',
+ ];
+
+ /**
+ * Returns the document ids and not the content.
+ *
+ * @return $this
+ */
+ public function noContent(): self
+ {
+ $this->arguments[] = 'NOCONTENT';
+
+ return $this;
+ }
+
+ /**
+ * Returns the value of the sorting key, right after the id and score and/or payload, if requested.
+ *
+ * @return $this
+ */
+ public function withSortKeys(): self
+ {
+ $this->arguments[] = 'WITHSORTKEYS';
+
+ return $this;
+ }
+
+ /**
+ * Limits results to those having numeric values ranging between min and max,
+ * if numeric_attribute is defined as a numeric attribute in FT.CREATE.
+ * Min and max follow ZRANGE syntax, and can be -inf, +inf, and use( for exclusive ranges.
+ * Multiple numeric filters for different attributes are supported in one query.
+ *
+ * @param array ...$filter Should contain: numeric_field, min and max. Example: ['numeric_field', 1, 10]
+ * @return $this
+ */
+ public function searchFilter(array ...$filter): self
+ {
+ $arguments = func_get_args();
+
+ foreach ($arguments as $argument) {
+ array_push($this->arguments, 'FILTER', ...$argument);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Filter the results to a given radius from lon and lat. Radius is given as a number and units.
+ *
+ * @param array ...$filter Should contain: geo_field, lon, lat, radius, unit. Example: ['geo_field', 34.1231, 35.1231, 300, km]
+ * @return $this
+ */
+ public function geoFilter(array ...$filter): self
+ {
+ $arguments = func_get_args();
+
+ foreach ($arguments as $argument) {
+ array_push($this->arguments, 'GEOFILTER', ...$argument);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Limits the result to a given set of keys specified in the list.
+ *
+ * @param array $keys
+ * @return $this
+ */
+ public function inKeys(array $keys): self
+ {
+ $this->arguments[] = 'INKEYS';
+ $this->arguments[] = count($keys);
+ $this->arguments = array_merge($this->arguments, $keys);
+
+ return $this;
+ }
+
+ /**
+ * Filters the results to those appearing only in specific attributes of the document, like title or URL.
+ *
+ * @param array $fields
+ * @return $this
+ */
+ public function inFields(array $fields): self
+ {
+ $this->arguments[] = 'INFIELDS';
+ $this->arguments[] = count($fields);
+ $this->arguments = array_merge($this->arguments, $fields);
+
+ return $this;
+ }
+
+ /**
+ * Limits the attributes returned from the document.
+ * Num is the number of attributes following the keyword.
+ * If num is 0, it acts like NOCONTENT.
+ * Identifier is either an attribute name (for hashes and JSON) or a JSON Path expression (for JSON).
+ * Property is an optional name used in the result. If not provided, the identifier is used in the result.
+ *
+ * If you want to add alias property to your identifier just add "true" value in identifier enumeration,
+ * next value will be considered as alias to previous one.
+ *
+ * Example: 'identifier', true, 'property' => 'identifier' AS 'property'
+ *
+ * @param int $count
+ * @param string|bool ...$identifier
+ * @return $this
+ */
+ public function addReturn(int $count, ...$identifier): self
+ {
+ $arguments = func_get_args();
+
+ $this->arguments[] = 'RETURN';
+
+ for ($i = 1, $iMax = count($arguments); $i < $iMax; $i++) {
+ if (true === $arguments[$i]) {
+ $arguments[$i] = 'AS';
+ }
+ }
+
+ $this->arguments = array_merge($this->arguments, $arguments);
+
+ return $this;
+ }
+
+ /**
+ * Returns only the sections of the attribute that contain the matched text.
+ *
+ * @param array $fields
+ * @param int $frags
+ * @param int $len
+ * @param string $separator
+ * @return $this
+ */
+ public function summarize(array $fields = [], int $frags = 0, int $len = 0, string $separator = ''): self
+ {
+ $this->arguments[] = 'SUMMARIZE';
+
+ if (!empty($fields)) {
+ $this->arguments[] = 'FIELDS';
+ $this->arguments[] = count($fields);
+ $this->arguments = array_merge($this->arguments, $fields);
+ }
+
+ if ($frags !== 0) {
+ $this->arguments[] = 'FRAGS';
+ $this->arguments[] = $frags;
+ }
+
+ if ($len !== 0) {
+ $this->arguments[] = 'LEN';
+ $this->arguments[] = $len;
+ }
+
+ if ($separator !== '') {
+ $this->arguments[] = 'SEPARATOR';
+ $this->arguments[] = $separator;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Formats occurrences of matched text.
+ *
+ * @param array $fields
+ * @param string $openTag
+ * @param string $closeTag
+ * @return $this
+ */
+ public function highlight(array $fields = [], string $openTag = '', string $closeTag = ''): self
+ {
+ $this->arguments[] = 'HIGHLIGHT';
+
+ if (!empty($fields)) {
+ $this->arguments[] = 'FIELDS';
+ $this->arguments[] = count($fields);
+ $this->arguments = array_merge($this->arguments, $fields);
+ }
+
+ if ($openTag !== '' && $closeTag !== '') {
+ array_push($this->arguments, 'TAGS', $openTag, $closeTag);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows a maximum of N intervening number of unmatched offsets between phrase terms.
+ * In other words, the slop for exact phrases is 0.
+ *
+ * @param int $slop
+ * @return $this
+ */
+ public function slop(int $slop): self
+ {
+ $this->arguments[] = 'SLOP';
+ $this->arguments[] = $slop;
+
+ return $this;
+ }
+
+ /**
+ * Puts the query terms in the same order in the document as in the query, regardless of the offsets between them.
+ * Typically used in conjunction with SLOP.
+ *
+ * @return $this
+ */
+ public function inOrder(): self
+ {
+ $this->arguments[] = 'INORDER';
+
+ return $this;
+ }
+
+ /**
+ * Uses a custom query expander instead of the stemmer.
+ *
+ * @param string $expander
+ * @return $this
+ */
+ public function expander(string $expander): self
+ {
+ $this->arguments[] = 'EXPANDER';
+ $this->arguments[] = $expander;
+
+ return $this;
+ }
+
+ /**
+ * Uses a custom scoring function you define.
+ *
+ * @param string $scorer
+ * @return $this
+ */
+ public function scorer(string $scorer): self
+ {
+ $this->arguments[] = 'SCORER';
+ $this->arguments[] = $scorer;
+
+ return $this;
+ }
+
+ /**
+ * Returns a textual description of how the scores were calculated.
+ * Using this options requires the WITHSCORES option.
+ *
+ * @return $this
+ */
+ public function explainScore(): self
+ {
+ $this->arguments[] = 'EXPLAINSCORE';
+
+ return $this;
+ }
+
+ /**
+ * Orders the results by the value of this attribute.
+ * This applies to both text and numeric attributes.
+ * Attributes needed for SORTBY should be declared as SORTABLE in the index, in order to be available with very low latency.
+ * Note that this adds memory overhead.
+ *
+ * @param string $sortAttribute
+ * @param string $orderBy
+ * @return $this
+ */
+ public function sortBy(string $sortAttribute, string $orderBy = 'asc'): self
+ {
+ $this->arguments[] = 'SORTBY';
+ $this->arguments[] = $sortAttribute;
+
+ if (in_array(strtoupper($orderBy), $this->sortingEnum)) {
+ $this->arguments[] = $this->sortingEnum[strtolower($orderBy)];
+ } else {
+ $enumValues = implode(', ', array_values($this->sortingEnum));
+ throw new InvalidArgumentException("Wrong order direction value given. Currently supports: {$enumValues}");
+ }
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/Search/SpellcheckArguments.php b/src/Command/Argument/Search/SpellcheckArguments.php
new file mode 100644
index 00000000..7a6c2489
--- /dev/null
+++ b/src/Command/Argument/Search/SpellcheckArguments.php
@@ -0,0 +1,59 @@
+ 'INCLUDE',
+ 'exclude' => 'EXCLUDE',
+ ];
+
+ /**
+ * Is maximum Levenshtein distance for spelling suggestions (default: 1, max: 4).
+ *
+ * @return $this
+ */
+ public function distance(int $distance): self
+ {
+ $this->arguments[] = 'DISTANCE';
+ $this->arguments[] = $distance;
+
+ return $this;
+ }
+
+ /**
+ * Specifies an inclusion (INCLUDE) or exclusion (EXCLUDE) of a custom dictionary named {dict}.
+ *
+ * @param string $dictionary
+ * @param string $modifier
+ * @param string ...$terms
+ * @return $this
+ */
+ public function terms(string $dictionary, string $modifier = 'INCLUDE', string ...$terms): self
+ {
+ if (!in_array(strtoupper($modifier), $this->termsEnum)) {
+ $enumValues = implode(', ', array_values($this->termsEnum));
+ throw new InvalidArgumentException("Wrong modifier value given. Currently supports: {$enumValues}");
+ }
+
+ array_push($this->arguments, 'TERMS', $this->termsEnum[strtolower($modifier)], $dictionary, ...$terms);
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/Search/SugAddArguments.php b/src/Command/Argument/Search/SugAddArguments.php
new file mode 100644
index 00000000..c8b97697
--- /dev/null
+++ b/src/Command/Argument/Search/SugAddArguments.php
@@ -0,0 +1,28 @@
+arguments[] = 'INCR';
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/Search/SugGetArguments.php b/src/Command/Argument/Search/SugGetArguments.php
new file mode 100644
index 00000000..1176c773
--- /dev/null
+++ b/src/Command/Argument/Search/SugGetArguments.php
@@ -0,0 +1,41 @@
+arguments[] = 'FUZZY';
+
+ return $this;
+ }
+
+ /**
+ * Limits the results to a maximum of num (default: 5).
+ *
+ * @param int $num
+ * @return $this
+ */
+ public function max(int $num): self
+ {
+ array_push($this->arguments, 'MAX', $num);
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/Search/SynUpdateArguments.php b/src/Command/Argument/Search/SynUpdateArguments.php
new file mode 100644
index 00000000..a6b286a4
--- /dev/null
+++ b/src/Command/Argument/Search/SynUpdateArguments.php
@@ -0,0 +1,17 @@
+arguments, 'ON_DUPLICATE', $policy);
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/TimeSeries/AlterArguments.php b/src/Command/Argument/TimeSeries/AlterArguments.php
new file mode 100644
index 00000000..238ebc36
--- /dev/null
+++ b/src/Command/Argument/TimeSeries/AlterArguments.php
@@ -0,0 +1,17 @@
+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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Is used when a time series is a compaction.
+ * With LATEST, TS.GET reports the compacted value of the latest, possibly partial, bucket.
+ *
+ * @return $this
+ */
+ public function latest(): self
+ {
+ $this->arguments[] = 'LATEST';
+
+ return $this;
+ }
+
+ /**
+ * Includes in the reply all label-value pairs representing metadata labels of the time series.
+ *
+ * @return $this
+ */
+ public function withLabels(): self
+ {
+ $this->arguments[] = 'WITHLABELS';
+
+ return $this;
+ }
+
+ /**
+ * Returns a subset of the label-value pairs that represent metadata labels of the time series.
+ *
+ * @return $this
+ */
+ public function selectedLabels(string ...$labels): self
+ {
+ array_push($this->arguments, 'SELECTED_LABELS', ...$labels);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray(): array
+ {
+ return $this->arguments;
+ }
+}
diff --git a/src/Command/Argument/TimeSeries/CreateArguments.php b/src/Command/Argument/TimeSeries/CreateArguments.php
new file mode 100644
index 00000000..e47d8ef7
--- /dev/null
+++ b/src/Command/Argument/TimeSeries/CreateArguments.php
@@ -0,0 +1,17 @@
+arguments, 'TIMESTAMP', $timeStamp);
+
+ return $this;
+ }
+
+ /**
+ * Changes data storage from compressed (default) to uncompressed.
+ *
+ * @return $this
+ */
+ public function uncompressed(): self
+ {
+ $this->arguments[] = 'UNCOMPRESSED';
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/TimeSeries/InfoArguments.php b/src/Command/Argument/TimeSeries/InfoArguments.php
new file mode 100644
index 00000000..1b2cec66
--- /dev/null
+++ b/src/Command/Argument/TimeSeries/InfoArguments.php
@@ -0,0 +1,43 @@
+arguments[] = 'DEBUG';
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray(): array
+ {
+ return $this->arguments;
+ }
+}
diff --git a/src/Command/Argument/TimeSeries/MGetArguments.php b/src/Command/Argument/TimeSeries/MGetArguments.php
new file mode 100644
index 00000000..585f574e
--- /dev/null
+++ b/src/Command/Argument/TimeSeries/MGetArguments.php
@@ -0,0 +1,17 @@
+arguments, 'FILTER', ...$filterExpressions);
+
+ return $this;
+ }
+
+ /**
+ * Splits time series into groups, each group contains time series that share the same
+ * value for the provided label name, then aggregates results in each group.
+ *
+ * @param string $label
+ * @param string $reducer
+ * @return $this
+ */
+ public function groupBy(string $label, string $reducer): self
+ {
+ array_push($this->arguments, 'GROUPBY', $label, 'REDUCE', $reducer);
+
+ return $this;
+ }
+}
diff --git a/src/Command/Argument/TimeSeries/RangeArguments.php b/src/Command/Argument/TimeSeries/RangeArguments.php
new file mode 100644
index 00000000..00d3772d
--- /dev/null
+++ b/src/Command/Argument/TimeSeries/RangeArguments.php
@@ -0,0 +1,85 @@
+arguments, 'FILTER_BY_TS', ...$ts);
+
+ return $this;
+ }
+
+ /**
+ * Filters samples by minimum and maximum values.
+ *
+ * @param int $min
+ * @param int $max
+ * @return $this
+ */
+ public function filterByValue(int $min, int $max): self
+ {
+ array_push($this->arguments, 'FILTER_BY_VALUE', $min, $max);
+
+ return $this;
+ }
+
+ /**
+ * Limits the number of returned samples.
+ *
+ * @param int $count
+ * @return $this
+ */
+ public function count(int $count): self
+ {
+ array_push($this->arguments, 'COUNT', $count);
+
+ return $this;
+ }
+
+ /**
+ * Aggregates samples into time buckets.
+ *
+ * @param string $aggregator
+ * @param int $bucketDuration Is duration of each bucket, in milliseconds.
+ * @param int $align It controls the time bucket timestamps by changing the reference timestamp on which a bucket is defined.
+ * @param int $bucketTimestamp Controls how bucket timestamps are reported.
+ * @param bool $empty Is a flag, which, when specified, reports aggregations also for empty buckets.
+ * @return $this
+ */
+ public function aggregation(string $aggregator, int $bucketDuration, int $align = 0, int $bucketTimestamp = 0, bool $empty = false): self
+ {
+ if ($align > 0) {
+ array_push($this->arguments, 'ALIGN', $align);
+ }
+
+ array_push($this->arguments, 'AGGREGATION', $aggregator, $bucketDuration);
+
+ if ($bucketTimestamp > 0) {
+ array_push($this->arguments, 'BUCKETTIMESTAMP', $bucketTimestamp);
+ }
+
+ if (true === $empty) {
+ $this->arguments[] = 'EMPTY';
+ }
+
+ return $this;
+ }
+}
diff --git a/src/Command/Redis/BloomFilter/BFADD.php b/src/Command/Redis/BloomFilter/BFADD.php
new file mode 100644
index 00000000..af30de08
--- /dev/null
+++ b/src/Command/Redis/BloomFilter/BFADD.php
@@ -0,0 +1,29 @@
+ 'CAPACITY',
+ 'size' => 'SIZE',
+ 'filters' => 'FILTERS',
+ 'items' => 'ITEMS',
+ 'expansion' => 'EXPANSION',
+ ];
+
+ public function getId()
+ {
+ return 'BF.INFO';
+ }
+
+ public function setArguments(array $arguments)
+ {
+ if (isset($arguments[1])) {
+ $modifier = array_pop($arguments);
+
+ if ($modifier === '') {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if (!in_array(strtoupper($modifier), $this->modifierEnum)) {
+ $enumValues = implode(', ', array_keys($this->modifierEnum));
+ throw new UnexpectedValueException("Argument accepts only: {$enumValues} values");
+ }
+
+ $arguments[] = $this->modifierEnum[strtolower($modifier)];
+ }
+
+ parent::setArguments($arguments);
+ }
+
+ public function parseResponse($data)
+ {
+ if (count($data) > 1) {
+ $result = [];
+
+ for ($i = 0, $iMax = count($data); $i < $iMax; ++$i) {
+ if (array_key_exists($i + 1, $data)) {
+ $result[(string) $data[$i]] = $data[++$i];
+ }
+ }
+
+ return $result;
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Command/Redis/BloomFilter/BFINSERT.php b/src/Command/Redis/BloomFilter/BFINSERT.php
new file mode 100644
index 00000000..50e23eea
--- /dev/null
+++ b/src/Command/Redis/BloomFilter/BFINSERT.php
@@ -0,0 +1,72 @@
+setNoCreate($arguments);
+ $arguments = $this->getArguments();
+
+ if (array_key_exists(5, $arguments) && $arguments[5]) {
+ $arguments[5] = 'NONSCALING';
+ }
+
+ $this->setItems($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setExpansion($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setErrorRate($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setCapacity($arguments);
+ $this->filterArguments();
+ }
+}
diff --git a/src/Command/Redis/BloomFilter/BFLOADCHUNK.php b/src/Command/Redis/BloomFilter/BFLOADCHUNK.php
new file mode 100644
index 00000000..76a78e99
--- /dev/null
+++ b/src/Command/Redis/BloomFilter/BFLOADCHUNK.php
@@ -0,0 +1,28 @@
+setExpansion($arguments);
+ $this->filterArguments();
+ }
+}
diff --git a/src/Command/Redis/BloomFilter/BFSCANDUMP.php b/src/Command/Redis/BloomFilter/BFSCANDUMP.php
new file mode 100644
index 00000000..f8576d2d
--- /dev/null
+++ b/src/Command/Redis/BloomFilter/BFSCANDUMP.php
@@ -0,0 +1,29 @@
+ 1) {
+ $result = [];
+
+ for ($i = 0, $iMax = count($data); $i < $iMax; ++$i) {
+ if (array_key_exists($i + 1, $data)) {
+ $result[(string) $data[$i]] = $data[++$i];
+ }
+ }
+
+ return $result;
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Command/Redis/CountMinSketch/CMSINITBYDIM.php b/src/Command/Redis/CountMinSketch/CMSINITBYDIM.php
new file mode 100644
index 00000000..8f1f23ff
--- /dev/null
+++ b/src/Command/Redis/CountMinSketch/CMSINITBYDIM.php
@@ -0,0 +1,28 @@
+ 1) {
+ $result = [];
+
+ for ($i = 0, $iMax = count($data); $i < $iMax; ++$i) {
+ if (array_key_exists($i + 1, $data)) {
+ $result[(string) $data[$i]] = $data[++$i];
+ }
+ }
+
+ return $result;
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Command/Redis/CuckooFilter/CFINSERT.php b/src/Command/Redis/CuckooFilter/CFINSERT.php
new file mode 100644
index 00000000..3a49a38d
--- /dev/null
+++ b/src/Command/Redis/CuckooFilter/CFINSERT.php
@@ -0,0 +1,52 @@
+setNoCreate($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setItems($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setCapacity($arguments);
+ $this->filterArguments();
+ }
+}
diff --git a/src/Command/Redis/CuckooFilter/CFINSERTNX.php b/src/Command/Redis/CuckooFilter/CFINSERTNX.php
new file mode 100644
index 00000000..629327b4
--- /dev/null
+++ b/src/Command/Redis/CuckooFilter/CFINSERTNX.php
@@ -0,0 +1,27 @@
+setExpansion($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setMaxIterations($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setBucketSize($arguments);
+ $this->filterArguments();
+ }
+}
diff --git a/src/Command/Redis/CuckooFilter/CFSCANDUMP.php b/src/Command/Redis/CuckooFilter/CFSCANDUMP.php
new file mode 100644
index 00000000..59caa651
--- /dev/null
+++ b/src/Command/Redis/CuckooFilter/CFSCANDUMP.php
@@ -0,0 +1,29 @@
+parseModuleRow($row);
+ }
+
[$k, $v] = explode(':', $row, 2);
if (preg_match('/^db\d+$/', $k)) {
@@ -124,4 +128,30 @@ class INFO extends RedisCommand
return $db;
}
+
+ /**
+ * Parsing module rows because of different format.
+ *
+ * @param string $row
+ * @return array
+ */
+ protected function parseModuleRow(string $row): array
+ {
+ [$moduleKeyword, $moduleData] = explode(':', $row);
+ $explodedData = explode(',', $moduleData);
+ $parsedData = [];
+
+ foreach ($explodedData as $moduleDataRow) {
+ [$k, $v] = explode('=', $moduleDataRow);
+
+ if ($k === 'name') {
+ $parsedData[0] = $v;
+ continue;
+ }
+
+ $parsedData[1][$k] = $v;
+ }
+
+ return $parsedData;
+ }
}
diff --git a/src/Command/Redis/Json/JSONARRAPPEND.php b/src/Command/Redis/Json/JSONARRAPPEND.php
new file mode 100644
index 00000000..1b2a9203
--- /dev/null
+++ b/src/Command/Redis/Json/JSONARRAPPEND.php
@@ -0,0 +1,28 @@
+setSpace($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setNewline($arguments);
+ $arguments = $this->getArguments();
+
+ $this->setIndent($arguments);
+ $this->filterArguments();
+ }
+}
diff --git a/src/Command/Redis/Json/JSONMGET.php b/src/Command/Redis/Json/JSONMGET.php
new file mode 100644
index 00000000..1a5163fb
--- /dev/null
+++ b/src/Command/Redis/Json/JSONMGET.php
@@ -0,0 +1,36 @@
+setSubcommand($arguments);
+ $this->filterArguments();
+ }
+}
diff --git a/src/Command/Redis/Json/JSONSTRAPPEND.php b/src/Command/Redis/Json/JSONSTRAPPEND.php
new file mode 100644
index 00000000..9b11458e
--- /dev/null
+++ b/src/Command/Redis/Json/JSONSTRAPPEND.php
@@ -0,0 +1,28 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$index, $query],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTALIASADD.php b/src/Command/Redis/Search/FTALIASADD.php
new file mode 100644
index 00000000..a2511ea3
--- /dev/null
+++ b/src/Command/Redis/Search/FTALIASADD.php
@@ -0,0 +1,28 @@
+toArray() : [];
+
+ $schema = array_reduce($schema, static function (array $carry, FieldInterface $field) {
+ return array_merge($carry, $field->toArray());
+ }, []);
+
+ array_unshift($schema, 'SCHEMA', 'ADD');
+
+ parent::setArguments(array_merge(
+ [$index],
+ $commandArguments,
+ $schema
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTCONFIG.php b/src/Command/Redis/Search/FTCONFIG.php
new file mode 100644
index 00000000..9cba60e0
--- /dev/null
+++ b/src/Command/Redis/Search/FTCONFIG.php
@@ -0,0 +1,30 @@
+toArray() : [];
+
+ $schema = array_reduce($schema, static function (array $carry, FieldInterface $field) {
+ return array_merge($carry, $field->toArray());
+ }, []);
+
+ array_unshift($schema, 'SCHEMA');
+
+ parent::setArguments(array_merge(
+ [$index],
+ $commandArguments,
+ $schema
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTCURSOR.php b/src/Command/Redis/Search/FTCURSOR.php
new file mode 100644
index 00000000..14a01948
--- /dev/null
+++ b/src/Command/Redis/Search/FTCURSOR.php
@@ -0,0 +1,34 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$subcommand, $index, $cursorId],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTDICTADD.php b/src/Command/Redis/Search/FTDICTADD.php
new file mode 100644
index 00000000..c0bc3da7
--- /dev/null
+++ b/src/Command/Redis/Search/FTDICTADD.php
@@ -0,0 +1,28 @@
+toArray();
+ }
+
+ parent::setArguments(array_merge(
+ [$index],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTEXPLAIN.php b/src/Command/Redis/Search/FTEXPLAIN.php
new file mode 100644
index 00000000..e2aa35d4
--- /dev/null
+++ b/src/Command/Redis/Search/FTEXPLAIN.php
@@ -0,0 +1,43 @@
+toArray();
+ }
+
+ parent::setArguments(array_merge(
+ [$index, $query],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTINFO.php b/src/Command/Redis/Search/FTINFO.php
new file mode 100644
index 00000000..02fa9597
--- /dev/null
+++ b/src/Command/Redis/Search/FTINFO.php
@@ -0,0 +1,28 @@
+toArray()
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTSEARCH.php b/src/Command/Redis/Search/FTSEARCH.php
new file mode 100644
index 00000000..9fbe8a56
--- /dev/null
+++ b/src/Command/Redis/Search/FTSEARCH.php
@@ -0,0 +1,39 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$index, $query],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTSPELLCHECK.php b/src/Command/Redis/Search/FTSPELLCHECK.php
new file mode 100644
index 00000000..ac0232bb
--- /dev/null
+++ b/src/Command/Redis/Search/FTSPELLCHECK.php
@@ -0,0 +1,38 @@
+toArray();
+ }
+
+ parent::setArguments(array_merge(
+ [$index, $query],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTSUGADD.php b/src/Command/Redis/Search/FTSUGADD.php
new file mode 100644
index 00000000..71c42a4a
--- /dev/null
+++ b/src/Command/Redis/Search/FTSUGADD.php
@@ -0,0 +1,39 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key, $string, $score],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTSUGDEL.php b/src/Command/Redis/Search/FTSUGDEL.php
new file mode 100644
index 00000000..15457d10
--- /dev/null
+++ b/src/Command/Redis/Search/FTSUGDEL.php
@@ -0,0 +1,28 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key, $prefix],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTSUGLEN.php b/src/Command/Redis/Search/FTSUGLEN.php
new file mode 100644
index 00000000..1e11d8d3
--- /dev/null
+++ b/src/Command/Redis/Search/FTSUGLEN.php
@@ -0,0 +1,28 @@
+toArray();
+ }
+
+ $terms = array_slice($arguments, 3);
+
+ parent::setArguments(array_merge(
+ [$index, $synonymGroupId],
+ $commandArguments,
+ $terms
+ ));
+ }
+}
diff --git a/src/Command/Redis/Search/FTTAGVALS.php b/src/Command/Redis/Search/FTTAGVALS.php
new file mode 100644
index 00000000..b323949f
--- /dev/null
+++ b/src/Command/Redis/Search/FTTAGVALS.php
@@ -0,0 +1,28 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key, $timestamp, $value],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSALTER.php b/src/Command/Redis/TimeSeries/TSALTER.php
new file mode 100644
index 00000000..8797d68a
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSALTER.php
@@ -0,0 +1,39 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSCREATE.php b/src/Command/Redis/TimeSeries/TSCREATE.php
new file mode 100644
index 00000000..0d88072c
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSCREATE.php
@@ -0,0 +1,39 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSCREATERULE.php b/src/Command/Redis/TimeSeries/TSCREATERULE.php
new file mode 100644
index 00000000..c8bd62bc
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSCREATERULE.php
@@ -0,0 +1,40 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key, $value],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSDEL.php b/src/Command/Redis/TimeSeries/TSDEL.php
new file mode 100644
index 00000000..747c5a62
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSDEL.php
@@ -0,0 +1,28 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSINCRBY.php b/src/Command/Redis/TimeSeries/TSINCRBY.php
new file mode 100644
index 00000000..3141e5de
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSINCRBY.php
@@ -0,0 +1,41 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key, $value],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSINFO.php b/src/Command/Redis/TimeSeries/TSINFO.php
new file mode 100644
index 00000000..d4941d40
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSINFO.php
@@ -0,0 +1,39 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSMADD.php b/src/Command/Redis/TimeSeries/TSMADD.php
new file mode 100644
index 00000000..08522469
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSMADD.php
@@ -0,0 +1,28 @@
+toArray();
+
+ array_push($processedArguments, 'FILTER', ...$arguments);
+
+ parent::setArguments(array_merge(
+ $commandArguments,
+ $processedArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSMRANGE.php b/src/Command/Redis/TimeSeries/TSMRANGE.php
new file mode 100644
index 00000000..3d68cdd9
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSMRANGE.php
@@ -0,0 +1,39 @@
+toArray();
+
+ parent::setArguments(array_merge(
+ [$fromTimestamp, $toTimestamp],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSMREVRANGE.php b/src/Command/Redis/TimeSeries/TSMREVRANGE.php
new file mode 100644
index 00000000..987fd820
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSMREVRANGE.php
@@ -0,0 +1,26 @@
+toArray() : [];
+
+ parent::setArguments(array_merge(
+ [$key, $fromTimestamp, $toTimestamp],
+ $commandArguments
+ ));
+ }
+}
diff --git a/src/Command/Redis/TimeSeries/TSREVRANGE.php b/src/Command/Redis/TimeSeries/TSREVRANGE.php
new file mode 100644
index 00000000..1e8768e9
--- /dev/null
+++ b/src/Command/Redis/TimeSeries/TSREVRANGE.php
@@ -0,0 +1,26 @@
+filterArguments();
+ }
+
+ public function parseResponse($data)
+ {
+ if ($this->isWithCountModifier()) {
+ $result = [];
+
+ for ($i = 0, $iMax = count($data); $i < $iMax; ++$i) {
+ if (array_key_exists($i + 1, $data)) {
+ $result[(string) $data[$i]] = $data[++$i];
+ }
+ }
+
+ return $result;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Checks for the presence of the WITHCOUNT modifier.
+ *
+ * @return bool
+ */
+ private function isWithCountModifier(): bool
+ {
+ $arguments = $this->getArguments();
+ $lastArgument = (!empty($arguments)) ? $arguments[count($arguments) - 1] : null;
+
+ return is_string($lastArgument) && strtoupper($lastArgument) === 'WITHCOUNT';
+ }
+}
diff --git a/src/Command/Redis/TopK/TOPKQUERY.php b/src/Command/Redis/TopK/TOPKQUERY.php
new file mode 100644
index 00000000..12890219
--- /dev/null
+++ b/src/Command/Redis/TopK/TOPKQUERY.php
@@ -0,0 +1,29 @@
+commands = [
@@ -44,13 +47,17 @@ class RedisFactory extends Factory
$commandID = strtoupper($commandID);
if (isset($this->commands[$commandID]) || array_key_exists($commandID, $this->commands)) {
- $commandClass = $this->commands[$commandID];
- } elseif (class_exists($commandClass = "Predis\Command\Redis\\$commandID")) {
- $this->commands[$commandID] = $commandClass;
- } else {
+ return $this->commands[$commandID];
+ }
+
+ $commandClass = $this->resolve($commandID);
+
+ if (null === $commandClass) {
return null;
}
+ $this->commands[$commandID] = $commandClass;
+
return $commandClass;
}
@@ -66,4 +73,40 @@ class RedisFactory extends Factory
// details of the implementation of this mechanism.
$this->commands[strtoupper($commandID)] = null;
}
+
+ /**
+ * Resolves command object from given command ID.
+ *
+ * @param string $commandID Command ID of virtual method call
+ * @return string|null FQDN of corresponding command object
+ */
+ private function resolve(string $commandID): ?string
+ {
+ if (class_exists($commandClass = self::COMMANDS_NAMESPACE . '\\' . $commandID)) {
+ return $commandClass;
+ }
+
+ $commandModule = $this->resolveCommandModuleByPrefix($commandID);
+
+ if (null === $commandModule) {
+ return null;
+ }
+
+ if (class_exists($commandClass = self::COMMANDS_NAMESPACE . '\\' . $commandModule . '\\' . $commandID)) {
+ return $commandClass;
+ }
+
+ return null;
+ }
+
+ private function resolveCommandModuleByPrefix(string $commandID): ?string
+ {
+ foreach (ClientConfiguration::getModules() as $module) {
+ if (preg_match("/^{$module['commandPrefix']}/", $commandID)) {
+ return $module['name'];
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/Command/Traits/BitByte.php b/src/Command/Traits/BitByte.php
index b7ce689c..067302d2 100644
--- a/src/Command/Traits/BitByte.php
+++ b/src/Command/Traits/BitByte.php
@@ -23,6 +23,12 @@ trait BitByte
{
$value = array_pop($arguments);
+ if (null === $value) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
if (in_array(strtoupper($value), self::$argumentEnum, true)) {
$arguments[] = self::$argumentEnum[$value];
} else {
diff --git a/src/Command/Traits/BloomFilters/BucketSize.php b/src/Command/Traits/BloomFilters/BucketSize.php
new file mode 100644
index 00000000..99e22be0
--- /dev/null
+++ b/src/Command/Traits/BloomFilters/BucketSize.php
@@ -0,0 +1,57 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$bucketSizeArgumentPositionOffset] === -1) {
+ array_splice($arguments, static::$bucketSizeArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$bucketSizeArgumentPositionOffset] < 1) {
+ throw new UnexpectedValueException('Wrong bucket size argument value or position offset');
+ }
+
+ $argument = $arguments[static::$bucketSizeArgumentPositionOffset];
+ $argumentsBefore = array_slice($arguments, 0, static::$bucketSizeArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$bucketSizeArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$bucketSizeModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/BloomFilters/Capacity.php b/src/Command/Traits/BloomFilters/Capacity.php
new file mode 100644
index 00000000..c0dccc8a
--- /dev/null
+++ b/src/Command/Traits/BloomFilters/Capacity.php
@@ -0,0 +1,57 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$capacityArgumentPositionOffset] === -1) {
+ array_splice($arguments, static::$capacityArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$capacityArgumentPositionOffset] < 1) {
+ throw new UnexpectedValueException('Wrong capacity argument value or position offset');
+ }
+
+ $argument = $arguments[static::$capacityArgumentPositionOffset];
+ $argumentsBefore = array_slice($arguments, 0, static::$capacityArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$capacityArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$capacityModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/BloomFilters/Error.php b/src/Command/Traits/BloomFilters/Error.php
new file mode 100644
index 00000000..661def80
--- /dev/null
+++ b/src/Command/Traits/BloomFilters/Error.php
@@ -0,0 +1,57 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$errorArgumentPositionOffset] === -1) {
+ array_splice($arguments, static::$errorArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$errorArgumentPositionOffset] < 0) {
+ throw new UnexpectedValueException('Wrong error argument value or position offset');
+ }
+
+ $argument = $arguments[static::$errorArgumentPositionOffset];
+ $argumentsBefore = array_slice($arguments, 0, static::$errorArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$errorArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$errorModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/BloomFilters/Expansion.php b/src/Command/Traits/BloomFilters/Expansion.php
new file mode 100644
index 00000000..74d916f4
--- /dev/null
+++ b/src/Command/Traits/BloomFilters/Expansion.php
@@ -0,0 +1,53 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$expansionArgumentPositionOffset] === -1) {
+ array_splice($arguments, static::$expansionArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$expansionArgumentPositionOffset] < 1) {
+ throw new UnexpectedValueException('Wrong expansion argument value or position offset');
+ }
+
+ $argument = $arguments[static::$expansionArgumentPositionOffset];
+ $argumentsBefore = array_slice($arguments, 0, static::$expansionArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$expansionArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$expansionModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/BloomFilters/Items.php b/src/Command/Traits/BloomFilters/Items.php
new file mode 100644
index 00000000..9d2e4dcf
--- /dev/null
+++ b/src/Command/Traits/BloomFilters/Items.php
@@ -0,0 +1,45 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ $argument = $arguments[static::$itemsArgumentPositionOffset];
+ $argumentsBefore = array_slice($arguments, 0, static::$itemsArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$itemsArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$itemsModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/BloomFilters/MaxIterations.php b/src/Command/Traits/BloomFilters/MaxIterations.php
new file mode 100644
index 00000000..fb307e6d
--- /dev/null
+++ b/src/Command/Traits/BloomFilters/MaxIterations.php
@@ -0,0 +1,57 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$maxIterationsArgumentPositionOffset] === -1) {
+ array_splice($arguments, static::$maxIterationsArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$maxIterationsArgumentPositionOffset] < 1) {
+ throw new UnexpectedValueException('Wrong max iterations argument value or position offset');
+ }
+
+ $argument = $arguments[static::$maxIterationsArgumentPositionOffset];
+ $argumentsBefore = array_slice($arguments, 0, static::$maxIterationsArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$maxIterationsArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$maxIterationsModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/BloomFilters/NoCreate.php b/src/Command/Traits/BloomFilters/NoCreate.php
new file mode 100644
index 00000000..7fc084ec
--- /dev/null
+++ b/src/Command/Traits/BloomFilters/NoCreate.php
@@ -0,0 +1,49 @@
+= $argumentsLength
+ || false === $arguments[static::$noCreateArgumentPositionOffset]
+ ) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ $argument = $arguments[static::$noCreateArgumentPositionOffset];
+
+ if (true === $argument) {
+ $argument = 'NOCREATE';
+ } else {
+ throw new UnexpectedValueException('Wrong NOCREATE argument type');
+ }
+
+ $argumentsBefore = array_slice($arguments, 0, static::$noCreateArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$noCreateArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter));
+ }
+}
diff --git a/src/Command/Traits/Expire/ExpireOptions.php b/src/Command/Traits/Expire/ExpireOptions.php
index 85cff7a2..4f683f3c 100644
--- a/src/Command/Traits/Expire/ExpireOptions.php
+++ b/src/Command/Traits/Expire/ExpireOptions.php
@@ -25,6 +25,12 @@ trait ExpireOptions
{
$value = array_pop($arguments);
+ if (null === $value) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
if (in_array(strtoupper($value), self::$argumentEnum, true)) {
$arguments[] = self::$argumentEnum[strtolower($value)];
} else {
diff --git a/src/Command/Traits/Json/Indent.php b/src/Command/Traits/Json/Indent.php
new file mode 100644
index 00000000..3e0dfb13
--- /dev/null
+++ b/src/Command/Traits/Json/Indent.php
@@ -0,0 +1,54 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$indentArgumentPositionOffset] === '') {
+ array_splice($arguments, static::$indentArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ $argument = $arguments[static::$indentArgumentPositionOffset];
+
+ if (!is_string($argument)) {
+ throw new UnexpectedValueException('Indent argument value should be a string');
+ }
+
+ $argumentsBefore = array_slice($arguments, 0, static::$indentArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$indentArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$indentModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/Json/Newline.php b/src/Command/Traits/Json/Newline.php
new file mode 100644
index 00000000..7bab8205
--- /dev/null
+++ b/src/Command/Traits/Json/Newline.php
@@ -0,0 +1,54 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$newlineArgumentPositionOffset] === '') {
+ array_splice($arguments, static::$newlineArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ $argument = $arguments[static::$newlineArgumentPositionOffset];
+
+ if (!is_string($argument)) {
+ throw new UnexpectedValueException('Newline argument value should be a string');
+ }
+
+ $argumentsBefore = array_slice($arguments, 0, static::$newlineArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$newlineArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$newlineModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/Json/NxXxArgument.php b/src/Command/Traits/Json/NxXxArgument.php
new file mode 100644
index 00000000..39d3cb23
--- /dev/null
+++ b/src/Command/Traits/Json/NxXxArgument.php
@@ -0,0 +1,64 @@
+ 'NX',
+ 'xx' => 'XX',
+ ];
+
+ public function setArguments(array $arguments)
+ {
+ $argumentsLength = count($arguments);
+
+ if (static::$nxXxArgumentPositionOffset >= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if (null === $arguments[static::$nxXxArgumentPositionOffset]) {
+ array_splice($arguments, static::$nxXxArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ $argument = $arguments[static::$nxXxArgumentPositionOffset];
+
+ if (!in_array(strtoupper($argument), self::$argumentEnum, true)) {
+ $enumValues = implode(', ', array_keys(self::$argumentEnum));
+ throw new UnexpectedValueException("Argument accepts only: {$enumValues} values");
+ }
+
+ $argumentsBefore = array_slice($arguments, 0, static::$nxXxArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$nxXxArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$argumentEnum[strtolower($argument)]],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Command/Traits/Json/Space.php b/src/Command/Traits/Json/Space.php
new file mode 100644
index 00000000..5c99828f
--- /dev/null
+++ b/src/Command/Traits/Json/Space.php
@@ -0,0 +1,54 @@
+= $argumentsLength) {
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ if ($arguments[static::$spaceArgumentPositionOffset] === '') {
+ array_splice($arguments, static::$spaceArgumentPositionOffset, 1, [false]);
+ parent::setArguments($arguments);
+
+ return;
+ }
+
+ $argument = $arguments[static::$spaceArgumentPositionOffset];
+
+ if (!is_string($argument)) {
+ throw new UnexpectedValueException('Space argument value should be a string');
+ }
+
+ $argumentsBefore = array_slice($arguments, 0, static::$spaceArgumentPositionOffset);
+ $argumentsAfter = array_slice($arguments, static::$spaceArgumentPositionOffset + 1);
+
+ parent::setArguments(array_merge(
+ $argumentsBefore,
+ [self::$spaceModifier],
+ [$argument],
+ $argumentsAfter
+ ));
+ }
+}
diff --git a/src/Connection/StreamConnection.php b/src/Connection/StreamConnection.php
index 44025306..e0e077c9 100644
--- a/src/Connection/StreamConnection.php
+++ b/src/Connection/StreamConnection.php
@@ -244,7 +244,10 @@ class StreamConnection extends AbstractConnection
public function disconnect()
{
if ($this->isConnected()) {
- fclose($this->getResource());
+ $resource = $this->getResource();
+ if (is_resource($resource)) {
+ fclose($resource);
+ }
parent::disconnect();
}
}
diff --git a/tests/.editorconfig b/tests/.editorconfig
new file mode 100644
index 00000000..71aa167b
--- /dev/null
+++ b/tests/.editorconfig
@@ -0,0 +1,2 @@
+[Predis/Command/Redis/Search/FTEXPLAIN_Test.php]
+indent_size = unset
diff --git a/tests/PHPUnit/PredisCommandTestCase.php b/tests/PHPUnit/PredisCommandTestCase.php
index 8e9fb270..9aa6f516 100644
--- a/tests/PHPUnit/PredisCommandTestCase.php
+++ b/tests/PHPUnit/PredisCommandTestCase.php
@@ -109,9 +109,10 @@ abstract class PredisCommandTestCase extends PredisTestCase
public function testCommandId(): void
{
$command = $this->getCommand();
+ $sanitizedCommandId = str_replace('.', '', $command->getId());
$this->assertInstanceOf('Predis\Command\CommandInterface', $command);
- $this->assertEquals($this->getExpectedId(), $command->getId());
+ $this->assertEquals($this->getExpectedId(), $sanitizedCommandId);
}
/**
diff --git a/tests/PHPUnit/PredisTestCase.php b/tests/PHPUnit/PredisTestCase.php
index f2605e68..d4156b10 100644
--- a/tests/PHPUnit/PredisTestCase.php
+++ b/tests/PHPUnit/PredisTestCase.php
@@ -23,6 +23,24 @@ use Predis\Connection;
abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
{
protected $redisServerVersion = null;
+ protected $redisJsonVersion;
+
+ /**
+ * @var string[]
+ */
+ private $modulesMapping = [
+ 'json' => ['annotation' => 'requiresRedisJsonVersion', 'name' => 'ReJSON'],
+ 'bloomFilter' => ['annotation' => 'requiresRedisBfVersion', 'name' => 'bf'],
+ 'search' => ['annotation' => 'requiresRediSearchVersion', 'name' => 'search'],
+ 'timeSeries' => ['annotation' => 'requiresRedisTimeSeriesVersion', 'name' => 'timeseries'],
+ ];
+
+ /**
+ * Info of current Redis instance.
+ *
+ * @var array
+ */
+ private $info;
/**
* {@inheritdoc}
@@ -30,6 +48,10 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
protected function setUp(): void
{
$this->checkRequiredRedisServerVersion();
+
+ foreach ($this->modulesMapping as $module => $config) {
+ $this->checkRequiredRedisModuleVersion($module);
+ }
}
/**
@@ -301,8 +323,13 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
return $this->redisServerVersion;
}
- $client = $this->createClient(null, null, true);
- $info = array_change_key_case($client->info());
+ if (isset($this->info)) {
+ $info = $this->info;
+ } else {
+ $client = $this->createClient(null, null, true);
+ $info = array_change_key_case($client->info());
+ $this->info = $info;
+ }
if (isset($info['server']['redis_version'])) {
// Redis >= 2.6
@@ -311,6 +338,7 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
// Redis < 2.6
$version = $info['redis_version'];
} else {
+ $client = $this->createClient(null, null, true);
$connection = $client->getConnection();
throw new RuntimeException("Unable to retrieve a valid server info payload from $connection");
}
@@ -396,6 +424,110 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
}
}
+ /**
+ * Ensures the current Redis JSON module matches version requirements for tests.
+ *
+ * @param string $module
+ * @return void
+ */
+ protected function checkRequiredRedisModuleVersion(string $module): void
+ {
+ if (null === $requiredVersion = $this->getRequiredModuleVersion($module)) {
+ return;
+ }
+
+ if (version_compare($this->getRedisServerVersion(), '6.0.0', '<')) {
+ $this->markTestSkipped(
+ 'Test skipped because Redis JSON module available since Redis 6.x'
+ );
+ }
+
+ $requiredVersion = explode(' ', $requiredVersion, 2);
+
+ if (count($requiredVersion) === 1) {
+ $reqVersion = $requiredVersion[0];
+ } else {
+ $reqVersion = $requiredVersion[1];
+ }
+
+ if (!$this->isSatisfiedRedisModuleVersion($reqVersion, $module)) {
+ $redisModuleVersion = $this->getRedisModuleVersion($module);
+ $module = strtoupper($module);
+
+ $this->markTestSkipped(
+ "Test requires a Redis $module module >= $reqVersion but target module is $redisModuleVersion"
+ );
+ }
+ }
+
+ /**
+ * @param string $versionToCheck
+ * @param string $module
+ * @return bool
+ */
+ protected function isSatisfiedRedisModuleVersion(string $versionToCheck, string $module): bool
+ {
+ $currentVersion = $this->getRedisModuleVersion($this->modulesMapping[$module]['name']);
+ $versionToCheck = str_replace('.', '0', $versionToCheck);
+
+ return $currentVersion >= (int) $versionToCheck;
+ }
+
+ /**
+ * Returns version of Redis JSON module if it's available.
+ *
+ * @param string $module
+ * @return string
+ */
+ protected function getRedisModuleVersion(string $module): string
+ {
+ if (isset($this->info)) {
+ $info = $this->info;
+ } else {
+ $client = $this->createClient(null, null, true);
+ $info = array_change_key_case($client->info());
+ $this->info = $info;
+ }
+
+ if (isset($info['modules'][$module]['ver'])) {
+ $this->redisJsonVersion = $info['modules'][$module]['ver'];
+
+ return $info['modules'][$module]['ver'];
+ }
+
+ return '0';
+ }
+
+ /**
+ * Returns version of given module for current Redis instance.
+ * Runs if command belong to one of modules and marked with appropriate annotation
+ * Runs on @connected tests.
+ *
+ * @param string $module
+ * @return string
+ */
+ protected function getRequiredModuleVersion(string $module): ?string
+ {
+ if (!isset($this->modulesMapping[$module])) {
+ throw new InvalidArgumentException('No existing annotation for given module');
+ }
+
+ $moduleAnnotation = $this->modulesMapping[$module]['annotation'];
+ $annotations = TestUtil::parseTestMethodAnnotations(
+ get_class($this),
+ $this->getName(false)
+ );
+
+ if (isset($annotations['method'][$moduleAnnotation], $annotations['method']['group']) &&
+ !empty($annotations['method'][$moduleAnnotation]) &&
+ in_array('connected', $annotations['method']['group'], true)
+ ) {
+ return $annotations['method'][$moduleAnnotation][0];
+ }
+
+ return null;
+ }
+
/**
* Marks current test skipped when test suite is running on CI environments.
*
diff --git a/tests/Predis/Command/Argument/Search/AggregateArgumentsTest.php b/tests/Predis/Command/Argument/Search/AggregateArgumentsTest.php
new file mode 100644
index 00000000..6286fade
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/AggregateArgumentsTest.php
@@ -0,0 +1,189 @@
+arguments = new AggregateArguments();
+ }
+
+ /**
+ * @dataProvider loadProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithLoadModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->load(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithGroupByModifier(): void
+ {
+ $this->arguments->groupBy('property1', 'property2');
+
+ $this->assertSame(['GROUPBY', 2, 'property1', 'property2'], $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider reduceProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithReduceModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->reduce(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider sortByProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithSortByModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->sortBy(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider applyProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithApplyByModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->apply(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider withCursorProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithWithCursorByModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->withCursor(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ public function loadProvider(): array
+ {
+ return [
+ 'with given fields' => [
+ ['field1', 'field2'],
+ ['LOAD', 2, 'field1', 'field2'],
+ ],
+ 'with all fields' => [
+ ['*'],
+ ['LOAD', '*'],
+ ],
+ ];
+ }
+
+ public function reduceProvider(): array
+ {
+ return [
+ 'without aliases' => [
+ ['function', 'arg1', 'arg2'],
+ ['REDUCE', 'function', 2, 'arg1', 'arg2'],
+ ],
+ 'with aliases' => [
+ ['function', 'arg1', true, 'alias1', 'arg2', true, 'alias2'],
+ ['REDUCE', 'function', 2, 'arg1', 'AS', 'alias1', 'arg2', 'AS', 'alias2'],
+ ],
+ ];
+ }
+
+ public function sortByProvider(): array
+ {
+ return [
+ 'without sorting direction and max value' => [
+ [0, 'property1', 'property2'],
+ ['SORTBY', 2, 'property1', 'property2'],
+ ],
+ 'with sorting direction' => [
+ [0, 'property1', 'ASC', 'property2', 'DESC'],
+ ['SORTBY', 4, 'property1', 'ASC', 'property2', 'DESC'],
+ ],
+ 'with max value' => [
+ [2, 'property1', 'property2'],
+ ['SORTBY', 2, 'property1', 'property2', 'MAX', 2],
+ ],
+ 'with sorting direction and max value' => [
+ [2, 'property1', 'ASC', 'property2', 'DESC'],
+ ['SORTBY', 4, 'property1', 'ASC', 'property2', 'DESC', 'MAX', 2],
+ ],
+ ];
+ }
+
+ public function applyProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['expression'],
+ ['APPLY', 'expression'],
+ ],
+ 'with alias' => [
+ ['expression', 'name'],
+ ['APPLY', 'expression', 'AS', 'name'],
+ ],
+ ];
+ }
+
+ public function withCursorProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ [],
+ ['WITHCURSOR'],
+ ],
+ 'with readSize argument' => [
+ [2],
+ ['WITHCURSOR', 'COUNT', 2],
+ ],
+ 'with maxIdle argument' => [
+ [0, 2],
+ ['WITHCURSOR', 'MAXIDLE', 2],
+ ],
+ 'with readSize and maxIdle arguments' => [
+ [3, 2],
+ ['WITHCURSOR', 'COUNT', 3, 'MAXIDLE', 2],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/CommonArgumentsTest.php b/tests/Predis/Command/Argument/Search/CommonArgumentsTest.php
new file mode 100644
index 00000000..d49797b6
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/CommonArgumentsTest.php
@@ -0,0 +1,138 @@
+arguments = new CommonArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithLanguageModifier(): void
+ {
+ $this->arguments->language('english');
+
+ $this->assertSame(['LANGUAGE', 'english'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithDialectModifier(): void
+ {
+ $this->arguments->dialect('dialect');
+
+ $this->assertSame(['DIALECT', 'dialect'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithPayloadModifier(): void
+ {
+ $this->arguments->payload('payload');
+
+ $this->assertSame(['PAYLOAD', 'payload'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithStopInitialScanModifier(): void
+ {
+ $this->arguments->skipInitialScan();
+
+ $this->assertSame(['SKIPINITIALSCAN'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithWithScoresModifier(): void
+ {
+ $this->arguments->withScores();
+
+ $this->assertSame(['WITHSCORES'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithWithPayloadsModifier(): void
+ {
+ $this->arguments->withPayloads();
+
+ $this->assertSame(['WITHPAYLOADS'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithVerbatimModifier(): void
+ {
+ $this->arguments->verbatim();
+
+ $this->assertSame(['VERBATIM'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithTimeoutModifier(): void
+ {
+ $this->arguments->timeout(2);
+
+ $this->assertSame(['TIMEOUT', 2], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithLimitModifier(): void
+ {
+ $this->arguments->limit(2, 2);
+
+ $this->assertSame(['LIMIT', 2, 2], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithFilterModifier(): void
+ {
+ $this->arguments->filter('@age>16');
+
+ $this->assertSame(['FILTER', '@age>16'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithParamsModifier(): void
+ {
+ $this->arguments->params(['name1', 'value1', 'name2', 'value2']);
+
+ $this->assertSame(['PARAMS', 4, 'name1', 'value1', 'name2', 'value2'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/CreateArgumentsTest.php b/tests/Predis/Command/Argument/Search/CreateArgumentsTest.php
new file mode 100644
index 00000000..0cf92711
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/CreateArgumentsTest.php
@@ -0,0 +1,175 @@
+arguments = new CreateArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithOnModifier(): void
+ {
+ $this->arguments->on('json');
+
+ $this->assertSame(['ON', 'JSON'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnInvalidModifierValue(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Wrong modifier value given. Currently supports: HASH, JSON');
+
+ $this->arguments->on('wrong');
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithPrefixModifier(): void
+ {
+ $this->arguments->prefix(['prefix:', 'prefix1:']);
+
+ $this->assertSame(['PREFIX', 2, 'prefix:', 'prefix1:'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithLanguageFieldModifier(): void
+ {
+ $this->arguments->languageField('language_attribute');
+
+ $this->assertSame(['LANGUAGE_FIELD', 'language_attribute'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithScoreModifier(): void
+ {
+ $this->arguments->score(10.0);
+
+ $this->assertSame(['SCORE', 10.0], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithScoreFieldModifier(): void
+ {
+ $this->arguments->scoreField('score_field');
+
+ $this->assertSame(['SCORE_FIELD', 'score_field'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithMaxTestFieldsModifier(): void
+ {
+ $this->arguments->maxTextFields();
+
+ $this->assertSame(['MAXTEXTFIELDS'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithNoOffsetsModifier(): void
+ {
+ $this->arguments->noOffsets();
+
+ $this->assertSame(['NOOFFSETS'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithTemporaryModifier(): void
+ {
+ $this->arguments->temporary(1);
+
+ $this->assertSame(['TEMPORARY', 1], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithNoHlModifier(): void
+ {
+ $this->arguments->noHl();
+
+ $this->assertSame(['NOHL'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithNoFieldsModifier(): void
+ {
+ $this->arguments->noFields();
+
+ $this->assertSame(['NOFIELDS'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithNoFreqsModifier(): void
+ {
+ $this->arguments->noFreqs();
+
+ $this->assertSame(['NOFREQS'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithStopWordsModifier(): void
+ {
+ $this->arguments->stopWords(['word1', 'word2']);
+
+ $this->assertSame(['STOPWORDS', 2, 'word1', 'word2'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesCorrectFTCreateArgumentsSetOnMethodsChainCall(): void
+ {
+ $this->arguments->prefix(['prefix:', 'prefix1:']);
+ $this->arguments->filter('@age>16');
+ $this->arguments->stopWords(['hello', 'world']);
+
+ $this->assertSame(
+ ['PREFIX', 2, 'prefix:', 'prefix1:', 'FILTER', '@age>16', 'STOPWORDS', 2, 'hello', 'world'],
+ $this->arguments->toArray()
+ );
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/CursorArgumentsTest.php b/tests/Predis/Command/Argument/Search/CursorArgumentsTest.php
new file mode 100644
index 00000000..263ef370
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/CursorArgumentsTest.php
@@ -0,0 +1,38 @@
+arguments = new CursorArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithCountModifier(): void
+ {
+ $this->arguments->count(2);
+
+ $this->assertSame(['COUNT', 2], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/DropArgumentsTest.php b/tests/Predis/Command/Argument/Search/DropArgumentsTest.php
new file mode 100644
index 00000000..c843013a
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/DropArgumentsTest.php
@@ -0,0 +1,38 @@
+arguments = new DropArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithLanguageModifier(): void
+ {
+ $this->arguments->dd();
+
+ $this->assertSame(['DD'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/ProfileArgumentsTest.php b/tests/Predis/Command/Argument/Search/ProfileArgumentsTest.php
new file mode 100644
index 00000000..f11312fc
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/ProfileArgumentsTest.php
@@ -0,0 +1,68 @@
+arguments = new ProfileArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithSearchModifier(): void
+ {
+ $this->arguments->search();
+
+ $this->assertSame(['SEARCH'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithAggregateModifier(): void
+ {
+ $this->arguments->aggregate();
+
+ $this->assertSame(['AGGREGATE'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithLimitedModifier(): void
+ {
+ $this->arguments->limited();
+
+ $this->assertSame(['LIMITED'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithQueryModifier(): void
+ {
+ $this->arguments->query('query');
+
+ $this->assertSame(['QUERY', 'query'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php
new file mode 100644
index 00000000..155c77e5
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php
@@ -0,0 +1,57 @@
+assertSame($expectedSchema, (new GeoField(...$arguments))->toArray());
+ }
+
+ public function geoFieldsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['field_name'],
+ ['field_name', 'GEO'],
+ ],
+ 'with alias' => [
+ ['field_name', 'fn'],
+ ['field_name', 'AS', 'fn', 'GEO'],
+ ],
+ 'with sortable - no UNF' => [
+ ['field_name', '', AbstractField::SORTABLE],
+ ['field_name', 'GEO', 'SORTABLE'],
+ ],
+ 'with sortable - with UNF' => [
+ ['field_name', '', AbstractField::SORTABLE_UNF],
+ ['field_name', 'GEO', 'SORTABLE', 'UNF'],
+ ],
+ 'with NOINDEX modifier' => [
+ ['field_name', '', AbstractField::NOT_SORTABLE, true],
+ ['field_name', 'GEO', 'NOINDEX'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php
new file mode 100644
index 00000000..14078e1f
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php
@@ -0,0 +1,57 @@
+assertSame($expectedSchema, (new NumericField(...$arguments))->toArray());
+ }
+
+ public function numericFieldsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['field_name'],
+ ['field_name', 'NUMERIC'],
+ ],
+ 'with alias' => [
+ ['field_name', 'fn'],
+ ['field_name', 'AS', 'fn', 'NUMERIC'],
+ ],
+ 'with sortable - no UNF' => [
+ ['field_name', '', AbstractField::SORTABLE],
+ ['field_name', 'NUMERIC', 'SORTABLE'],
+ ],
+ 'with sortable - with UNF' => [
+ ['field_name', '', AbstractField::SORTABLE_UNF],
+ ['field_name', 'NUMERIC', 'SORTABLE', 'UNF'],
+ ],
+ 'with NOINDEX modifier' => [
+ ['field_name', '', AbstractField::NOT_SORTABLE, true],
+ ['field_name', 'NUMERIC', 'NOINDEX'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php
new file mode 100644
index 00000000..34420882
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php
@@ -0,0 +1,57 @@
+assertSame($expectedSchema, (new TagField(...$arguments))->toArray());
+ }
+
+ public function tagFieldsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['field_name'],
+ ['field_name', 'TAG'],
+ ],
+ 'with alias' => [
+ ['field_name', 'fn'],
+ ['field_name', 'AS', 'fn', 'TAG'],
+ ],
+ 'with sortable - no UNF' => [
+ ['field_name', '', AbstractField::SORTABLE],
+ ['field_name', 'TAG', 'SORTABLE'],
+ ],
+ 'with sortable - with UNF' => [
+ ['field_name', '', AbstractField::SORTABLE_UNF],
+ ['field_name', 'TAG', 'SORTABLE', 'UNF'],
+ ],
+ 'with NOINDEX modifier' => [
+ ['field_name', '', AbstractField::NOT_SORTABLE, true],
+ ['field_name', 'TAG', 'NOINDEX'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php
new file mode 100644
index 00000000..03b9a468
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php
@@ -0,0 +1,57 @@
+assertSame($expectedSchema, (new TextField(...$arguments))->toArray());
+ }
+
+ public function textFieldsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['field_name'],
+ ['field_name', 'TEXT'],
+ ],
+ 'with alias' => [
+ ['field_name', 'fn'],
+ ['field_name', 'AS', 'fn', 'TEXT'],
+ ],
+ 'with sortable - no UNF' => [
+ ['field_name', '', AbstractField::SORTABLE],
+ ['field_name', 'TEXT', 'SORTABLE'],
+ ],
+ 'with sortable - with UNF' => [
+ ['field_name', '', AbstractField::SORTABLE_UNF],
+ ['field_name', 'TEXT', 'SORTABLE', 'UNF'],
+ ],
+ 'with NOINDEX modifier' => [
+ ['field_name', '', AbstractField::NOT_SORTABLE, true],
+ ['field_name', 'TEXT', 'NOINDEX'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/VectorFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/VectorFieldTest.php
new file mode 100644
index 00000000..64116b7b
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SchemaFields/VectorFieldTest.php
@@ -0,0 +1,28 @@
+assertSame(
+ ['field_name', 'AS', 'field', 'VECTOR', 'FLAT', 2, 'attribute_name', 'attribute_value'],
+ (new VectorField('field_name', 'FLAT', ['attribute_name', 'attribute_value'], 'field'))->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SearchArgumentsTest.php b/tests/Predis/Command/Argument/Search/SearchArgumentsTest.php
new file mode 100644
index 00000000..9ba8aee2
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SearchArgumentsTest.php
@@ -0,0 +1,297 @@
+arguments = new SearchArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithNoContentModifier(): void
+ {
+ $this->arguments->noContent();
+
+ $this->assertSame(['NOCONTENT'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithWithSortKeysModifier(): void
+ {
+ $this->arguments->withSortKeys();
+
+ $this->assertSame(['WITHSORTKEYS'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithSearchFilterModifier(): void
+ {
+ $this->arguments->searchFilter(['numeric_field', 1, 10], ['numeric_field1', 2, 5]);
+
+ $this->assertSame(
+ ['FILTER', 'numeric_field', 1, 10, 'FILTER', 'numeric_field1', 2, 5],
+ $this->arguments->toArray()
+ );
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithGeoFilterModifier(): void
+ {
+ $this->arguments->geoFilter(
+ ['geo_field', 13.2321, 14.2321, 300, 'km'],
+ ['geo_field1', 15.231, 16.234, 210, 'km']
+ );
+
+ $this->assertSame(
+ ['GEOFILTER', 'geo_field', 13.2321, 14.2321, 300, 'km', 'GEOFILTER', 'geo_field1', 15.231, 16.234, 210, 'km'],
+ $this->arguments->toArray()
+ );
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithInKeysModifier(): void
+ {
+ $this->arguments->inKeys(['key1', 'key2']);
+
+ $this->assertSame(['INKEYS', 2, 'key1', 'key2'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithInFieldsModifier(): void
+ {
+ $this->arguments->inFields(['field1', 'field2']);
+
+ $this->assertSame(['INFIELDS', 2, 'field1', 'field2'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithReturnModifier(): void
+ {
+ $this->arguments->addReturn(2, 'identifier', true, 'property', 'identifier2', 'identifier3');
+
+ $this->assertSame(
+ ['RETURN', 2, 'identifier', 'AS', 'property', 'identifier2', 'identifier3'],
+ $this->arguments->toArray()
+ );
+ }
+
+ /**
+ * @dataProvider summarizeProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithSummarizeModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->summarize(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider highlightProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithHighlightModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->highlight(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithSlopModifier(): void
+ {
+ $this->arguments->slop(2);
+
+ $this->assertSame(['SLOP', 2], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithInOrderModifier(): void
+ {
+ $this->arguments->inOrder();
+
+ $this->assertSame(['INORDER'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithExpanderModifier(): void
+ {
+ $this->arguments->expander('expander');
+
+ $this->assertSame(['EXPANDER', 'expander'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithScorerModifier(): void
+ {
+ $this->arguments->scorer('scorer');
+
+ $this->assertSame(['SCORER', 'scorer'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsExplainScoreModifier(): void
+ {
+ $this->arguments->explainScore();
+
+ $this->assertSame(['EXPLAINSCORE'], $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider sortByProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithSortByModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->sortBy(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnSortByWrongOrderByModifier(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Wrong order direction value given. Currently supports: ASC, DESC');
+
+ $this->arguments->sortBy('sort_attribute', 'wrong');
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesCorrectFTSearchArgumentsSetOnMethodsChainCall(): void
+ {
+ $this->arguments->withScores();
+ $this->arguments->withPayloads();
+ $this->arguments->searchFilter(['numeric_field', 1, 10]);
+ $this->arguments->addReturn(2, 'identifier', true, 'property');
+
+ $this->assertSame(
+ ['WITHSCORES', 'WITHPAYLOADS', 'FILTER', 'numeric_field', 1, 10, 'RETURN', 2, 'identifier', 'AS', 'property'],
+ $this->arguments->toArray()
+ );
+ }
+
+ public function sortByProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['sort_attribute'],
+ ['SORTBY', 'sort_attribute', 'ASC'],
+ ],
+ 'with DESC modifier' => [
+ ['sort_attribute', 'desc'],
+ ['SORTBY', 'sort_attribute', 'DESC'],
+ ],
+ ];
+ }
+
+ public function summarizeProvider(): array
+ {
+ return [
+ 'with no arguments' => [
+ [],
+ ['SUMMARIZE'],
+ ],
+ 'with fields only' => [
+ [['field1', 'field2']],
+ ['SUMMARIZE', 'FIELDS', 2, 'field1', 'field2'],
+ ],
+ 'with non-default FRAGS' => [
+ [['field1', 'field2'], 2],
+ ['SUMMARIZE', 'FIELDS', 2, 'field1', 'field2', 'FRAGS', 2],
+ ],
+ 'with non-default LEN' => [
+ [['field1', 'field2'], 0, 2],
+ ['SUMMARIZE', 'FIELDS', 2, 'field1', 'field2', 'LEN', 2],
+ ],
+ 'with non-default SEPARATOR' => [
+ [['field1', 'field2'], 0, 0, ','],
+ ['SUMMARIZE', 'FIELDS', 2, 'field1', 'field2', 'SEPARATOR', ','],
+ ],
+ 'with all arguments' => [
+ [['field1', 'field2'], 2, 2, ','],
+ ['SUMMARIZE', 'FIELDS', 2, 'field1', 'field2', 'FRAGS', 2, 'LEN', 2, 'SEPARATOR', ','],
+ ],
+ ];
+ }
+
+ public function highlightProvider(): array
+ {
+ return [
+ 'with no arguments' => [
+ [],
+ ['HIGHLIGHT'],
+ ],
+ 'with fields only' => [
+ [['field1', 'field2']],
+ ['HIGHLIGHT', 'FIELDS', 2, 'field1', 'field2'],
+ ],
+ 'with open tag only' => [
+ [['field1', 'field2'], 'openTag'],
+ ['HIGHLIGHT', 'FIELDS', 2, 'field1', 'field2'],
+ ],
+ 'with close tag only' => [
+ [['field1', 'field2'], '', 'closeTag'],
+ ['HIGHLIGHT', 'FIELDS', 2, 'field1', 'field2'],
+ ],
+ 'with both tags' => [
+ [['field1', 'field2'], 'openTag', 'closeTag'],
+ ['HIGHLIGHT', 'FIELDS', 2, 'field1', 'field2', 'TAGS', 'openTag', 'closeTag'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SpellcheckArgumentsTest.php b/tests/Predis/Command/Argument/Search/SpellcheckArgumentsTest.php
new file mode 100644
index 00000000..32a126cf
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SpellcheckArgumentsTest.php
@@ -0,0 +1,77 @@
+arguments = new SpellcheckArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithDistanceModifier(): void
+ {
+ $this->arguments->distance(2);
+
+ $this->assertSame(['DISTANCE', 2], $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider termsProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithTermsModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->terms(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnInvalidTermsModifierValue(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Wrong modifier value given. Currently supports: INCLUDE, EXCLUDE');
+
+ $this->arguments->terms('dict', 'wrong');
+ }
+
+ public function termsProvider(): array
+ {
+ return [
+ 'with INCLUDE modifier' => [
+ ['dict', 'INCLUDE', 'term1', 'term2'],
+ ['TERMS', 'INCLUDE', 'dict', 'term1', 'term2'],
+ ],
+ 'with EXCLUDE modifier' => [
+ ['dict', 'EXCLUDE', 'term1', 'term2'],
+ ['TERMS', 'EXCLUDE', 'dict', 'term1', 'term2'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SugAddArgumentsTest.php b/tests/Predis/Command/Argument/Search/SugAddArgumentsTest.php
new file mode 100644
index 00000000..e551b1fd
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SugAddArgumentsTest.php
@@ -0,0 +1,38 @@
+arguments = new SugAddArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithIncrModifier(): void
+ {
+ $this->arguments->incr();
+
+ $this->assertSame(['INCR'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/Search/SugGetArgumentsTest.php b/tests/Predis/Command/Argument/Search/SugGetArgumentsTest.php
new file mode 100644
index 00000000..3cbe502f
--- /dev/null
+++ b/tests/Predis/Command/Argument/Search/SugGetArgumentsTest.php
@@ -0,0 +1,48 @@
+arguments = new SugGetArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithFuzzyModifier(): void
+ {
+ $this->arguments->fuzzy();
+
+ $this->assertSame(['FUZZY'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithMaxModifier(): void
+ {
+ $this->arguments->max(5);
+
+ $this->assertSame(['MAX', 5], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/TimeSeries/AddArgumentsTest.php b/tests/Predis/Command/Argument/TimeSeries/AddArgumentsTest.php
new file mode 100644
index 00000000..eb1397ad
--- /dev/null
+++ b/tests/Predis/Command/Argument/TimeSeries/AddArgumentsTest.php
@@ -0,0 +1,38 @@
+arguments = new AddArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithOnDuplicateModifier(): void
+ {
+ $this->arguments->onDuplicate(CommonArguments::POLICY_LAST);
+
+ $this->assertSame(['ON_DUPLICATE', CommonArguments::POLICY_LAST], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/TimeSeries/CommonArgumentsTest.php b/tests/Predis/Command/Argument/TimeSeries/CommonArgumentsTest.php
new file mode 100644
index 00000000..529ff8ac
--- /dev/null
+++ b/tests/Predis/Command/Argument/TimeSeries/CommonArgumentsTest.php
@@ -0,0 +1,108 @@
+arguments = new CommonArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithRetentionModifier(): void
+ {
+ $this->arguments->retentionMsecs(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());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithEncodingModifier(): void
+ {
+ $this->arguments->encoding(CommonArguments::ENCODING_UNCOMPRESSED);
+
+ $this->assertSame(['ENCODING', CommonArguments::ENCODING_UNCOMPRESSED], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithLatestModifier(): void
+ {
+ $this->arguments->latest();
+
+ $this->assertSame(['LATEST'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithWithLabelsModifier(): void
+ {
+ $this->arguments->withLabels();
+
+ $this->assertSame(['WITHLABELS'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithSelectedLabelsModifier(): void
+ {
+ $this->arguments->selectedLabels('label1', 'label2');
+
+ $this->assertSame(['SELECTED_LABELS', 'label1', 'label2'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/TimeSeries/CreateArgumentsTest.php b/tests/Predis/Command/Argument/TimeSeries/CreateArgumentsTest.php
new file mode 100644
index 00000000..6d82570a
--- /dev/null
+++ b/tests/Predis/Command/Argument/TimeSeries/CreateArgumentsTest.php
@@ -0,0 +1,38 @@
+arguments = new CreateArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithEncodingModifier(): void
+ {
+ $this->arguments->encoding(CreateArguments::ENCODING_UNCOMPRESSED);
+
+ $this->assertSame(['ENCODING', CreateArguments::ENCODING_UNCOMPRESSED], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/TimeSeries/IncrByArgumentsTest.php b/tests/Predis/Command/Argument/TimeSeries/IncrByArgumentsTest.php
new file mode 100644
index 00000000..1f3ba321
--- /dev/null
+++ b/tests/Predis/Command/Argument/TimeSeries/IncrByArgumentsTest.php
@@ -0,0 +1,48 @@
+arguments = new IncrByArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithTimestampModifier(): void
+ {
+ $this->arguments->timestamp('*');
+
+ $this->assertSame(['TIMESTAMP', '*'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithUncompressedModifier(): void
+ {
+ $this->arguments->uncompressed();
+
+ $this->assertSame(['UNCOMPRESSED'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/TimeSeries/InfoArgumentsTest.php b/tests/Predis/Command/Argument/TimeSeries/InfoArgumentsTest.php
new file mode 100644
index 00000000..f41eb10f
--- /dev/null
+++ b/tests/Predis/Command/Argument/TimeSeries/InfoArgumentsTest.php
@@ -0,0 +1,38 @@
+arguments = new InfoArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithDebugModifier(): void
+ {
+ $this->arguments->debug();
+
+ $this->assertSame(['DEBUG'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/TimeSeries/MRangeArgumentsTest.php b/tests/Predis/Command/Argument/TimeSeries/MRangeArgumentsTest.php
new file mode 100644
index 00000000..13431213
--- /dev/null
+++ b/tests/Predis/Command/Argument/TimeSeries/MRangeArgumentsTest.php
@@ -0,0 +1,48 @@
+arguments = new MRangeArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithFilterModifier(): void
+ {
+ $this->arguments->filter('exp1', 'exp2');
+
+ $this->assertSame(['FILTER', 'exp1', 'exp2'], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithGroupByModifier(): void
+ {
+ $this->arguments->groupBy('label', 'reducer');
+
+ $this->assertSame(['GROUPBY', 'label', 'REDUCE', 'reducer'], $this->arguments->toArray());
+ }
+}
diff --git a/tests/Predis/Command/Argument/TimeSeries/RangeArgumentsTest.php b/tests/Predis/Command/Argument/TimeSeries/RangeArgumentsTest.php
new file mode 100644
index 00000000..5f36be38
--- /dev/null
+++ b/tests/Predis/Command/Argument/TimeSeries/RangeArgumentsTest.php
@@ -0,0 +1,97 @@
+arguments = new RangeArguments();
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithFilterByTsModifier(): void
+ {
+ $this->arguments->filterByTs(1000, 1001);
+
+ $this->assertSame(['FILTER_BY_TS', 1000, 1001], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithFilterByValueModifier(): void
+ {
+ $this->arguments->filterByValue(1000, 1001);
+
+ $this->assertSame(['FILTER_BY_VALUE', 1000, 1001], $this->arguments->toArray());
+ }
+
+ /**
+ * @return void
+ */
+ public function testCreatesArgumentsWithCountModifier(): void
+ {
+ $this->arguments->count(1000);
+
+ $this->assertSame(['COUNT', 1000], $this->arguments->toArray());
+ }
+
+ /**
+ * @dataProvider aggregatorProvider
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testCreatesArgumentsWithAggregatorModifier(array $arguments, array $expectedResponse): void
+ {
+ $this->arguments->aggregation(...$arguments);
+
+ $this->assertSame($expectedResponse, $this->arguments->toArray());
+ }
+
+ public function aggregatorProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['sum', 1000],
+ ['AGGREGATION', 'sum', 1000],
+ ],
+ 'with ALIGN modifier' => [
+ ['sum', 1000, 10],
+ ['ALIGN', 10, 'AGGREGATION', 'sum', 1000],
+ ],
+ 'with BUCKETTIMESTAMP modifier' => [
+ ['sum', 1000, 0, 10000],
+ ['AGGREGATION', 'sum', 1000, 'BUCKETTIMESTAMP', 10000],
+ ],
+ 'with EMPTY modifier' => [
+ ['sum', 1000, 0, 0, true],
+ ['AGGREGATION', 'sum', 1000, 'EMPTY'],
+ ],
+ 'with all arguments' => [
+ ['sum', 1000, 10, 10000, true],
+ ['ALIGN', 10, 'AGGREGATION', 'sum', 1000, 'BUCKETTIMESTAMP', 10000, 'EMPTY'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFADD_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFADD_Test.php
new file mode 100644
index 00000000..81bf9a90
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFADD_Test.php
@@ -0,0 +1,90 @@
+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
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testAddGivenItemIntoBloomFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->bfadd('key', 'item');
+ $this->assertSame(1, $actualResponse);
+ $this->assertSame(1, $redis->bfexists('key', 'item'));
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('bfadd_foo', 'bar');
+ $redis->bfadd('bfadd_foo', 'foo');
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFEXISTS_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFEXISTS_Test.php
new file mode 100644
index 00000000..52845332
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFEXISTS_Test.php
@@ -0,0 +1,75 @@
+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
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testExistsReturnsExistingItemWithinBloomFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->bfadd('key', 'item');
+
+ $this->assertSame(1, $redis->bfexists('key', 'item'));
+ $this->assertSame(0, $redis->bfexists('key', 'non-existing'));
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFINFO_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFINFO_Test.php
new file mode 100644
index 00000000..e83e17ce
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFINFO_Test.php
@@ -0,0 +1,226 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSame($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ * @dataProvider responsesProvider
+ */
+ public function testParseResponse(array $actualResponse, array $expectedResponse): void
+ {
+ $this->assertSame($expectedResponse, $this->getCommand()->parseResponse($actualResponse));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider filtersProvider
+ * @param array $filter
+ * @param string $key
+ * @param string $modifier
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion 1.0.0
+ */
+ public function testInfoReturnsCorrectInformationAboutBloomFilter(
+ array $filter,
+ string $key,
+ string $modifier,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->bfadd(...$filter);
+ $this->assertSame($expectedResponse, $redis->bfinfo($key, $modifier));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion 1.0.0
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Argument accepts only: capacity, size, filters, items, expansion values');
+
+ $redis->bfinfo('key', 'wrong');
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('binfo_foo', 'bar');
+ $redis->bfinfo('binfo_foo');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'without argument' => [
+ [],
+ [],
+ ],
+ 'with default modifier value' => [
+ ['key', ''],
+ ['key'],
+ ],
+ 'with CAPACITY modifier' => [
+ ['key', 'capacity'],
+ ['key', 'CAPACITY'],
+ ],
+ 'with SIZE modifier' => [
+ ['key', 'size'],
+ ['key', 'SIZE'],
+ ],
+ 'with FILTERS modifier' => [
+ ['key', 'filters'],
+ ['key', 'FILTERS'],
+ ],
+ 'with ITEMS modifier' => [
+ ['key', 'items'],
+ ['key', 'ITEMS'],
+ ],
+ 'with EXPANSION modifier' => [
+ ['key', 'expansion'],
+ ['key', 'EXPANSION'],
+ ],
+ ];
+ }
+
+ public function responsesProvider(): array
+ {
+ return [
+ 'with one modifier' => [
+ [100],
+ [100],
+ ],
+ 'with all modifiers' => [
+ [
+ 'Capacity',
+ 100,
+ 'Size',
+ 296,
+ 'Number of filters',
+ 1,
+ 'Number of items inserted',
+ 1,
+ 'Expansion rate',
+ 2,
+ ],
+ [
+ 'Capacity' => 100,
+ 'Size' => 296,
+ 'Number of filters' => 1,
+ 'Number of items inserted' => 1,
+ 'Expansion rate' => 2,
+ ],
+ ],
+ ];
+ }
+
+ public function filtersProvider(): array
+ {
+ return [
+ 'without modifier' => [
+ ['key', 'item'],
+ 'key',
+ '',
+ [
+ 'Capacity' => 100,
+ 'Size' => 240,
+ 'Number of filters' => 1,
+ 'Number of items inserted' => 1,
+ 'Expansion rate' => 2,
+ ],
+ ],
+ 'with CAPACITY modifier' => [
+ ['key', 'item'],
+ 'key',
+ 'capacity',
+ [100],
+ ],
+ 'with SIZE modifier' => [
+ ['key', 'item'],
+ 'key',
+ 'size',
+ [240],
+ ],
+ 'with FILTERS modifier' => [
+ ['key', 'item'],
+ 'key',
+ 'filters',
+ [1],
+ ],
+ 'with ITEMS modifier' => [
+ ['key', 'item'],
+ 'key',
+ 'items',
+ [1],
+ ],
+ 'with EXPANSION modifier' => [
+ ['key', 'item'],
+ 'key',
+ 'expansion',
+ [2],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFINSERT_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFINSERT_Test.php
new file mode 100644
index 00000000..a53bcc89
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFINSERT_Test.php
@@ -0,0 +1,272 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider filtersProvider
+ * @param array $arguments
+ * @param string $key
+ * @param string $modifier
+ * @param array $expectedInfo
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertCreatesBloomFilterWithGivenItems(
+ array $arguments,
+ string $key,
+ string $modifier,
+ array $expectedInfo,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->bfinsert(...$arguments);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame($expectedInfo, $redis->bfinfo($key, $modifier));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertThrowsExceptionOnNonExistingBloomFilterWithNoCreateModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR not found');
+
+ $redis->bfinsert('key', -1, -1, -1, true, false, 'item1');
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertAddItemOnlyOnExistingFilterWithNoCreateModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->bfadd('key', 'item1');
+ $actualResponse = $redis->bfinsert(
+ 'key',
+ -1,
+ -1,
+ -1,
+ false,
+ false,
+ 'item2'
+ );
+
+ $this->assertSame([1], $actualResponse);
+ $this->assertSame(
+ [
+ 'Capacity' => 100,
+ 'Size' => 240,
+ 'Number of filters' => 1,
+ 'Number of items inserted' => 2,
+ 'Expansion rate' => 2,
+ ],
+ $redis->bfinfo('key', '')
+ );
+ }
+
+ /**
+ * @group connected
+ * @dataProvider unexpectedValuesProvider
+ * @param array $arguments
+ * @param string $expectedExceptionMessage
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(
+ array $arguments,
+ string $expectedExceptionMessage
+ ): void {
+ $redis = $this->getClient();
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+
+ $redis->bfinsert(...$arguments);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', -1, -1, -1, false, false, 'item1'],
+ ['key', 'ITEMS', 'item1'],
+ ],
+ 'with CAPACITY modifier' => [
+ ['key', 100, -1, -1, false, false, 'item1'],
+ ['key', 'CAPACITY', 100, 'ITEMS', 'item1'],
+ ],
+ 'with ERROR modifier' => [
+ ['key', -1, 0.01, -1, false, false, 'item1'],
+ ['key', 'ERROR', 0.01, 'ITEMS', 'item1'],
+ ],
+ 'with EXPANSION modifier' => [
+ ['key', -1, -1, 2, false, false, 'item1'],
+ ['key', 'EXPANSION', 2, 'ITEMS', 'item1'],
+ ],
+ 'with NOCREATE modifier' => [
+ ['key', -1, -1, -1, true, false, 'item1'],
+ ['key', 'NOCREATE', 'ITEMS', 'item1'],
+ ],
+ 'with NONSCALING modifier' => [
+ ['key', -1, -1, -1, false, true, 'item1'],
+ ['key', 'NONSCALING', 'ITEMS', 'item1'],
+ ],
+ 'with all arguments' => [
+ ['key', 100, 0.01, 2, true, true, 'item1', 'item2'],
+ ['key', 'CAPACITY', 100, 'ERROR', 0.01, 'EXPANSION', 2, 'NOCREATE', 'NONSCALING', 'ITEMS', 'item1', 'item2'],
+ ],
+ ];
+ }
+
+ public function filtersProvider(): array
+ {
+ return [
+ 'with default filter' => [
+ ['key', -1, -1, -1, false, false, 'item1', 'item2'],
+ 'key',
+ '',
+ [
+ 'Capacity' => 100,
+ 'Size' => 240,
+ 'Number of filters' => 1,
+ 'Number of items inserted' => 2,
+ 'Expansion rate' => 2,
+ ],
+ [1, 1],
+ ],
+ 'with CAPACITY modifier' => [
+ ['key', 120, -1, -1, false, false, 'item1', 'item2'],
+ 'key',
+ 'capacity',
+ [120],
+ [1, 1],
+ ],
+ 'with ERROR modifier' => [
+ ['key', -1, 0.01, -1, false, false, 'item1', 'item2'],
+ 'key',
+ '',
+ [
+ 'Capacity' => 100,
+ 'Size' => 240,
+ 'Number of filters' => 1,
+ 'Number of items inserted' => 2,
+ 'Expansion rate' => 2,
+ ],
+ [1, 1],
+ ],
+ 'with EXPANSION modifier' => [
+ ['key', -1, -1, 3, false, false, 'item1', 'item2'],
+ 'key',
+ 'expansion',
+ [3],
+ [1, 1],
+ ],
+ 'with NONSCALING modifier' => [
+ ['key', -1, -1, -1, false, true, 'item1', 'item2'],
+ 'key',
+ 'expansion',
+ [null],
+ [1, 1],
+ ],
+ 'with all arguments' => [
+ ['key', 120, 0.01, 3, false, false, 'item1', 'item2'],
+ 'key',
+ '',
+ [
+ 'Capacity' => 120,
+ 'Size' => 264,
+ 'Number of filters' => 1,
+ 'Number of items inserted' => 2,
+ 'Expansion rate' => 3,
+ ],
+ [1, 1],
+ ],
+ ];
+ }
+
+ public function unexpectedValuesProvider(): array
+ {
+ return [
+ 'with wrong CAPACITY' => [
+ ['key', -5, -1, -1, false, false, 'item1', 'item2'],
+ 'Wrong capacity argument value or position offset',
+ ],
+ 'with wrong ERROR' => [
+ ['key', -1, -5, -1, false, false, 'item1', 'item2'],
+ 'Wrong error argument value or position offset',
+ ],
+ 'with wrong EXPANSION' => [
+ ['key', -1, -1, -5, false, false, 'item1', 'item2'],
+ 'Wrong expansion argument value or position offset',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFLOADCHUNK_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFLOADCHUNK_Test.php
new file mode 100644
index 00000000..4e443aa1
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFLOADCHUNK_Test.php
@@ -0,0 +1,112 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testLoadChunkSuccessfullyRestoresBloomFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->bfadd('key', 'item1');
+
+ $chunks = [];
+ $iter = 0;
+
+ while (true) {
+ [$iter, $data] = $redis->bfscandump('key', $iter);
+
+ if ($iter === 0) {
+ break;
+ }
+
+ $chunks[] = [$iter, $data];
+ }
+
+ $redis->flushall();
+
+ foreach ($chunks as $chunk) {
+ [$iter, $data] = $chunk;
+ $actualResponse = $redis->bfloadchunk('key', $iter, $data);
+
+ $this->assertEquals('OK', $actualResponse);
+ }
+
+ $this->assertSame(1, $redis->bfexists('key', 'item1'));
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('bfloadchunk_foo', 'bar');
+ $redis->bfloadchunk('bfloadchunk_foo', 0, 'data');
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFMADD_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFMADD_Test.php
new file mode 100644
index 00000000..5b0b4968
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFMADD_Test.php
@@ -0,0 +1,90 @@
+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
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testAddGivenItemsIntoBloomFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->bfmadd('key', 'item1', 'item2');
+ $this->assertSame([1, 1], $actualResponse);
+ $this->assertSame([1, 1], $redis->bfmexists('key', 'item1', 'item2'));
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('bfmadd_foo', 'bar');
+ $redis->bfmadd('bfmadd_foo', 'foo');
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFMEXISTS_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFMEXISTS_Test.php
new file mode 100644
index 00000000..df151381
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFMEXISTS_Test.php
@@ -0,0 +1,73 @@
+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
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testExistsReturnsExistingItemsWithinBloomFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->bfmadd('key', 'item1', 'item2');
+ $this->assertSame([1, 1], $redis->bfmexists('key', 'item1', 'item2'));
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFRESERVE_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFRESERVE_Test.php
new file mode 100644
index 00000000..1ac80529
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFRESERVE_Test.php
@@ -0,0 +1,159 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider filtersProvider
+ * @param array $filter
+ * @param string $key
+ * @param string $modifier
+ * @param array $expectedModification
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testReserveCreatesBloomFilterWithCorrectConfiguration(
+ array $filter,
+ string $key,
+ string $modifier,
+ array $expectedModification
+ ): void {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->bfreserve(...$filter);
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame($expectedModification, $redis->bfinfo($key, $modifier));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion 1.0.0
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong expansion argument value or position offset');
+
+ $redis->bfreserve('key', 0.01, 2, 0);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('bfreserve_foo', 'bar');
+ $redis->bfreserve('bfreserve_foo', 0.01, 2);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 0.01, 2],
+ ['key', 0.01, 2],
+ ],
+ 'with EXPANSION argument' => [
+ ['key', 0.01, 2, 2],
+ ['key', 0.01, 2, 'EXPANSION', 2],
+ ],
+ 'with NONSCALING modifier' => [
+ ['key', 0.01, 2, -1, true],
+ ['key', 0.01, 2, 'NONSCALING'],
+ ],
+ 'with all arguments' => [
+ ['key', 0.01, 2, 2, true],
+ ['key', 0.01, 2, 'EXPANSION', 2, 'NONSCALING'],
+ ],
+ ];
+ }
+
+ public function filtersProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 0.01, 100],
+ 'key',
+ 'capacity',
+ [100],
+ ],
+ 'with modified expansion' => [
+ ['key', 0.01, 100, 2],
+ 'key',
+ 'expansion',
+ [2],
+ ],
+ 'with NONSCALING modifier' => [
+ ['key', 0.01, 100, -1, true],
+ 'key',
+ 'expansion',
+ [null],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/BloomFilter/BFSCANDUMP_Test.php b/tests/Predis/Command/Redis/BloomFilter/BFSCANDUMP_Test.php
new file mode 100644
index 00000000..78f4a9ec
--- /dev/null
+++ b/tests/Predis/Command/Redis/BloomFilter/BFSCANDUMP_Test.php
@@ -0,0 +1,93 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testScanDumpReturnsCorrectDataChunk(): void
+ {
+ $expectedIterator = 1;
+ $redis = $this->getClient();
+
+ $redis->bfadd('key', 'item1');
+ [$iterator, $dataChunk] = $redis->bfscandump('key', 0);
+
+ $this->assertSame($expectedIterator, $iterator);
+ $this->assertNotEmpty($dataChunk);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('bfscandump_foo', 'bar');
+ $redis->bfscandump('bfscandump_foo', 0);
+ }
+}
diff --git a/tests/Predis/Command/Redis/Container/ContainerFactoryTest.php b/tests/Predis/Command/Redis/Container/ContainerFactoryTest.php
index cf6d28c4..f98901e1 100644
--- a/tests/Predis/Command/Redis/Container/ContainerFactoryTest.php
+++ b/tests/Predis/Command/Redis/Container/ContainerFactoryTest.php
@@ -12,14 +12,16 @@
namespace Predis\Command\Redis\Container;
+use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Predis\ClientInterface;
+use Predis\Command\Redis\Container\Search\FTCONFIG;
use UnexpectedValueException;
class ContainerFactoryTest extends TestCase
{
/**
- * @var \PHPUnit\Framework\MockObject\MockObject|ClientInterface
+ * @var MockObject|ClientInterface
*/
private $mockClient;
@@ -28,37 +30,59 @@ class ContainerFactoryTest extends TestCase
*/
private $factory;
- /**
- * @var ContainerInterface
- */
- private $expectedContainer;
-
protected function setUp(): void
{
$this->mockClient = $this->getMockBuilder(ClientInterface::class)->getMock();
- $this->expectedContainer = new FunctionContainer($this->mockClient);
$this->factory = new ContainerFactory();
}
/**
+ * @dataProvider containerProvider
+ * @param string $containerCommandId
+ * @param string $expectedContainerClass
* @return void
*/
- public function testCreatesReturnsExistingCommandContainerClass(): void
- {
+ public function testCreatesReturnsExistingCommandContainerClass(
+ string $containerCommandId,
+ string $expectedContainerClass
+ ): void {
+ $expectedContainer = new $expectedContainerClass($this->mockClient);
+
$this->assertEquals(
- $this->expectedContainer,
- $this->factory::create($this->mockClient, 'function')
+ $expectedContainer,
+ $this->factory::create($this->mockClient, $containerCommandId)
);
}
/**
+ * @dataProvider unexpectedValuesProvider
+ * @param string $containerCommandId
+ * @param string $expectedExceptionMessage
* @return void
*/
- public function testThrowsExceptionOnNonExistingCommand(): void
- {
+ public function testThrowsExceptionOnNonExistingCommand(
+ string $containerCommandId,
+ string $expectedExceptionMessage
+ ): void {
$this->expectException(UnexpectedValueException::class);
- $this->expectExceptionMessage('Given command is not supported.');
+ $this->expectExceptionMessage($expectedExceptionMessage);
- $this->factory::create($this->mockClient, 'foobar');
+ $this->factory::create($this->mockClient, $containerCommandId);
+ }
+
+ public function containerProvider(): array
+ {
+ return [
+ 'core command' => ['function', FunctionContainer::class],
+ 'module command' => ['ftconfig', FTCONFIG::class],
+ ];
+ }
+
+ public function unexpectedValuesProvider(): array
+ {
+ return [
+ 'not supported module container command' => ['ftfoobar', 'Given module container command is not supported.'],
+ 'not supported core container command' => ['foobar', 'Given container command is not supported.'],
+ ];
}
}
diff --git a/tests/Predis/Command/Redis/CountMinSketch/CMSINCRBY_Test.php b/tests/Predis/Command/Redis/CountMinSketch/CMSINCRBY_Test.php
new file mode 100644
index 00000000..d58779f1
--- /dev/null
+++ b/tests/Predis/Command/Redis/CountMinSketch/CMSINCRBY_Test.php
@@ -0,0 +1,121 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider sketchesProvider
+ * @param array $incrementArguments
+ * @param array $queryArguments
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testIncrementGivenItemsWithinCountMinSketch(
+ array $incrementArguments,
+ array $queryArguments,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->cmsinitbydim('key', 2000, 7);
+
+ $actualResponse = $redis->cmsincrby(...$incrementArguments);
+ $queryResponse = $redis->cmsquery(...$queryArguments);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame($expectedResponse, $queryResponse);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: key does not exist');
+
+ $redis = $this->getClient();
+
+ $redis->cmsincrby('cmsincrby_foo', 'item1', 1);
+ }
+
+ public function sketchesProvider(): array
+ {
+ return [
+ 'with single item' => [
+ ['key', 'item1', 1],
+ ['key', 'item1'],
+ [1],
+ ],
+ 'with multiple items' => [
+ ['key', 'item1', 1, 'item2', 1],
+ ['key', 'item1', 'item2'],
+ [1, 1],
+ ],
+ 'with incrementing on X' => [
+ ['key', 'item1', 2, 'item2', 5],
+ ['key', 'item1', 'item2'],
+ [2, 5],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/CountMinSketch/CMSINFO_Test.php b/tests/Predis/Command/Redis/CountMinSketch/CMSINFO_Test.php
new file mode 100644
index 00000000..3f44471f
--- /dev/null
+++ b/tests/Predis/Command/Redis/CountMinSketch/CMSINFO_Test.php
@@ -0,0 +1,95 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $actualResponse = ['width', 2000, 'depth', 10, 'count', 0];
+ $expectedResponse = ['width' => 2000, 'depth' => 10, 'count' => 0];
+
+ $this->assertSame($expectedResponse, $this->getCommand()->parseResponse($actualResponse));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testReturnsInfoAboutGivenCountMinSketch(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = ['width' => 2000, 'depth' => 10, 'count' => 0];
+
+ $redis->cmsinitbydim('key', 2000, 10);
+
+ $actualResponse = $redis->cmsinfo('key');
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: key does not exist');
+
+ $redis = $this->getClient();
+
+ $redis->cmsinfo('cmsinfo_foo');
+ }
+}
diff --git a/tests/Predis/Command/Redis/CountMinSketch/CMSINITBYDIM_Test.php b/tests/Predis/Command/Redis/CountMinSketch/CMSINITBYDIM_Test.php
new file mode 100644
index 00000000..4449ba62
--- /dev/null
+++ b/tests/Predis/Command/Redis/CountMinSketch/CMSINITBYDIM_Test.php
@@ -0,0 +1,93 @@
+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
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testInitializeCountMinSketchWithGivenConfiguration(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->cmsinitbydim('key', 2000, 10);
+ $info = $redis->cmsinfo('key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame(2000, $info['width']);
+ $this->assertSame(10, $info['depth']);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnAlreadyExistingKey(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: key already exists');
+
+ $redis = $this->getClient();
+
+ $redis->set('cmsinitbydim_foo', 'bar');
+ $redis->cmsinitbydim('cmsinitbydim_foo', 2000, 10);
+ }
+}
diff --git a/tests/Predis/Command/Redis/CountMinSketch/CMSINITBYPROB_Test.php b/tests/Predis/Command/Redis/CountMinSketch/CMSINITBYPROB_Test.php
new file mode 100644
index 00000000..f280456f
--- /dev/null
+++ b/tests/Predis/Command/Redis/CountMinSketch/CMSINITBYPROB_Test.php
@@ -0,0 +1,93 @@
+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
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testInitializeCountMinSketchWithDesiredErrorRateAndProbability(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->cmsinitbyprob('key', 0.001, 0.01);
+ $info = $redis->cmsinfo('key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame(2000, $info['width']);
+ $this->assertSame(7, $info['depth']);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnAlreadyExistingKey(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: key already exists');
+
+ $redis = $this->getClient();
+
+ $redis->set('cmsinitbydim_foo', 'bar');
+ $redis->cmsinitbyprob('cmsinitbydim_foo', 0.001, 0.01);
+ }
+}
diff --git a/tests/Predis/Command/Redis/CountMinSketch/CMSMERGE_Test.php b/tests/Predis/Command/Redis/CountMinSketch/CMSMERGE_Test.php
new file mode 100644
index 00000000..42eb687c
--- /dev/null
+++ b/tests/Predis/Command/Redis/CountMinSketch/CMSMERGE_Test.php
@@ -0,0 +1,180 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSame($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider sketchesProvider
+ * @param array $mergeArguments
+ * @param string $destinationKey
+ * @param array $items
+ * @param array $expectedCounts
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testMergeSketchesAndSaveWithinDestinationCountMinSketch(
+ array $mergeArguments,
+ string $destinationKey,
+ array $items,
+ array $expectedCounts
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->cmsinitbyprob('source1', 0.001, 0.01);
+ $redis->cmsinitbyprob('source2', 0.001, 0.01);
+ $redis->cmsinitbyprob('destination', 0.001, 0.01);
+ $redis->cmsincrby('source1', 'item1', 1, 'item2', 1);
+ $redis->cmsincrby('source2', 'item1', 1, 'item2', 1);
+
+ $actualResponse = $redis->cmsmerge(...$mergeArguments);
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame($expectedCounts, $redis->cmsquery('destination', ...$items));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingDestinationCountMinSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cmsinitbyprob('source1', 0.001, 0.01);
+ $redis->cmsinitbyprob('source2', 0.001, 0.01);
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: key does not exist');
+
+ $redis->cmsmerge('destination', ['source1', 'source2']);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingSourceCountMinSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cmsinitbyprob('source1', 0.001, 0.01);
+ $redis->cmsinitbyprob('destination', 0.001, 0.01);
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: key does not exist');
+
+ $redis->cmsmerge('destination', ['source1', 'source2']);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnDifferentCountMinSketchesConfigurations(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cmsinitbyprob('source1', 0.001, 0.01);
+ $redis->cmsinitbyprob('source2', 0.01, 0.01);
+ $redis->cmsinitbyprob('destination', 0.001, 0.01);
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: width/depth is not equal');
+
+ $redis->cmsmerge('destination', ['source1', 'source2']);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['destination', ['source']],
+ ['destination', 1, 'source'],
+ ],
+ 'with multiple sources' => [
+ ['destination', ['source1', 'source2']],
+ ['destination', 2, 'source1', 'source2'],
+ ],
+ 'with WEIGHTS' => [
+ ['destination', ['source1', 'source2'], [1, 3]],
+ ['destination', 2, 'source1', 'source2', 'WEIGHTS', 1, 3],
+ ],
+ ];
+ }
+
+ public function sketchesProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['destination', ['source1', 'source2']],
+ 'destination',
+ ['item1', 'item2'],
+ [2, 2],
+ ],
+ 'with modified WEIGHTS' => [
+ ['destination', ['source1', 'source2'], [2, 4]],
+ 'destination',
+ ['item1', 'item2'],
+ [6, 6],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/CountMinSketch/CMSQUERY_Test.php b/tests/Predis/Command/Redis/CountMinSketch/CMSQUERY_Test.php
new file mode 100644
index 00000000..f3335a61
--- /dev/null
+++ b/tests/Predis/Command/Redis/CountMinSketch/CMSQUERY_Test.php
@@ -0,0 +1,109 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider sketchesProvider
+ * @param array $queryArguments
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testReturnCountValuesForGivenItemsWithinCountMinSketch(
+ array $queryArguments,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->cmsinitbydim('key', 2000, 7);
+
+ $actualResponse = $redis->cmsquery(...$queryArguments);
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('CMS: key does not exist');
+
+ $redis = $this->getClient();
+
+ $redis->cmsquery('cmsquery_foo', 'item1');
+ }
+
+ public function sketchesProvider(): array
+ {
+ return [
+ 'with single item' => [
+ ['key', 'item1'],
+ [0],
+ ],
+ 'with multiple items' => [
+ ['key', 'item1', 'item2'],
+ [0, 0],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFADDNX_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFADDNX_Test.php
new file mode 100644
index 00000000..94a6960b
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFADDNX_Test.php
@@ -0,0 +1,94 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testAddItemToCuckooFilterWhenExists(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->cfaddnx('key', 'item');
+ $this->assertSame(1, $actualResponse);
+ $this->assertSame(1, $redis->cfexists('key', 'item'));
+
+ $actualResponse = $redis->cfaddnx('key', 'item');
+ $this->assertSame(0, $actualResponse);
+ $this->assertSame(1, $redis->cfexists('key', 'item'));
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('cfaddnx_foo', 'bar');
+ $redis->cfaddnx('cfaddnx_foo', 'foo');
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFADD_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFADD_Test.php
new file mode 100644
index 00000000..95f72990
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFADD_Test.php
@@ -0,0 +1,90 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testAddItemToCuckooFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->cfadd('key', 'item');
+ $this->assertSame(1, $actualResponse);
+ $this->assertSame(1, $redis->cfexists('key', 'item'));
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('cfadd_foo', 'bar');
+ $redis->cfadd('cfadd_foo', 'foo');
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFCOUNT_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFCOUNT_Test.php
new file mode 100644
index 00000000..76306df7
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFCOUNT_Test.php
@@ -0,0 +1,84 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testReturnsCountOfItemsWithinCuckooFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('key', 'item');
+
+ $singleItemResponse = $redis->cfcount('key', 'item');
+ $this->assertSame(1, $singleItemResponse);
+
+ $redis->cfadd('key', 'item');
+ $redis->cfadd('key', 'item');
+
+ $multipleItemsResponse = $redis->cfcount('key', 'item');
+ $this->assertSame(3, $multipleItemsResponse);
+
+ $nonExistingItemResponse = $redis->cfcount('non_existing_key', 'item');
+ $this->assertSame(0, $nonExistingItemResponse);
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFDEL_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFDEL_Test.php
new file mode 100644
index 00000000..70f2c098
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFDEL_Test.php
@@ -0,0 +1,102 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testDeletesItemFromGivenCuckooFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('key', 'item');
+ $singleItemResponse = $redis->cfdel('key', 'item');
+
+ $this->assertSame(1, $singleItemResponse);
+ $this->assertSame(0, $redis->cfexists('key', 'item'));
+
+ $redis->cfadd('key', 'item');
+ $redis->cfadd('key', 'item');
+
+ $multipleItemsResponse = $redis->cfdel('key', 'item');
+ $this->assertSame(1, $multipleItemsResponse);
+ $this->assertSame(1, $redis->cfexists('key', 'item'));
+
+ $nonExistingItemResponse = $redis->cfdel('key', 'non_existing_item');
+ $this->assertSame(0, $nonExistingItemResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testDeleteThrowsExceptionOnNonExistingFilterKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Not found');
+
+ $redis->cfdel('non_existing_key', 'item');
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFEXISTS_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFEXISTS_Test.php
new file mode 100644
index 00000000..78f6d021
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFEXISTS_Test.php
@@ -0,0 +1,75 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testExistsReturnsExistingItemWithinCuckooFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('key', 'item');
+
+ $this->assertSame(1, $redis->cfexists('key', 'item'));
+ $this->assertSame(0, $redis->cfexists('non-existing key', 'item'));
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFINFO_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFINFO_Test.php
new file mode 100644
index 00000000..72ab27f2
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFINFO_Test.php
@@ -0,0 +1,141 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ * @dataProvider responsesProvider
+ */
+ public function testParseResponse(array $actualResponse, array $expectedResponse): void
+ {
+ $this->assertSame($expectedResponse, $this->getCommand()->parseResponse($actualResponse));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInfoReturnsInformationAboutGivenCuckooFilter(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [
+ 'Size' => 1080,
+ 'Number of buckets' => 512,
+ 'Number of filters' => 1,
+ 'Number of items inserted' => 1,
+ 'Number of items deleted' => 0,
+ 'Bucket size' => 2,
+ 'Expansion rate' => 1,
+ 'Max iterations' => 20,
+ ];
+
+ $redis->cfadd('key', 'item');
+
+ $this->assertSame($expectedResponse, $redis->cfinfo('key'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInfoThrowsExceptionOnNonExistingFilterKeyGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR not found');
+
+ $redis->cfinfo('non_existing_key');
+ }
+
+ public function responsesProvider(): array
+ {
+ return [
+ 'with one modifier' => [
+ [100],
+ [100],
+ ],
+ 'with all modifiers' => [
+ [
+ 'Size',
+ 100,
+ 'Number of buckets',
+ 296,
+ 'Number of filter',
+ 1,
+ 'Number of items inserted',
+ 1,
+ 'Number of items deleted',
+ 1,
+ 'Bucket size',
+ 0,
+ 'Expansion rate',
+ 1,
+ 'Max iteration',
+ 20,
+ ],
+ [
+ 'Size' => 100,
+ 'Number of buckets' => 296,
+ 'Number of filter' => 1,
+ 'Number of items inserted' => 1,
+ 'Number of items deleted' => 1,
+ 'Bucket size' => 0,
+ 'Expansion rate' => 1,
+ 'Max iteration' => 20,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFINSERTNX_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFINSERTNX_Test.php
new file mode 100644
index 00000000..1f8a1da4
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFINSERTNX_Test.php
@@ -0,0 +1,168 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group connected
+ * @dataProvider filtersProvider
+ * @param array $filterArguments
+ * @param string $key
+ * @param int $expectedCapacity
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertItemsIntoNonExistingCuckooFilter(
+ array $filterArguments,
+ string $key,
+ int $expectedCapacity,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->cfinsertnx(...$filterArguments);
+ $info = $redis->cfinfo($key);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame($expectedCapacity, $info['Size']);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testDoNotInsertAlreadyExistingItems(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('filter', 'item1');
+ $redis->cfadd('filter', 'item2');
+
+ $actualResponse = $redis->cfinsertnx('filter', -1, false, 'item1', 'item2');
+ $this->assertSame([0, 0], $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertThrowsErrorOnInsertingIntoNonExistingFilterWithNoCreateModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR not found');
+
+ $redis->cfinsertnx('key', -1, true, 'item');
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertIntoAlreadyExistingFilterWithNoCreateModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('filter', 'item');
+
+ $actualResponse = $redis->cfinsertnx('filter', -1, true, 'item1');
+ $this->assertSame([1], $actualResponse);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', -1, false, 'item1'],
+ ['key', 'ITEMS', 'item1'],
+ ],
+ 'with CAPACITY modifier' => [
+ ['key', 500, false, 'item1'],
+ ['key', 'CAPACITY', 500, 'ITEMS', 'item1'],
+ ],
+ 'with NOCREATE modifier' => [
+ ['key', -1, true, 'item1'],
+ ['key', 'NOCREATE', 'ITEMS', 'item1'],
+ ],
+ 'with all arguments' => [
+ ['key', 500, true, 'item1', 'item2'],
+ ['key', 'CAPACITY', 500, 'NOCREATE', 'ITEMS', 'item1', 'item2'],
+ ],
+ ];
+ }
+
+ public function filtersProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', -1, false, 'item1'],
+ 'key',
+ 1080,
+ [1],
+ ],
+ 'with modified CAPACITY' => [
+ ['key', 500, false, 'item1'],
+ 'key',
+ 568,
+ [1],
+ ],
+ 'with multiple items' => [
+ ['key', -1, false, 'item1', 'item2'],
+ 'key',
+ 1080,
+ [1, 1],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php
new file mode 100644
index 00000000..90104060
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php
@@ -0,0 +1,186 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group connected
+ * @dataProvider filtersProvider
+ * @param array $filterArguments
+ * @param string $key
+ * @param int $expectedCapacity
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertItemsIntoGivenCuckooFilter(
+ array $filterArguments,
+ string $key,
+ int $expectedCapacity,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->cfinsert(...$filterArguments);
+ $info = $redis->cfinfo($key);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame($expectedCapacity, $info['Size']);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertIgnoresCapacityModifierOnAlreadyExistingFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('filter', 'item');
+
+ $actualResponse = $redis->cfinsert('filter', 500, false, 'item1');
+ $info = $redis->cfinfo('filter');
+
+ $this->assertSame([1], $actualResponse);
+ $this->assertSame(1080, $info['Size']);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertThrowsErrorOnInsertingIntoNonExistingFilterWithNoCreateModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR not found');
+
+ $redis->cfinsert('key', -1, true, 'item');
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testInsertIntoAlreadyExistingFilterWithNoCreateModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('filter', 'item');
+
+ $actualResponse = $redis->cfinsert('filter', -1, true, 'item1');
+ $this->assertSame([1], $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong NOCREATE argument type');
+
+ $redis->cfinsert('key', -1, 'wrong', 'item');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', -1, false, 'item1'],
+ ['key', 'ITEMS', 'item1'],
+ ],
+ 'with CAPACITY modifier' => [
+ ['key', 500, false, 'item1'],
+ ['key', 'CAPACITY', 500, 'ITEMS', 'item1'],
+ ],
+ 'with NOCREATE modifier' => [
+ ['key', -1, true, 'item1'],
+ ['key', 'NOCREATE', 'ITEMS', 'item1'],
+ ],
+ 'with all arguments' => [
+ ['key', 500, true, 'item1', 'item2'],
+ ['key', 'CAPACITY', 500, 'NOCREATE', 'ITEMS', 'item1', 'item2'],
+ ],
+ ];
+ }
+
+ public function filtersProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', -1, false, 'item1'],
+ 'key',
+ 1080,
+ [1],
+ ],
+ 'with modified CAPACITY' => [
+ ['key', 500, false, 'item1'],
+ 'key',
+ 568,
+ [1],
+ ],
+ 'with multiple items' => [
+ ['key', -1, false, 'item1', 'item2'],
+ 'key',
+ 1080,
+ [1, 1],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFLOADCHUNK_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFLOADCHUNK_Test.php
new file mode 100644
index 00000000..aaa738f9
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFLOADCHUNK_Test.php
@@ -0,0 +1,112 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testLoadChunkSuccessfullyRestoresCuckooFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->cfadd('key', 'item1');
+
+ $chunks = [];
+ $iter = 0;
+
+ while (true) {
+ [$iter, $data] = $redis->cfscandump('key', $iter);
+
+ if ($iter === 0) {
+ break;
+ }
+
+ $chunks[] = [$iter, $data];
+ }
+
+ $redis->flushall();
+
+ foreach ($chunks as $chunk) {
+ [$iter, $data] = $chunk;
+ $actualResponse = $redis->cfloadchunk('key', $iter, $data);
+
+ $this->assertEquals('OK', $actualResponse);
+ }
+
+ $this->assertSame(1, $redis->cfexists('key', 'item1'));
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Invalid position');
+
+ $redis = $this->getClient();
+
+ $redis->set('cfloadchunk_foo', 'bar');
+ $redis->cfloadchunk('cfloadchunk_foo', 0, 'data');
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFMEXISTS_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFMEXISTS_Test.php
new file mode 100644
index 00000000..a1b91ca0
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFMEXISTS_Test.php
@@ -0,0 +1,77 @@
+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
+ * @requiresRedisBfVersion >= 1.0
+ */
+ public function testExistsReturnsExistingItemsWithinCuckooFilter(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertSame([0, 0, 0], $redis->cfmexists('key', 'item1', 'item2', 'item3'));
+ $redis->cfadd('key', 'item1');
+ $this->assertSame([1, 0, 0], $redis->cfmexists('key', 'item1', 'item2', 'item3'));
+ $redis->cfadd('key', 'item2');
+ $redis->cfadd('key', 'item3');
+ $this->assertSame([1, 1, 1], $redis->cfmexists('key', 'item1', 'item2', 'item3'));
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFRESERVE_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFRESERVE_Test.php
new file mode 100644
index 00000000..eef7b021
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFRESERVE_Test.php
@@ -0,0 +1,164 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group connected
+ * @dataProvider filtersProvider
+ * @param array $filterArguments
+ * @param int $expectedCapacity
+ * @param int $expectedBucketSize
+ * @param int $expectedMaxIterations
+ * @param int $expectedExpansion
+ * @return void
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testReserveCreatesCuckooFilterWithCorrectConfiguration(
+ array $filterArguments,
+ int $expectedCapacity,
+ int $expectedBucketSize,
+ int $expectedMaxIterations,
+ int $expectedExpansion
+ ): void {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->cfreserve(...$filterArguments);
+ $this->assertEquals('OK', $actualResponse);
+
+ $info = $redis->cfinfo('key');
+
+ $this->assertSame($expectedCapacity, $info['Size']);
+ $this->assertSame($expectedBucketSize, $info['Bucket size']);
+ $this->assertSame($expectedMaxIterations, $info['Max iterations']);
+ $this->assertSame($expectedExpansion, $info['Expansion rate']);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('cfreserve_foo', 'bar');
+ $redis->cfreserve('cfreserve_foo', 500);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 500],
+ ['key', 500],
+ ],
+ 'with BUCKETSIZE argument' => [
+ ['key', 500, 2],
+ ['key', 500, 'BUCKETSIZE', 2],
+ ],
+ 'with MAXITERATIONS argument' => [
+ ['key', 500, -1, 15],
+ ['key', 500, 'MAXITERATIONS', 15],
+ ],
+ 'with EXPANSION argument' => [
+ ['key', 500, -1, -1, 3],
+ ['key', 500, 'EXPANSION', 3],
+ ],
+ 'with all arguments' => [
+ ['key', 500, 2, 15, 3],
+ ['key', 500, 'BUCKETSIZE', 2, 'MAXITERATIONS', 15, 'EXPANSION', 3],
+ ],
+ ];
+ }
+
+ public function filtersProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 500, -1, -1, -1],
+ 568,
+ 2,
+ 20,
+ 1,
+ ],
+ 'with modified bucket size' => [
+ ['key', 1000, 3, -1, -1],
+ 1592,
+ 3,
+ 20,
+ 1,
+ ],
+ 'with modified max iterations' => [
+ ['key', 1000, -1, 15, -1],
+ 1080,
+ 2,
+ 15,
+ 1,
+ ],
+ 'with modified expansion' => [
+ ['key', 1000, -1, -1, 3],
+ 1080,
+ 2,
+ 20,
+ 4,
+ ],
+ 'with all arguments' => [
+ ['key', 1000, 3, 15, 3],
+ 1592,
+ 3,
+ 15,
+ 4,
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFSCANDUMP_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFSCANDUMP_Test.php
new file mode 100644
index 00000000..1e6b5983
--- /dev/null
+++ b/tests/Predis/Command/Redis/CuckooFilter/CFSCANDUMP_Test.php
@@ -0,0 +1,93 @@
+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
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testScanDumpReturnsNotEmptyDataChunk(): void
+ {
+ $expectedIterator = 1;
+ $redis = $this->getClient();
+
+ $redis->cfadd('key', 'item1');
+ [$iterator, $dataChunk] = $redis->cfscandump('key', 0);
+
+ $this->assertSame($expectedIterator, $iterator);
+ $this->assertNotEmpty($dataChunk);
+ }
+
+ /**
+ * @group connected
+ * @requiresRedisBfVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongType(): void
+ {
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
+
+ $redis = $this->getClient();
+
+ $redis->set('cfscandump_foo', 'bar');
+ $redis->cfscandump('cfscandump_foo', 0);
+ }
+}
diff --git a/tests/Predis/Command/Redis/EXPIRE_Test.php b/tests/Predis/Command/Redis/EXPIRE_Test.php
index 2e849767..cfe0584d 100644
--- a/tests/Predis/Command/Redis/EXPIRE_Test.php
+++ b/tests/Predis/Command/Redis/EXPIRE_Test.php
@@ -143,14 +143,14 @@ class EXPIRE_Test extends PredisCommandTestCase
['noExpiry', 2, 'XX'],
],
'only if new expiry is greater then current one' => [
- ['newExpiryLower', 'value', 'EXAT', time() + 1000],
- ['newExpiryGreater', 'value', 'EXAT', time() + 10],
- ['newExpiryGreater', 20, 'GT'],
+ ['newExpiryLower', 'value', 'EX', 1000],
+ ['newExpiryGreater', 'value', 'EX', 10],
+ ['newExpiryGreater', 100, 'GT'],
['newExpiryLower', 20, 'GT'],
],
'only if new expiry is lower then current one' => [
- ['newExpiryLower', 'value', 'EXAT', time() + 1000],
- ['newExpiryGreater', 'value', 'EXAT', time() + 10],
+ ['newExpiryLower', 'value', 'EX', 1000],
+ ['newExpiryGreater', 'value', 'EX', 10],
['newExpiryLower', 20, 'LT'],
['newExpiryGreater', 20, 'LT'],
],
diff --git a/tests/Predis/Command/Redis/Json/JSONARRAPPEND_Test.php b/tests/Predis/Command/Redis/Json/JSONARRAPPEND_Test.php
new file mode 100644
index 00000000..67846355
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONARRAPPEND_Test.php
@@ -0,0 +1,136 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $values
+ * @param array $expectedArrayLength
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testAppendItemsToGivenJsonArray(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $values,
+ array $expectedArrayLength,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $actualResponse = $redis->jsonarrappend($key, $path, ...$values);
+
+ $this->assertSame($expectedArrayLength, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'append single item' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value2"]}'],
+ 'key',
+ '$.key2',
+ ['"value3"'],
+ [3],
+ '{"key1":"value1","key2":["value1","value2","value3"]}',
+ ],
+ 'append multiple items - same type' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value2"]}'],
+ 'key',
+ '$.key2',
+ ['"value3"', '"value4"'],
+ [4],
+ '{"key1":"value1","key2":["value1","value2","value3","value4"]}',
+ ],
+ 'append multiple items - different types' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value2"]}'],
+ 'key',
+ '$.key2',
+ ['"value3"', '5'],
+ [4],
+ '{"key1":"value1","key2":["value1","value2","value3",5]}',
+ ],
+ 'append on root and nested level' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]},"key2":["value1","value2"]}'],
+ 'key',
+ '$..key2',
+ ['"value3"', '5'],
+ [4, 4],
+ '{"key1":{"key2":["value1","value2","value3",5]},"key2":["value1","value2","value3",5]}',
+ ],
+ 'append on root and nested level - not array key' => [
+ ['key', '$', '{"key1":{"key2":"value2"},"key2":["value1","value2"]}'],
+ 'key',
+ '$..key2',
+ ['"value3"', '5'],
+ [4, null],
+ '{"key1":{"key2":"value2"},"key2":["value1","value2","value3",5]}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONARRINDEX_Test.php b/tests/Predis/Command/Redis/Json/JSONARRINDEX_Test.php
new file mode 100644
index 00000000..43e91ca4
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONARRINDEX_Test.php
@@ -0,0 +1,148 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param string $value
+ * @param int $start
+ * @param int $stop
+ * @param array $expectedIndices
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsCorrectJsonArrayIndex(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ string $value,
+ int $start,
+ int $stop,
+ array $expectedIndices
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $this->assertSame($expectedIndices, $redis->jsonarrindex($key, $path, $value, $start, $stop));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value2"]}'],
+ 'key',
+ '$.key2',
+ '"value2"',
+ 0,
+ 0,
+ [1],
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]}}'],
+ 'key',
+ '$..key2',
+ '"value2"',
+ 0,
+ 0,
+ [1],
+ ],
+ 'with both level matching keys' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]},"key2":["value2"]}'],
+ 'key',
+ '$..key2',
+ '"value2"',
+ 0,
+ 0,
+ [0, 1],
+ ],
+ 'with non-array path' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ '"value2"',
+ 0,
+ 0,
+ [null],
+ ],
+ 'not found - limit by start and stop' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]},"key2":["value2"]}'],
+ 'key',
+ '$..key2',
+ '"value2"',
+ 0,
+ 1,
+ [0, -1],
+ ],
+ 'not found - with wrong value' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]},"key2":["value2"]}'],
+ 'key',
+ '$..key2',
+ '"value3"',
+ 0,
+ 0,
+ [-1, -1],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONARRINSERT_Test.php b/tests/Predis/Command/Redis/Json/JSONARRINSERT_Test.php
new file mode 100644
index 00000000..5bb2c758
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONARRINSERT_Test.php
@@ -0,0 +1,143 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param int $index
+ * @param array $values
+ * @param array $expectedArrayLength
+ * @param string $expectedModifiedArray
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testInsertElementIntoJsonArrayBeforeGivenIndex(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ int $index,
+ array $values,
+ array $expectedArrayLength,
+ string $expectedModifiedArray
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $actualResponse = $redis->jsonarrinsert($key, $path, $index, ...$values);
+
+ $this->assertSame($expectedArrayLength, $actualResponse);
+ $this->assertSame($expectedModifiedArray, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value3"]}'],
+ 'key',
+ '$.key2',
+ 1,
+ ['"value2"'],
+ [3],
+ '{"key1":"value1","key2":["value1","value2","value3"]}',
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":{"key2":["value1","value3"]}}'],
+ 'key',
+ '$..key2',
+ 1,
+ ['"value2"'],
+ [3],
+ '{"key1":{"key2":["value1","value2","value3"]}}',
+ ],
+ 'with both levels matching keys' => [
+ ['key', '$', '{"key1":{"key2":["value1","value3"]},"key2":["value1","value3"]}'],
+ 'key',
+ '$..key2',
+ 1,
+ ['"value2"'],
+ [3, 3],
+ '{"key1":{"key2":["value1","value2","value3"]},"key2":["value1","value2","value3"]}',
+ ],
+ 'with multiple values inserted' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value4"]}'],
+ 'key',
+ '$.key2',
+ 1,
+ ['"value2"', '"value3"'],
+ [4],
+ '{"key1":"value1","key2":["value1","value2","value3","value4"]}',
+ ],
+ 'with non-array path' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ 1,
+ ['"value2"'],
+ [null],
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONARRLEN_Test.php b/tests/Predis/Command/Redis/Json/JSONARRLEN_Test.php
new file mode 100644
index 00000000..6565e347
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONARRLEN_Test.php
@@ -0,0 +1,113 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $expectedLength
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsCorrectJsonArrayLength(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $expectedLength
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertSame($expectedLength, $redis->jsonarrlen($key, $path));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value2"]}'],
+ 'key',
+ '$.key2',
+ [2],
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]}}'],
+ 'key',
+ '$..key2',
+ [2],
+ ],
+ 'with same keys on both levels' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]},"key2":["value1","value2","value3"]}'],
+ 'key',
+ '$..key2',
+ [3, 2],
+ ],
+ 'with non-array path' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ [null],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONARRPOP_Test.php b/tests/Predis/Command/Redis/Json/JSONARRPOP_Test.php
new file mode 100644
index 00000000..6d80fd99
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONARRPOP_Test.php
@@ -0,0 +1,127 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param int $index
+ * @param array $expectedPoppedElements
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testRemovesElementFromIndexOfJsonArray(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ int $index,
+ array $expectedPoppedElements,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $actualResponse = $redis->jsonarrpop($key, $path, $index);
+
+ $this->assertSame($expectedPoppedElements, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'removes last element' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value2"]}'],
+ 'key',
+ '$.key2',
+ -1,
+ ['"value2"'],
+ '{"key1":"value1","key2":["value1"]}',
+ ],
+ 'removes i-element' => [
+ ['key', '$', '{"key1":"value1","key2":["value1","value2"]}'],
+ 'key',
+ '$.key2',
+ 0,
+ ['"value1"'],
+ '{"key1":"value1","key2":["value2"]}',
+ ],
+ 'removes elements from root and nested levels' => [
+ ['key', '$', '{"key1":{"key2":["value1","value2"]},"key2":["value1","value2"]}'],
+ 'key',
+ '$..key2',
+ -1,
+ ['"value2"', '"value2"'],
+ '{"key1":{"key2":["value1"]},"key2":["value1"]}',
+ ],
+ 'removes element from non-array' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ -1,
+ [null],
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONARRTRIM_Test.php b/tests/Predis/Command/Redis/Json/JSONARRTRIM_Test.php
new file mode 100644
index 00000000..a40de6a0
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONARRTRIM_Test.php
@@ -0,0 +1,133 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param int $start
+ * @param int $stop
+ * @param array $expectedArrayLength
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testCorrectlyTrimGivenJsonArray(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ int $start,
+ int $stop,
+ array $expectedArrayLength,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $actualResponse = $redis->jsonarrtrim($key, $path, $start, $stop);
+
+ $this->assertSame($expectedArrayLength, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'trim array from start to stop' => [
+ ['key', '$', '{"key1":"value1","key2":[1,2,3,4,5,6]}'],
+ 'key',
+ '$.key2',
+ 1,
+ 4,
+ [4],
+ '{"key1":"value1","key2":[2,3,4,5]}',
+ ],
+ 'trim all values except first with 0 start and stop' => [
+ ['key', '$', '{"key1":"value1","key2":[1,2,3,4,5,6]}'],
+ 'key',
+ '$.key2',
+ 0,
+ 0,
+ [1],
+ '{"key1":"value1","key2":[1]}',
+ ],
+ 'trim arrays with same keys on root and nested levels' => [
+ ['key', '$', '{"key1":{"key2":[1,2,3,4,5,6]},"key2":[1,2,3,4,5,6]}'],
+ 'key',
+ '$..key2',
+ 1,
+ 4,
+ [4, 4],
+ '{"key1":{"key2":[2,3,4,5]},"key2":[2,3,4,5]}',
+ ],
+ 'do not trim on non-array key' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ 1,
+ 4,
+ [null],
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONCLEAR_Test.php b/tests/Predis/Command/Redis/Json/JSONCLEAR_Test.php
new file mode 100644
index 00000000..5e53cfa6
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONCLEAR_Test.php
@@ -0,0 +1,128 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param int $expectedClearValues
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 2.0.0
+ */
+ public function testClearValuesOnArraysAndObjects(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ int $expectedClearValues,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $actualResponse = $redis->jsonclear($key, $path);
+
+ $this->assertSame($expectedClearValues, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'with array values' => [
+ ['key', '$', '{"key1":"value1","key2":[1,2,3,4,5,6]}'],
+ 'key',
+ '$.key2',
+ 1,
+ '{"key1":"value1","key2":[]}',
+ ],
+ 'with json object' => [
+ ['key', '$', '{"key1":"value1","key2":{"key3":"value3"}}'],
+ 'key',
+ '$.key2',
+ 1,
+ '{"key1":"value1","key2":{}}',
+ ],
+ 'with numeric values' => [
+ ['key', '$', '{"key1":"value1","key2":1}'],
+ 'key',
+ '$.key2',
+ 1,
+ '{"key1":"value1","key2":0}',
+ ],
+ 'with all accepted values' => [
+ ['key', '$', '{"key1":1,"key2":{"key3":"value3"},"key3":[1,2,3]}'],
+ 'key',
+ '$.*',
+ 3,
+ '{"key1":0,"key2":{},"key3":[]}',
+ ],
+ 'with string and boolean value' => [
+ ['key', '$', '{"key1":"value1","key2":true}'],
+ 'key',
+ '$.*',
+ 0,
+ '{"key1":"value1","key2":true}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONDEBUG_Test.php b/tests/Predis/Command/Redis/Json/JSONDEBUG_Test.php
new file mode 100644
index 00000000..b13feec9
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONDEBUG_Test.php
@@ -0,0 +1,123 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testMemoryReturnsCorrectMemoryUsageAboutJson(
+ array $jsonArguments,
+ string $key,
+ string $path
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertGreaterThan(0, $redis->jsondebug->memory($key, $path));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testHelpReturnsInformationAboutContainerCommands(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->jsondebug->help();
+
+ $this->assertStringContainsString('MEMORY', $actualResponse[0]);
+ $this->assertStringContainsString('HELP', $actualResponse[1]);
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$',
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$..key2',
+ ],
+ 'with same keys on both levels' => [
+ ['key', '$', '{"key1":{"key2":"value2"},"key2":"value2"}'],
+ 'key',
+ '$..key2',
+ ],
+ 'with wrong key' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key1',
+ '$',
+ ],
+ 'with wrong path' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$.key3',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONDEL_Test.php b/tests/Predis/Command/Redis/Json/JSONDEL_Test.php
new file mode 100644
index 00000000..4d48d82f
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONDEL_Test.php
@@ -0,0 +1,121 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param int $expectedDeleteArgumentsCount
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testDeletesPathsAtKeyFromGivenJsonString(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ int $expectedDeleteArgumentsCount,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $actualResponse = $redis->jsondel($key, $path);
+
+ $this->assertSame($expectedDeleteArgumentsCount, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'without nested level' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ 1,
+ '{"key1":"value1"}',
+ ],
+ 'with nested level' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$..key2',
+ 1,
+ '{"key1":{}}',
+ ],
+ 'with nested level and same key on both levels' => [
+ ['key', '$', '{"key1":{"key2":"value2"},"key2":"value2"}'],
+ 'key',
+ '$..key2',
+ 2,
+ '{"key1":{}}',
+ ],
+ 'with wrong path' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key3',
+ 0,
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONFORGET_Test.php b/tests/Predis/Command/Redis/Json/JSONFORGET_Test.php
new file mode 100644
index 00000000..9e5ef474
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONFORGET_Test.php
@@ -0,0 +1,121 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param int $expectedDeleteArgumentsCount
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testDeletesPathsAtKeyFromGivenJsonString(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ int $expectedDeleteArgumentsCount,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $actualResponse = $redis->jsonforget($key, $path);
+
+ $this->assertSame($expectedDeleteArgumentsCount, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'without nested level' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ 1,
+ '{"key1":"value1"}',
+ ],
+ 'with nested level' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$..key2',
+ 1,
+ '{"key1":{}}',
+ ],
+ 'with nested level and same key on both levels' => [
+ ['key', '$', '{"key1":{"key2":"value2"},"key2":"value2"}'],
+ 'key',
+ '$..key2',
+ 2,
+ '{"key1":{}}',
+ ],
+ 'with wrong path' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key3',
+ 0,
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONGET_Test.php b/tests/Predis/Command/Redis/Json/JSONGET_Test.php
new file mode 100644
index 00000000..39c6cea8
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONGET_Test.php
@@ -0,0 +1,213 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonData
+ * @param string $key
+ * @param string $indent
+ * @param string $newline
+ * @param string $space
+ * @param string $expectedResponse
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsCorrectJsonResponse(
+ array $jsonData,
+ string $key,
+ string $indent,
+ string $newline,
+ string $space,
+ string $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonData);
+ $this->assertSame($expectedResponse, $redis->jsonget($key, $indent, $newline, $space));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsJsonValuesArrayOnMultiplePathsProvided(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->jsonset('key', '$', '{"key1":"value1","key2":{"key3":"value3"}}');
+ $actualResponse = $redis->jsonget('key', '', '', '', '$.key1', '$..key3');
+
+ $this->assertStringContainsString('"$.key1":["value1"]', $actualResponse);
+ $this->assertStringContainsString('"$..key3":["value3"]', $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @dataProvider unexpectedValuesProvider
+ * @param array $arguments
+ * @param string $expectedExceptionMessage
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(array $arguments, string $expectedExceptionMessage): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+
+ $redis->jsonget(...$arguments);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key'],
+ ['key'],
+ ],
+ 'with INDENT modifier' => [
+ ['key', '\t'],
+ ['key', 'INDENT', '\t'],
+ ],
+ 'with NEWLINE modifier' => [
+ ['key', '', '\n'],
+ ['key', 'NEWLINE', '\n'],
+ ],
+ 'with SPACE modifier' => [
+ ['key', '', '', ' '],
+ ['key', 'SPACE', ' '],
+ ],
+ 'with multiple paths' => [
+ ['key', '', '', '', 'path1', 'path2'],
+ ['key', 'path1', 'path2'],
+ ],
+ 'with all arguments' => [
+ ['key', '\t', '\n', ' '],
+ ['key', 'INDENT', '\t', 'NEWLINE', '\n', 'SPACE', ' '],
+ ],
+ ];
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'with key only' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '',
+ '',
+ '',
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ 'with INDENT modifier only' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '\t',
+ '',
+ '',
+ '{\t"key1":"value1",\t"key2":"value2"}',
+ ],
+ 'with NEWLINE modifier only' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '',
+ '\n',
+ '',
+ '{\n"key1":"value1",\n"key2":"value2"\n}',
+ ],
+ 'with SPACE modifier only' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '',
+ '',
+ ' ',
+ '{"key1": "value1","key2": "value2"}',
+ ],
+ 'with all modifiers' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '\t',
+ '\n',
+ ' ',
+ '{\n\t"key1": "value1",\n\t"key2": "value2"\n}',
+ ],
+ ];
+ }
+
+ public function unexpectedValuesProvider(): array
+ {
+ return [
+ 'with wrong INDENT modifier' => [
+ ['key', 1, '', ''],
+ 'Indent argument value should be a string',
+ ],
+ 'with wrong NEWLINE modifier' => [
+ ['key', '', 1, ''],
+ 'Newline argument value should be a string',
+ ],
+ 'with wrong SPACE modifier' => [
+ ['key', '', '', 1],
+ 'Space argument value should be a string',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONMGET_Test.php b/tests/Predis/Command/Redis/Json/JSONMGET_Test.php
new file mode 100644
index 00000000..5700bfee
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONMGET_Test.php
@@ -0,0 +1,120 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $firstJson
+ * @param array $secondJson
+ * @param array $keys
+ * @param string $path
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testMGetReturnsMultipleKeysArguments(
+ array $firstJson,
+ array $secondJson,
+ array $keys,
+ string $path,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$firstJson);
+ $redis->jsonset(...$secondJson);
+
+ $this->assertSame($expectedResponse, $redis->jsonmget($keys, $path));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'with both existing paths' => [
+ ['key1', '$', '{"key1":"value1","key2":"value2"}'],
+ ['key2', '$', '{"key1":"value3","key2":"value2"}'],
+ ['key1', 'key2'],
+ '$.key1',
+ ['["value1"]', '["value3"]'],
+ ],
+ 'with non-existing path' => [
+ ['key1', '$', '{"key1":"value1","key2":"value2"}'],
+ ['key2', '$', '{"key1":"value3","key3":"value3"}'],
+ ['key1', 'key2'],
+ '$.key3',
+ ['[]', '["value3"]'],
+ ],
+ 'with nested paths - different keys' => [
+ ['key1', '$', '{"key1":"value1","key2":{"key3":"value3"}}'],
+ ['key2', '$', '{"key1":"value3","key2":{"key3":"value4"}}'],
+ ['key1', 'key2'],
+ '$..key3',
+ ['["value3"]', '["value4"]'],
+ ],
+ 'with nested paths - similar keys' => [
+ ['key1', '$', '{"key1":"value1","key2":{"key1":"value3"}}'],
+ ['key2', '$', '{"key1":"value3","key2":{"key1":"value4"}}'],
+ ['key1', 'key2'],
+ '$..key1',
+ ['["value1","value3"]', '["value3","value4"]'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONNUMINCRBY_Test.php b/tests/Predis/Command/Redis/Json/JSONNUMINCRBY_Test.php
new file mode 100644
index 00000000..3dc87189
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONNUMINCRBY_Test.php
@@ -0,0 +1,119 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param int $value
+ * @param string $expectedIncrementedResponse
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testIncrementJsonNumericOnGivenValue(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ int $value,
+ string $expectedIncrementedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertSame($expectedIncrementedResponse, $redis->jsonnumincrby($key, $path, $value));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":1}'],
+ 'key',
+ '$.key2',
+ 1,
+ '[2]',
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":{"key2":5}}'],
+ 'key',
+ '$..key2',
+ 3,
+ '[8]',
+ ],
+ 'on both levels' => [
+ ['key', '$', '{"key1":{"key2":5},"key2":4}'],
+ 'key',
+ '$..key2',
+ 2,
+ '[6,7]',
+ ],
+ 'with non-numeric' => [
+ ['key', '$', '{"key1":{"key2":[1,2,3]}}'],
+ 'key',
+ '$..key2',
+ 2,
+ '[null]',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONOBJKEYS_Test.php b/tests/Predis/Command/Redis/Json/JSONOBJKEYS_Test.php
new file mode 100644
index 00000000..5726e9a1
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONOBJKEYS_Test.php
@@ -0,0 +1,113 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $expectedKeys
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsKeysForGivenJsonObject(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $expectedKeys
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertSame($expectedKeys, $redis->jsonobjkeys($key, $path));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$',
+ [['key1', 'key2']],
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":"value1","key2":{"key1":"value1","key2":"value2"}}'],
+ 'key',
+ '$.key2',
+ [['key1', 'key2']],
+ ],
+ 'with same key on both levels' => [
+ ['key', '$', '{"key1":{"key3":"value3","key4":"value4"},"key2":{"key1":{"key2":"value2"}}}'],
+ 'key',
+ '$..key1',
+ [['key3', 'key4'], ['key2']],
+ ],
+ 'with one of the keys not a JSON object' => [
+ ['key', '$', '{"key1":"value1","key2":{"key1":{"key2":"value2"}}}'],
+ 'key',
+ '$..key1',
+ [null, ['key2']],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONOBJLEN_Test.php b/tests/Predis/Command/Redis/Json/JSONOBJLEN_Test.php
new file mode 100644
index 00000000..0380bc93
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONOBJLEN_Test.php
@@ -0,0 +1,113 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $expectedObjectLength
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsLengthOfGivenJsonObject(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $expectedObjectLength
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertSame($expectedObjectLength, $redis->jsonobjlen($key, $path));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$',
+ [2],
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":"value1","key2":{"key1":"value1","key2":"value2"}}'],
+ 'key',
+ '$.key2',
+ [2],
+ ],
+ 'with same key on both levels' => [
+ ['key', '$', '{"key1":{"key3":"value3","key4":"value4"},"key2":{"key1":{"key2":"value2"}}}'],
+ 'key',
+ '$..key1',
+ [2, 1],
+ ],
+ 'with one of the keys not a JSON object' => [
+ ['key', '$', '{"key1":"value1","key2":{"key1":{"key2":"value2"}}}'],
+ 'key',
+ '$..key1',
+ [null, 1],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONRESP_Test.php b/tests/Predis/Command/Redis/Json/JSONRESP_Test.php
new file mode 100644
index 00000000..5fa15aaf
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONRESP_Test.php
@@ -0,0 +1,126 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnRespValueFromGivenJson(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertEquals($expectedResponse, $redis->jsonresp($key, $path));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'with string value' => [
+ ['key', '$', '{"key1":"value1"}'],
+ 'key',
+ '$.key1',
+ ['value1'],
+ ],
+ 'with numeric value' => [
+ ['key', '$', '{"key1":1}'],
+ 'key',
+ '$.key1',
+ [1],
+ ],
+ 'with array value' => [
+ ['key', '$', '{"key1":[1,2,3]}'],
+ 'key',
+ '$.key1',
+ [[new Status('['), 1, 2, 3]],
+ ],
+ 'with json object value' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$.key1',
+ [[new Status('{'), 'key2', 'value2']],
+ ],
+ 'with boolean value' => [
+ ['key', '$', '{"key1":true}'],
+ 'key',
+ '$.key1',
+ ['true'],
+ ],
+ 'with null value' => [
+ ['key', '$', '{"key1":null}'],
+ 'key',
+ '$.key1',
+ [null],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONSET_Test.php b/tests/Predis/Command/Redis/Json/JSONSET_Test.php
new file mode 100644
index 00000000..dc1715f8
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONSET_Test.php
@@ -0,0 +1,190 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSame($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param string $key
+ * @param string $defaultJson
+ * @param string $appendedJson
+ * @param string $path
+ * @param string|null $nxXxArgument
+ * @param string|null $expectedResponse
+ * @param string $expectedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testSetCorrectJsonValueAndReturnsCorrespondingResponse(
+ string $key,
+ string $defaultJson,
+ string $appendedJson,
+ string $path,
+ ?string $nxXxArgument,
+ ?string $expectedResponse,
+ string $expectedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $this->assertEquals('OK', $redis->jsonset($key, '$', $defaultJson));
+ $this->assertEquals($expectedResponse, $redis->jsonset($key, $path, $appendedJson, $nxXxArgument));
+ $this->assertSame($expectedJson, $redis->jsonget($key));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Argument accepts only: nx, xx values');
+
+ $redis->jsonset('key', '$', 'value', 'wrong');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 'path', 'value'],
+ ['key', 'path', 'value'],
+ ],
+ 'with NX argument' => [
+ ['key', 'path', 'value', 'nx'],
+ ['key', 'path', 'value', 'NX'],
+ ],
+ 'with XX argument' => [
+ ['key', 'path', 'value', 'xx'],
+ ['key', 'path', 'value', 'XX'],
+ ],
+ ];
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'override json' => [
+ 'key',
+ '{"key1":"value1","key2":"value2"}',
+ '{"key3":"value3"}',
+ '$',
+ null,
+ 'OK',
+ '{"key3":"value3"}',
+ ],
+ 'override certain key - without nxXx argument' => [
+ 'key',
+ '{"key1":"value1","key2":"value2"}',
+ '"value3"',
+ '$.key2',
+ null,
+ 'OK',
+ '{"key1":"value1","key2":"value3"}',
+ ],
+ 'append to json - without nxXx argument' => [
+ 'key',
+ '{"key1":"value1","key2":"value2"}',
+ '"value3"',
+ '$.key3',
+ null,
+ 'OK',
+ '{"key1":"value1","key2":"value2","key3":"value3"}',
+ ],
+ 'override certain key - with XX argument' => [
+ 'key',
+ '{"key1":"value1","key2":"value2"}',
+ '"value3"',
+ '$.key2',
+ 'xx',
+ 'OK',
+ '{"key1":"value1","key2":"value3"}',
+ ],
+ 'append to json - with NX argument' => [
+ 'key',
+ '{"key1":"value1","key2":"value2"}',
+ '"value3"',
+ '$.key3',
+ 'nx',
+ 'OK',
+ '{"key1":"value1","key2":"value2","key3":"value3"}',
+ ],
+ 'override failed with XX argument' => [
+ 'key',
+ '{"key1":"value1","key2":"value2"}',
+ '"value3"',
+ '$.key3',
+ 'xx',
+ null,
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ 'append failed with NX argument' => [
+ 'key',
+ '{"key1":"value1","key2":"value2"}',
+ '"value2"',
+ '$.key2',
+ 'nx',
+ null,
+ '{"key1":"value1","key2":"value2"}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONSTRAPPEND_Test.php b/tests/Predis/Command/Redis/Json/JSONSTRAPPEND_Test.php
new file mode 100644
index 00000000..3f2b1e32
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONSTRAPPEND_Test.php
@@ -0,0 +1,127 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param string $value
+ * @param array $expectedStringLength
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testAppendStringToExistingJsonString(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ string $value,
+ array $expectedStringLength,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $actualResponse = $redis->jsonstrappend($key, $path, $value);
+
+ $this->assertSame($expectedStringLength, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'appends to json string on root level' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ '"foo"',
+ [9],
+ '{"key1":"value1","key2":"value2foo"}',
+ ],
+ 'appends to json string on nested level' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$..key2',
+ '"foo"',
+ [9],
+ '{"key1":{"key2":"value2foo"}}',
+ ],
+ 'appends to json string on both levels' => [
+ ['key', '$', '{"key1":{"key2":"value2"},"key2":"value2"}'],
+ 'key',
+ '$..key2',
+ '"foo"',
+ [9, 9],
+ '{"key1":{"key2":"value2foo"},"key2":"value2foo"}',
+ ],
+ 'appends to non-json string' => [
+ ['key', '$', '{"key1":{"key2":[1,2,3]}}'],
+ 'key',
+ '$..key2',
+ '"foo"',
+ [null],
+ '{"key1":{"key2":[1,2,3]}}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONSTRLEN_Test.php b/tests/Predis/Command/Redis/Json/JSONSTRLEN_Test.php
new file mode 100644
index 00000000..4058d89a
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONSTRLEN_Test.php
@@ -0,0 +1,113 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $expectedStringLength
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsLengthOfGivenJsonString(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $expectedStringLength
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertSame($expectedStringLength, $redis->jsonstrlen($key, $path));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":"value1","key2":"value2"}'],
+ 'key',
+ '$.key2',
+ [6],
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$..key2',
+ [6],
+ ],
+ 'on both levels' => [
+ ['key', '$', '{"key1":{"key2":"value2"},"key2":"value2"}'],
+ 'key',
+ '$..key2',
+ [6, 6],
+ ],
+ 'with non-json string' => [
+ ['key', '$', '{"key1":{"key2":[1,2,3]}}'],
+ 'key',
+ '$..key2',
+ [null],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONTOGGLE_Test.php b/tests/Predis/Command/Redis/Json/JSONTOGGLE_Test.php
new file mode 100644
index 00000000..40dc8950
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONTOGGLE_Test.php
@@ -0,0 +1,128 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $expectedResponse
+ * @param string $expectedModifiedJson
+ * @return void
+ * @requiresRedisJsonVersion >= 2.0.0
+ */
+ public function testToggleChangesBooleanValueToOpposite(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $expectedResponse,
+ string $expectedModifiedJson
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+ $actualResponse = $redis->jsontoggle($key, $path);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame($expectedModifiedJson, $redis->jsonget($key));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'on root level' => [
+ ['key', '$', '{"key1":true}'],
+ 'key',
+ '$.key1',
+ [0],
+ '{"key1":false}',
+ ],
+ 'on nested level' => [
+ ['key', '$', '{"key1":{"key2":false}}'],
+ 'key',
+ '$..key2',
+ [1],
+ '{"key1":{"key2":true}}',
+ ],
+ 'with same keys on both levels' => [
+ ['key', '$', '{"key1":{"key2":false},"key2":true}'],
+ 'key',
+ '$..key2',
+ [0, 1],
+ '{"key1":{"key2":true},"key2":false}',
+ ],
+ 'on non-boolean value' => [
+ ['key', '$', '{"key1":"value1"}'],
+ 'key',
+ '$.key1',
+ [null],
+ '{"key1":"value1"}',
+ ],
+ 'on wrong path' => [
+ ['key', '$', '{"key1":"value1"}'],
+ 'key',
+ '$.key2',
+ [],
+ '{"key1":"value1"}',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Json/JSONTYPE_Test.php b/tests/Predis/Command/Redis/Json/JSONTYPE_Test.php
new file mode 100644
index 00000000..f40d4e13
--- /dev/null
+++ b/tests/Predis/Command/Redis/Json/JSONTYPE_Test.php
@@ -0,0 +1,125 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider jsonProvider
+ * @param array $jsonArguments
+ * @param string $key
+ * @param string $path
+ * @param array $expectedType
+ * @return void
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testReturnsJsonValueType(
+ array $jsonArguments,
+ string $key,
+ string $path,
+ array $expectedType
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->jsonset(...$jsonArguments);
+
+ $this->assertSame($expectedType, $redis->jsontype($key, $path));
+ }
+
+ public function jsonProvider(): array
+ {
+ return [
+ 'with string value' => [
+ ['key', '$', '{"key1":"value1"}'],
+ 'key',
+ '$.key1',
+ ['string'],
+ ],
+ 'with numeric value' => [
+ ['key', '$', '{"key1":1}'],
+ 'key',
+ '$.key1',
+ ['integer'],
+ ],
+ 'with array value' => [
+ ['key', '$', '{"key1":[1,2,3]}'],
+ 'key',
+ '$.key1',
+ ['array'],
+ ],
+ 'with json object value' => [
+ ['key', '$', '{"key1":{"key2":"value2"}}'],
+ 'key',
+ '$.key1',
+ ['object'],
+ ],
+ 'with boolean value' => [
+ ['key', '$', '{"key1":true}'],
+ 'key',
+ '$.key1',
+ ['boolean'],
+ ],
+ 'with null value' => [
+ ['key', '$', '{"key1":null}'],
+ 'key',
+ '$.key1',
+ ['null'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/MOVE_Test.php b/tests/Predis/Command/Redis/MOVE_Test.php
index 65c1838c..6c9966f6 100644
--- a/tests/Predis/Command/Redis/MOVE_Test.php
+++ b/tests/Predis/Command/Redis/MOVE_Test.php
@@ -66,7 +66,7 @@ class MOVE_Test extends PredisCommandTestCase
*/
public function testMovesKeysToDifferentDatabases(): void
{
- $db = REDIS_SERVER_DBNUM - 1;
+ $db = REDIS_SERVER_DBNUM + 1;
$redis = $this->getClient();
$redis->set('foo', 'bar');
diff --git a/tests/Predis/Command/Redis/SELECT_Test.php b/tests/Predis/Command/Redis/SELECT_Test.php
index ea78de6e..30d6bea8 100644
--- a/tests/Predis/Command/Redis/SELECT_Test.php
+++ b/tests/Predis/Command/Redis/SELECT_Test.php
@@ -65,7 +65,7 @@ class SELECT_Test extends PredisCommandTestCase
$redis->set('foo', 'bar');
- $this->assertEquals('OK', $redis->select(REDIS_SERVER_DBNUM - 1));
+ $this->assertEquals('OK', $redis->select(REDIS_SERVER_DBNUM + 1));
$this->assertSame(0, $redis->exists('foo'));
}
diff --git a/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php
new file mode 100644
index 00000000..190c37b6
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php
@@ -0,0 +1,203 @@
+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
+ * @requiresRediSearchVersion >= 1.1.0
+ */
+ public function testReturnsAggregatedSearchResultWithGivenModifiers(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [
+ 2,
+ [
+ 'country', 'Ukraine', 'birth', '1995', 'country_birth_Vlad_count', '2',
+ ],
+ [
+ 'country', 'Israel', 'birth', '1994', 'country_birth_Vlad_count', '1',
+ ],
+ ];
+
+ $ftCreateArguments = (new CreateArguments())->prefix(['user:']);
+ $schema = [
+ new TextField('name'),
+ new TextField('country'),
+ new NumericField('dob', '', AbstractField::SORTABLE),
+ ];
+
+ $this->assertEquals('OK', $redis->ftcreate('idx', $schema, $ftCreateArguments));
+ $this->assertSame(
+ 3,
+ $redis->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600)
+ );
+ $this->assertSame(
+ 3,
+ $redis->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600)
+ );
+ $this->assertSame(
+ 3,
+ $redis->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600)
+ );
+
+ $ftAggregateArguments = (new AggregateArguments())
+ ->apply('year(@dob)', 'birth')
+ ->groupBy('@country', '@birth')
+ ->reduce('COUNT', true, 'country_birth_Vlad_count')
+ ->sortBy(0, '@birth', 'DESC');
+
+ $this->assertSame(
+ $expectedResponse,
+ $redis->ftaggregate('idx', '@name: "Vlad"', $ftAggregateArguments)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.1.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('index: no such index');
+
+ $redis->ftaggregate('index', 'query');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['index', 'query'],
+ ['index', 'query'],
+ ],
+ 'with VERBATIM modifier' => [
+ ['index', 'query', (new AggregateArguments())->verbatim()],
+ ['index', 'query', 'VERBATIM'],
+ ],
+ 'with LOAD modifier - specified fields' => [
+ ['index', 'query', (new AggregateArguments())->load('field1', 'field2')],
+ ['index', 'query', 'LOAD', 2, 'field1', 'field2'],
+ ],
+ 'with LOAD modifier - all fields' => [
+ ['index', 'query', (new AggregateArguments())->load('*')],
+ ['index', 'query', 'LOAD', '*'],
+ ],
+ 'with TIMEOUT modifier' => [
+ ['index', 'query', (new AggregateArguments())->timeout(2)],
+ ['index', 'query', 'TIMEOUT', 2],
+ ],
+ 'with GROUPBY modifier' => [
+ ['index', 'query', (new AggregateArguments())->groupBy('property1', 'property2')],
+ ['index', 'query', 'GROUPBY', 2, 'property1', 'property2'],
+ ],
+ 'with REDUCE modifier' => [
+ ['index', 'query', (new AggregateArguments())->reduce('function', 'arg1', true, 'alias1', 'arg2')],
+ ['index', 'query', 'REDUCE', 'function', 2, 'arg1', 'AS', 'alias1', 'arg2'],
+ ],
+ 'with SORTBY modifier' => [
+ ['index', 'query', (new AggregateArguments())->sortBy(2, 'property1', 'ASC', 'property2', 'DESC')],
+ ['index', 'query', 'SORTBY', 2, 'property1', 'ASC', 'property2', 'DESC', 'MAX', 2],
+ ],
+ 'with APPLY modifier' => [
+ ['index', 'query', (new AggregateArguments())->apply('expression', 'name')],
+ ['index', 'query', 'APPLY', 'expression', 'AS', 'name'],
+ ],
+ 'with LIMIT modifier' => [
+ ['index', 'query', (new AggregateArguments())->limit(2, 3)],
+ ['index', 'query', 'LIMIT', 2, 3],
+ ],
+ 'with FILTER modifier' => [
+ ['index', 'query', (new AggregateArguments())->filter('filter')],
+ ['index', 'query', 'FILTER', 'filter'],
+ ],
+ 'with WITHCURSOR modifier' => [
+ ['index', 'query', (new AggregateArguments())->withCursor(10, 20)],
+ ['index', 'query', 'WITHCURSOR', 'COUNT', 10, 'MAXIDLE', 20],
+ ],
+ 'with PARAMS modifier' => [
+ ['index', 'query', (new AggregateArguments())->params(['name1', 'value1', 'name2', 'value2'])],
+ ['index', 'query', 'PARAMS', 4, 'name1', 'value1', 'name2', 'value2'],
+ ],
+ 'with DIALECT modifier' => [
+ ['index', 'query', (new AggregateArguments())->dialect('dialect')],
+ ['index', 'query', 'DIALECT', 'dialect'],
+ ],
+ 'with chain of arguments' => [
+ [
+ 'index',
+ '@name: "test"',
+ (new AggregateArguments())
+ ->apply('year(@dob)', 'birth')
+ ->groupBy('@birth', '@country')
+ ->reduce('COUNT', true, 'num_visits')
+ ->sortBy(0, '@day'),
+ ],
+ [
+ 'index', '@name: "test"', 'APPLY', 'year(@dob)', 'AS', 'birth', 'GROUPBY', 2, '@birth', '@country',
+ 'REDUCE', 'COUNT', 0, 'AS', 'num_visits', 'SORTBY', 1, '@day',
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTALIASADD_Test.php b/tests/Predis/Command/Redis/Search/FTALIASADD_Test.php
new file mode 100644
index 00000000..fb659acf
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTALIASADD_Test.php
@@ -0,0 +1,127 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testAddAliasToGivenIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = new CreateArguments();
+ $arguments->prefix(['prefix:']);
+ $arguments->language();
+
+ $schema = [new TextField('text_field')];
+
+ $createResponse = $redis->ftcreate('index', $schema, $arguments);
+ $this->assertEquals('OK', $createResponse);
+
+ $actualResponse = $redis->ftaliasadd('alias', 'index');
+ $this->assertEquals('OK', $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown index name (or name is an alias itself)');
+
+ $redis->ftaliasadd('alias', 'index');
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnAlreadyExistingAlias(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = new CreateArguments();
+ $arguments->prefix(['prefix:']);
+ $arguments->language();
+
+ $schema = [new TextField('text_field')];
+
+ $createResponse = $redis->ftcreate('index', $schema, $arguments);
+ $this->assertEquals('OK', $createResponse);
+
+ $actualResponse = $redis->ftaliasadd('alias', 'index');
+ $this->assertEquals('OK', $actualResponse);
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Alias already exists');
+
+ $redis->ftaliasadd('alias', 'index');
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTALIASDEL_Test.php b/tests/Predis/Command/Redis/Search/FTALIASDEL_Test.php
new file mode 100644
index 00000000..c448c799
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTALIASDEL_Test.php
@@ -0,0 +1,93 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testRemovesAliasFromGivenIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $schema = [new TextField('text_field')];
+
+ $this->assertEquals('OK', $redis->ftcreate('index', $schema));
+ $this->assertEquals('OK', $redis->ftaliasadd('alias', 'index'));
+ $this->assertEquals('OK', $redis->ftaliasdel('alias'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingAlias(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Alias does not exist');
+
+ $redis->ftaliasdel('alias');
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTALIASUPDATE_Test.php b/tests/Predis/Command/Redis/Search/FTALIASUPDATE_Test.php
new file mode 100644
index 00000000..e21531bb
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTALIASUPDATE_Test.php
@@ -0,0 +1,114 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testUpdateAliasAddAliasToGivenIndexIfAliasNotExists(): void
+ {
+ $redis = $this->getClient();
+
+ $schema = [new TextField('text_field')];
+
+ $createResponse = $redis->ftcreate('index', $schema);
+ $this->assertEquals('OK', $createResponse);
+
+ $actualResponse = $redis->ftaliasupdate('alias', 'index');
+ $this->assertEquals('OK', $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testUpdateRemovesAliasAssociationFromAlreadyExistingAlias(): void
+ {
+ $redis = $this->getClient();
+
+ $schema = [new TextField('text_field')];
+
+ $createResponse = $redis->ftcreate('index', $schema);
+ $this->assertEquals('OK', $createResponse);
+
+ $actualResponse = $redis->ftaliasadd('alias', 'index');
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertEquals('OK', $redis->ftaliasupdate('new_alias', 'index'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown index name (or name is an alias itself)');
+
+ $redis->ftaliasupdate('alias', 'index');
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTALTER_Test.php b/tests/Predis/Command/Redis/Search/FTALTER_Test.php
new file mode 100644
index 00000000..06e815e5
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTALTER_Test.php
@@ -0,0 +1,108 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testAddsAttributeToExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $schema = [new TextField('field_name')];
+
+ $this->assertEquals('OK', $redis->ftcreate('index', $schema));
+
+ $schema = [new TextField('new_field_name')];
+
+ $this->assertEquals('OK', $redis->ftalter('index', $schema));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown index name');
+
+ $redis->ftalter('alias', [new TextField('field_name')]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['index', [new TextField('text_field')]],
+ ['index', 'SCHEMA', 'ADD', 'text_field', 'TEXT'],
+ ],
+ 'with SKIPINITIALSCAN modifier' => [
+ ['index', [new TextField('text_field')], (new AlterArguments())->skipInitialScan()],
+ ['index', 'SKIPINITIALSCAN', 'SCHEMA', 'ADD', 'text_field', 'TEXT'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTCONFIG_Test.php b/tests/Predis/Command/Redis/Search/FTCONFIG_Test.php
new file mode 100644
index 00000000..8b75363e
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTCONFIG_Test.php
@@ -0,0 +1,151 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSameValues($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testHelpFilterArguments(): void
+ {
+ $arguments = ['HELP', 'option'];
+ $expected = ['HELP', 'option'];
+
+ $command = $this->getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSameValues($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testSetFilterArguments(): void
+ {
+ $arguments = ['SET', 'option', 'value'];
+ $expected = ['SET', 'option', 'value'];
+
+ $command = $this->getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSameValues($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testSetGivenRediSearchConfigurationParameter(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertEquals('OK', $redis->ftconfig->set('TIMEOUT', 42));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testGetReturnsGivenRediSearchConfigurationParameter(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertEquals([['MAXEXPANSIONS', '200']], $redis->ftconfig->get('MAXEXPANSIONS'));
+ $this->assertEmpty($redis->ftconfig->get('foobar'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testHelpReturnsGivenRediSearchConfigurationDescription(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [
+ [
+ 'MAXEXPANSIONS',
+ 'Description',
+ 'Maximum prefix expansions to be used in a query',
+ 'Value',
+ '200',
+ ],
+ ];
+
+ $this->assertEquals($expectedResponse, $redis->ftconfig->help('MAXEXPANSIONS'));
+ $this->assertEmpty($redis->ftconfig->help('foobar'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testSetThrowsExceptionOnNonExistingOption(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Invalid option');
+
+ $redis->ftconfig->set('foobar', 'value');
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTCREATE_Test.php b/tests/Predis/Command/Redis/Search/FTCREATE_Test.php
new file mode 100644
index 00000000..746d3d7b
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTCREATE_Test.php
@@ -0,0 +1,169 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testCreatesSearchIndexWithGivenArgumentsAndSchema(): void
+ {
+ $redis = $this->getClient();
+
+ $schema = [
+ new TextField('first', 'fst', true, true),
+ new TextField('last'),
+ new NumericField('age'),
+ ];
+
+ $arguments = new CreateArguments();
+ $arguments->prefix(['prefix:', 'prefix1:']);
+ $arguments->filter('@age>16');
+ $arguments->stopWords(['hello', 'world']);
+
+ $actualResponse = $redis->ftcreate('index', $schema, $arguments);
+
+ $this->assertEquals('OK', $actualResponse);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'without arguments' => [
+ ['index', [new TextField('field_name')]],
+ ['index', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with ON modifier - HASH' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->on()],
+ ['index', 'ON', 'HASH', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with ON modifier - JSON' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->on('JSON')],
+ ['index', 'ON', 'JSON', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with prefixes' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->prefix(['prefix1:', 'prefix2:'])],
+ ['index', 'PREFIX', 2, 'prefix1:', 'prefix2:', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with FILTER' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->filter('@age>16')],
+ ['index', 'FILTER', '@age>16', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with LANGUAGE' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->language()],
+ ['index', 'LANGUAGE', 'english', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with LANGUAGE_FIELD' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->languageField('language_attribute')],
+ ['index', 'LANGUAGE_FIELD', 'language_attribute', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with SCORE' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->score()],
+ ['index', 'SCORE', 1.0, 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with SCORE_FIELD' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->scoreField('score_attribute')],
+ ['index', 'SCORE_FIELD', 'score_attribute', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with MAXTEXTFIELDS' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->maxTextFields()],
+ ['index', 'MAXTEXTFIELDS', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with TEMPORARY' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->temporary(1)],
+ ['index', 'TEMPORARY', 1, 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with NOOFFSETS' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->noOffsets()],
+ ['index', 'NOOFFSETS', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with NOHL' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->noHl()],
+ ['index', 'NOHL', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with NOFIELDS' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->noFields()],
+ ['index', 'NOFIELDS', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with NOFREQS' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->noFreqs()],
+ ['index', 'NOFREQS', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with STOPWORDS' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->stopWords(['word1', 'word2'])],
+ ['index', 'STOPWORDS', 2, 'word1', 'word2', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with SKIPINITIALSCAN' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->skipInitialScan()],
+ ['index', 'SKIPINITIALSCAN', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with chain of arguments' => [
+ ['index', [new TextField('field_name')], (new CreateArguments())->on()->prefix(['prefix1:', 'prefix2:'])->filter('@age>16')],
+ ['index', 'ON', 'HASH', 'PREFIX', 2, 'prefix1:', 'prefix2:', 'FILTER', '@age>16', 'SCHEMA', 'field_name', 'TEXT'],
+ ],
+ 'with multiple fields schema' => [
+ ['index', [new TextField('text_field'), new NumericField('numeric_field'), new TagField('tag_field', 'tf')], (new CreateArguments())->on()],
+ ['index', 'ON', 'HASH', 'SCHEMA', 'text_field', 'TEXT', 'numeric_field', 'NUMERIC', 'tag_field', 'AS', 'tf', 'TAG'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTCURSOR_Test.php b/tests/Predis/Command/Redis/Search/FTCURSOR_Test.php
new file mode 100644
index 00000000..637c0be4
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTCURSOR_Test.php
@@ -0,0 +1,218 @@
+getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSameValues($expected, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ * @dataProvider readArgumentsProvider
+ */
+ public function testReadFilterArguments(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
+ * @requiresRediSearchVersion >= 1.1.0
+ */
+ public function testReadAggregatedResultsFromExistingCursor(): void
+ {
+ $redis = $this->getClient();
+
+ $expectedResponse = [
+ [
+ 'country', 'Ukraine', 'birth', '1995', 'country_birth_Vlad_count', '2',
+ ],
+ [
+ 'country', 'Israel', 'birth', '1994', 'country_birth_Vlad_count', '1',
+ ],
+ ];
+
+ $ftCreateArguments = (new CreateArguments())->prefix(['user:']);
+ $schema = [
+ new TextField('name'),
+ new TextField('country'),
+ new NumericField('dob', '', AbstractField::SORTABLE),
+ ];
+
+ $this->assertEquals('OK', $redis->ftcreate('idx', $schema, $ftCreateArguments));
+ $this->assertSame(
+ 3,
+ $redis->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600)
+ );
+ $this->assertSame(
+ 3,
+ $redis->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600)
+ );
+ $this->assertSame(
+ 3,
+ $redis->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600)
+ );
+
+ $ftAggregateArguments = (new AggregateArguments())
+ ->apply('year(@dob)', 'birth')
+ ->groupBy('@country', '@birth')
+ ->reduce('COUNT', true, 'country_birth_Vlad_count')
+ ->sortBy(0, '@birth', 'DESC')
+ ->withCursor(1);
+
+ [$response, $cursor] = $redis->ftaggregate('idx', '@name: "Vlad"', $ftAggregateArguments);
+ $actualResponse = [];
+
+ while ($cursor) {
+ $actualResponse[] = $response[1];
+ [$response, $cursor] = $redis->ftcursor->read('idx', $cursor);
+ }
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.1.0
+ */
+ public function testDelExplicitlyRemovesExistingCursor(): void
+ {
+ $redis = $this->getClient();
+
+ $ftCreateArguments = (new CreateArguments())->prefix(['user:']);
+ $schema = [
+ new TextField('name'),
+ new TextField('country'),
+ new NumericField('dob', '', AbstractField::SORTABLE),
+ ];
+
+ $this->assertEquals('OK', $redis->ftcreate('idx', $schema, $ftCreateArguments));
+ $this->assertSame(
+ 3,
+ $redis->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600)
+ );
+ $this->assertSame(
+ 3,
+ $redis->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600)
+ );
+ $this->assertSame(
+ 3,
+ $redis->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600)
+ );
+
+ $ftAggregateArguments = (new AggregateArguments())
+ ->apply('year(@dob)', 'birth')
+ ->groupBy('@country', '@birth')
+ ->reduce('COUNT', true, 'country_birth_Vlad_count')
+ ->sortBy(0, '@birth', 'DESC')
+ ->withCursor(1);
+
+ [$_, $cursor] = $redis->ftaggregate('idx', '@name: "Vlad"', $ftAggregateArguments);
+
+ $this->assertEquals('OK', $redis->ftcursor->del('idx', $cursor));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.1.0
+ */
+ public function testReadThrowsExceptionOnWrongCursorId(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Cursor not found');
+
+ $redis->ftcursor->read('idx', 21412412);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.1.0
+ */
+ public function testDelThrowsExceptionOnWrongCursorId(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Cursor does not exist');
+
+ $redis->ftcursor->del('idx', 21412412);
+ }
+
+ public function readArgumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['READ', 'index', 2],
+ ['READ', 'index', 2],
+ ],
+ 'with COUNT modifier' => [
+ ['READ', 'index', 2, (new CursorArguments())->count(2)],
+ ['READ', 'index', 2, 'COUNT', 2],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTDICTADD_Test.php b/tests/Predis/Command/Redis/Search/FTDICTADD_Test.php
new file mode 100644
index 00000000..577fda96
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTDICTADD_Test.php
@@ -0,0 +1,74 @@
+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
+ * @requiresRediSearchVersion >= 1.4.0
+ */
+ public function testAddTermsIntoGivenDictionary(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->ftdictadd('dict', 'foo', 'bar');
+
+ $this->assertSame(2, $actualResponse);
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTDICTDEL_Test.php b/tests/Predis/Command/Redis/Search/FTDICTDEL_Test.php
new file mode 100644
index 00000000..566add53
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTDICTDEL_Test.php
@@ -0,0 +1,102 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider dictionariesProvider
+ * @param array $addArguments
+ * @param array $deleteArguments
+ * @param int $expectedResponse
+ * @return void
+ * @requiresRediSearchVersion >= 1.4.0
+ */
+ public function testRemovesTermsFromGivenDictionary(
+ array $addArguments,
+ array $deleteArguments,
+ int $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->ftdictadd(...$addArguments);
+
+ $this->assertSame($expectedResponse, $redis->ftdictdel(...$deleteArguments));
+ }
+
+ public function dictionariesProvider(): array
+ {
+ return [
+ 'removes existing term' => [
+ ['dict', 'foo', 'bar'],
+ ['dict', 'foo'],
+ 1,
+ ],
+ 'removes non-existing term' => [
+ ['dict', 'foo', 'bar'],
+ ['dict', 'baz'],
+ 0,
+ ],
+ 'removes from non-existing dict' => [
+ ['dict', 'foo', 'bar'],
+ ['dict123', 'baz'],
+ 0,
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTDICTDUMP_Test.php b/tests/Predis/Command/Redis/Search/FTDICTDUMP_Test.php
new file mode 100644
index 00000000..7c260dc7
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTDICTDUMP_Test.php
@@ -0,0 +1,90 @@
+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
+ * @requiresRediSearchVersion >= 1.4.0
+ */
+ public function testDumpTermsFromGivenDictionary(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->ftdictadd('dict', 'foo', 'bar');
+
+ $this->assertSame(['bar', 'foo'], $redis->ftdictdump('dict'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.4.0
+ */
+ public function testThrowsExceptionOnNonExistingDictionary(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('could not open dict key');
+
+ $redis->ftdictdump('dict');
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTDROPINDEX_Test.php b/tests/Predis/Command/Redis/Search/FTDROPINDEX_Test.php
new file mode 100644
index 00000000..d026db7a
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTDROPINDEX_Test.php
@@ -0,0 +1,105 @@
+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
+ * @requiresRediSearchVersion >= 2.0.0
+ */
+ public function testDropRemovesGivenIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $schema = [new TextField('text_field')];
+
+ $this->assertEquals('OK', $redis->ftcreate('index', $schema));
+ $this->assertEquals('OK', $redis->ftdropindex('index'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown Index name');
+
+ $redis->ftdropindex('index');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['index'],
+ ['index'],
+ ],
+ 'with DD modifier' => [
+ ['index', (new DropArguments())->dd()],
+ ['index', 'DD'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php b/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php
new file mode 100644
index 00000000..11eeae12
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php
@@ -0,0 +1,135 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testExplainReturnsExecutionPlanForGivenQuery(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = <<assertEquals('OK', $redis->ftcreate('index', $schema));
+ $this->assertEquals(
+ $expectedResponse,
+ $redis->ftexplain('index', '(foo bar)|(hello world) @date:[100 200]|@date:[500 +inf]')
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('index: no such index');
+
+ $redis->ftexplain('index', 'query');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['index', 'query', null],
+ ['index', 'query'],
+ ],
+ 'with DIALECT' => [
+ ['index', 'query', (new ExplainArguments())->dialect('dialect')],
+ ['index', 'query', 'DIALECT', 'dialect'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTINFO_Test.php b/tests/Predis/Command/Redis/Search/FTINFO_Test.php
new file mode 100644
index 00000000..276be34e
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTINFO_Test.php
@@ -0,0 +1,100 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testInfoReturnsInformationAboutGivenIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = new CreateArguments();
+ $arguments->prefix(['prefix:']);
+ $arguments->language();
+
+ $schema = [new TextField('text_field')];
+
+ $createResponse = $redis->ftcreate('index', $schema, $arguments);
+ $this->assertEquals('OK', $createResponse);
+
+ $actualResponse = $redis->ftinfo('index');
+ $this->assertEquals('index', $actualResponse[1]);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown Index name');
+
+ $redis->ftinfo('index');
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTPROFILE_Test.php b/tests/Predis/Command/Redis/Search/FTPROFILE_Test.php
new file mode 100644
index 00000000..78c83c69
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTPROFILE_Test.php
@@ -0,0 +1,126 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider queryProvider
+ * @param array $createArguments
+ * @param array $profileArguments
+ * @return void
+ * @requiresRediSearchVersion >= 2.2.0
+ */
+ public function testProfileReturnsPerformanceInformationAboutGivenQuery(
+ array $createArguments,
+ array $profileArguments
+ ): void {
+ $redis = $this->getClient();
+
+ $this->assertEquals('OK', $redis->ftcreate(...$createArguments));
+ $this->assertNotEmpty($redis->ftprofile(...$profileArguments));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 2.2.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('index: no such index');
+
+ $redis->ftprofile('index', (new ProfileArguments())->search()->query('query'));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments - SEARCH' => [
+ ['index', (new ProfileArguments())->search()->query('query')],
+ ['index', 'SEARCH', 'QUERY', 'query'],
+ ],
+ 'with default arguments - AGGREGATE' => [
+ ['index', (new ProfileArguments())->aggregate()->query('query')],
+ ['index', 'AGGREGATE', 'QUERY', 'query'],
+ ],
+ 'with LIMITED modifier' => [
+ ['index', (new ProfileArguments())->aggregate()->limited()->query('query')],
+ ['index', 'AGGREGATE', 'LIMITED', 'QUERY', 'query'],
+ ],
+ ];
+ }
+
+ public function queryProvider(): array
+ {
+ return [
+ 'with SEARCH context' => [
+ ['index', [new TextField('text_field')]],
+ ['index', (new ProfileArguments())->search()->query('hello world')],
+ ],
+ 'with AGGREGATE context' => [
+ ['index', [new TextField('text_field')]],
+ ['index', (new ProfileArguments())->aggregate()->query('hello world')],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php
new file mode 100644
index 00000000..ecbf593f
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php
@@ -0,0 +1,221 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ * @requiresRedisJsonVersion >= 1.0.0
+ */
+ public function testSearchValuesByJsonIndex(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [1, 'doc:1', ['arr', '[1,2,3]', 'val', 'hello']];
+
+ $jsonResponse = $redis->jsonset(
+ 'doc:1',
+ '$',
+ '[{"arr": [1, 2, 3]}, {"val": "hello"}, {"val": "world"}]'
+ );
+ $this->assertEquals('OK', $jsonResponse);
+
+ $createArguments = new CreateArguments();
+ $createArguments->on('json');
+ $createArguments->prefix(['doc:']);
+
+ $schema = [new NumericField('$..arr', 'arr'), new TextField('$..val', 'val')];
+
+ $ftCreateResponse = $redis->ftcreate('idx_json', $schema, $createArguments);
+ $this->assertEquals('OK', $ftCreateResponse);
+
+ $ftSearchArguments = new SearchArguments();
+ $ftSearchArguments->addReturn(2, 'arr', 'val');
+
+ $actualResponse = $redis->ftsearch('idx_json', '*', $ftSearchArguments);
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testSearchValuesByHashIndex(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [1, 'doc:1', ['should_return', 'value1']];
+
+ $hashResponse = $redis->hmset('doc:1', 'field1', 'value1', 'field2', 'value2');
+ $this->assertEquals('OK', $hashResponse);
+
+ $ftCreateArguments = new CreateArguments();
+ $ftCreateArguments->prefix(['doc:']);
+
+ $schema = [
+ new TextField('field1', 'should_return'),
+ new TextField('field2', 'should_not_return'),
+ ];
+
+ $ftCreateResponse = $redis->ftcreate('idx_hash', $schema, $ftCreateArguments);
+ $this->assertEquals('OK', $ftCreateResponse);
+
+ $ftSearchArguments = new SearchArguments();
+ $ftSearchArguments->addReturn(1, 'should_return');
+
+ $actualResponse = $redis->ftsearch('idx_hash', '*', $ftSearchArguments);
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with NOCONTENT modifier' => [
+ ['index', '*', (new SearchArguments())->noContent()],
+ ['index', '*', 'NOCONTENT'],
+ ],
+ 'with VERBATIM modifier' => [
+ ['index', '*', (new SearchArguments())->verbatim()],
+ ['index', '*', 'VERBATIM'],
+ ],
+ 'with WITHSCORES modifier' => [
+ ['index', '*', (new SearchArguments())->withScores()],
+ ['index', '*', 'WITHSCORES'],
+ ],
+ 'with WITHPAYLOADS modifier' => [
+ ['index', '*', (new SearchArguments())->withPayloads()],
+ ['index', '*', 'WITHPAYLOADS'],
+ ],
+ 'with WITHSORTKEYS modifier' => [
+ ['index', '*', (new SearchArguments())->withSortKeys()],
+ ['index', '*', 'WITHSORTKEYS'],
+ ],
+ 'with FILTER modifier' => [
+ ['index', '*', (new SearchArguments())->searchFilter(['numeric_field', 1, 10])],
+ ['index', '*', 'FILTER', 'numeric_field', 1, 10],
+ ],
+ 'with GEOFILTER modifier' => [
+ ['index', '*', (new SearchArguments())->geoFilter(['geo_field', 12.213, 14.212, 300, 'km'])],
+ ['index', '*', 'GEOFILTER', 'geo_field', 12.213, 14.212, 300, 'km'],
+ ],
+ 'with INKEYS modifier' => [
+ ['index', '*', (new SearchArguments())->inKeys(['key1', 'key2'])],
+ ['index', '*', 'INKEYS', 2, 'key1', 'key2'],
+ ],
+ 'with INFIELDS modifier' => [
+ ['index', '*', (new SearchArguments())->inFields(['field1', 'field2'])],
+ ['index', '*', 'INFIELDS', 2, 'field1', 'field2'],
+ ],
+ 'with RETURN modifier' => [
+ ['index', '*', (new SearchArguments())->addReturn(2, 'identifier', true, 'property')],
+ ['index', '*', 'RETURN', 2, 'identifier', 'AS', 'property'],
+ ],
+ 'with SUMMARIZE modifier' => [
+ ['index', '*', (new SearchArguments())->summarize(['field1', 'field2'], 2, 2, ',')],
+ ['index', '*', 'SUMMARIZE', 'FIELDS', 2, 'field1', 'field2', 'FRAGS', 2, 'LEN', 2, 'SEPARATOR', ','],
+ ],
+ 'with HIGHLIGHT modifier' => [
+ ['index', '*', (new SearchArguments())->highlight(['field1', 'field2'], 'openTag', 'closeTag')],
+ ['index', '*', 'HIGHLIGHT', 'FIELDS', 2, 'field1', 'field2', 'TAGS', 'openTag', 'closeTag'],
+ ],
+ 'with SLOP modifier' => [
+ ['index', '*', (new SearchArguments())->slop(2)],
+ ['index', '*', 'SLOP', 2],
+ ],
+ 'with TIMEOUT modifier' => [
+ ['index', '*', (new SearchArguments())->timeout(2)],
+ ['index', '*', 'TIMEOUT', 2],
+ ],
+ 'with INORDER modifier' => [
+ ['index', '*', (new SearchArguments())->inOrder()],
+ ['index', '*', 'INORDER'],
+ ],
+ 'with EXPANDER modifier' => [
+ ['index', '*', (new SearchArguments())->expander('expander')],
+ ['index', '*', 'EXPANDER', 'expander'],
+ ],
+ 'with SCORER modifier' => [
+ ['index', '*', (new SearchArguments())->scorer('scorer')],
+ ['index', '*', 'SCORER', 'scorer'],
+ ],
+ 'with EXPLAINSCORE modifier' => [
+ ['index', '*', (new SearchArguments())->explainScore()],
+ ['index', '*', 'EXPLAINSCORE'],
+ ],
+ 'with PAYLOAD modifier' => [
+ ['index', '*', (new SearchArguments())->payload('payload')],
+ ['index', '*', 'PAYLOAD', 'payload'],
+ ],
+ 'with SORTBY modifier' => [
+ ['index', '*', (new SearchArguments())->sortBy('sort_attribute', 'desc')],
+ ['index', '*', 'SORTBY', 'sort_attribute', 'DESC'],
+ ],
+ 'with LIMIT modifier' => [
+ ['index', '*', (new SearchArguments())->limit(2, 2)],
+ ['index', '*', 'LIMIT', 2, 2],
+ ],
+ 'with PARAMS modifier' => [
+ ['index', '*', (new SearchArguments())->params(['name1', 'value2', 'name2', 'value2'])],
+ ['index', '*', 'PARAMS', 4, 'name1', 'value2', 'name2', 'value2'],
+ ],
+ 'with DIALECT modifier' => [
+ ['index', '*', (new SearchArguments())->dialect('dialect')],
+ ['index', '*', 'DIALECT', 'dialect'],
+ ],
+ 'with chain of arguments' => [
+ ['index', '*', (new SearchArguments())->withScores()->withPayloads()->searchFilter(['numeric_field', 1, 10])->addReturn(2, 'identifier', true, 'property')],
+ ['index', '*', 'WITHSCORES', 'WITHPAYLOADS', 'FILTER', 'numeric_field', 1, 10, 'RETURN', 2, 'identifier', 'AS', 'property'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php b/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php
new file mode 100644
index 00000000..4bb77a12
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php
@@ -0,0 +1,156 @@
+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
+ * @requiresRediSearchVersion >= 1.4.0
+ */
+ public function testSpellcheckReturnsPossibleSuggestionsToGivenMisspelledTerm(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [['TERM', 'held', [['0', 'hello'], ['0', 'help']]]];
+
+ $this->assertEquals('OK', $redis->ftcreate(
+ 'index',
+ [new TextField('text_field')]
+ ));
+
+ $this->assertEquals(2, $redis->ftdictadd('dict', 'hello', 'help'));
+
+ $actualResponse = $redis->ftspellcheck(
+ 'index',
+ 'held',
+ (new SpellcheckArguments())->distance(2)->terms('dict')
+ );
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.4.0
+ */
+ public function testThrowsExceptionOnIncorrectTermsModifierGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Wrong modifier value given. Currently supports: INCLUDE, EXCLUDE');
+
+ $redis->ftspellcheck(
+ 'index',
+ 'held',
+ (new SpellcheckArguments())->distance(2)->terms('dict', 'wrong')
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.4.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown Index name');
+
+ $redis->ftspellcheck(
+ 'index',
+ 'held',
+ (new SpellcheckArguments())->distance(2)->terms('dict')
+ );
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['index', 'query'],
+ ['index', 'query'],
+ ],
+ 'with DISTANCE modifier' => [
+ ['index', 'query', (new SpellcheckArguments())->distance(2)],
+ ['index', 'query', 'DISTANCE', 2],
+ ],
+ 'with TERMS modifier - INCLUDE' => [
+ ['index', 'query', (new SpellcheckArguments())->terms('dict', 'INCLUDE', 'term')],
+ ['index', 'query', 'TERMS', 'INCLUDE', 'dict', 'term'],
+ ],
+ 'with TERMS modifier - EXCLUDE' => [
+ ['index', 'query', (new SpellcheckArguments())->terms('dict', 'EXCLUDE', 'term')],
+ ['index', 'query', 'TERMS', 'EXCLUDE', 'dict', 'term'],
+ ],
+ 'with DIALECT modifier' => [
+ ['index', 'query', (new SpellcheckArguments())->dialect('dialect')],
+ ['index', 'query', 'DIALECT', 'dialect'],
+ ],
+ 'with all arguments' => [
+ ['index', 'query', (new SpellcheckArguments())->distance(2)->terms('dict', 'INCLUDE', 'term')->dialect('dialect')],
+ ['index', 'query', 'DISTANCE', 2, 'TERMS', 'INCLUDE', 'dict', 'term', 'DIALECT', 'dialect'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSUGADD_Test.php b/tests/Predis/Command/Redis/Search/FTSUGADD_Test.php
new file mode 100644
index 00000000..f858796c
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSUGADD_Test.php
@@ -0,0 +1,93 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testAddSuggestionStringIntoAutoCompleteDictionary(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertSame(1, $redis->ftsugadd('key', 'string', 1));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 'string', 1],
+ ['key', 'string', 1],
+ ],
+ 'with INCR modifier' => [
+ ['key', 'string', 1, (new SugAddArguments())->incr()],
+ ['key', 'string', 1, 'INCR'],
+ ],
+ 'with PAYLOAD' => [
+ ['key', 'string', 1, (new SugAddArguments())->payload('payload')],
+ ['key', 'string', 1, 'PAYLOAD', 'payload'],
+ ],
+ 'with all arguments' => [
+ ['key', 'string', 1, (new SugAddArguments())->incr()->payload('payload')],
+ ['key', 'string', 1, 'INCR', 'PAYLOAD', 'payload'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSUGDEL_Test.php b/tests/Predis/Command/Redis/Search/FTSUGDEL_Test.php
new file mode 100644
index 00000000..3a52e420
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSUGDEL_Test.php
@@ -0,0 +1,85 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testRemovesSuggestionFromGivenSuggestionIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertSame(1, $redis->ftsugadd('key', 'string', 1));
+ $this->assertSame(1, $redis->ftsugdel('key', 'string'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testReturnsZeroOnNonExistingSuggestionIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertSame(0, $redis->ftsugdel('key', 'string'));
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSUGGET_Test.php b/tests/Predis/Command/Redis/Search/FTSUGGET_Test.php
new file mode 100644
index 00000000..da05f156
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSUGGET_Test.php
@@ -0,0 +1,169 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider suggestionProvider
+ * @param array $addArguments
+ * @param array $getArguments
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testGetSuggestionsForGivenPrefix(
+ array $addArguments,
+ array $getArguments,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->ftsugadd(...$addArguments);
+
+ $actualResponse = $redis->ftsugget(...$getArguments);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testGetReturnsLimitedResultsWithMaxModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->ftsugadd('key', 'hello', 2);
+ $redis->ftsugadd('key', 'hell', 2);
+
+ $actualResponse = $redis->ftsugget('key', 'hel', (new SugGetArguments())->max(1));
+
+ $this->assertSame(['hell'], $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testGetReturnsNullOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertNull($redis->ftsugget('key', 'hel'));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 'prefix'],
+ ['key', 'prefix'],
+ ],
+ 'with FUZZY modifier' => [
+ ['key', 'prefix', (new SugGetArguments())->fuzzy()],
+ ['key', 'prefix', 'FUZZY'],
+ ],
+ 'with WITHSCORES modifier' => [
+ ['key', 'prefix', (new SugGetArguments())->withScores()],
+ ['key', 'prefix', 'WITHSCORES'],
+ ],
+ 'with WITHPAYLOADS modifier' => [
+ ['key', 'prefix', (new SugGetArguments())->withPayloads()],
+ ['key', 'prefix', 'WITHPAYLOADS'],
+ ],
+ 'with MAX modifier' => [
+ ['key', 'prefix', (new SugGetArguments())->max(5)],
+ ['key', 'prefix', 'MAX', 5],
+ ],
+ 'with all arguments' => [
+ ['key', 'prefix', (new SugGetArguments())->fuzzy()->withScores()->withPayloads()->max(5)],
+ ['key', 'prefix', 'FUZZY', 'WITHSCORES', 'WITHPAYLOADS', 'MAX', 5],
+ ],
+ ];
+ }
+
+ public function suggestionProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 'hello', 2],
+ ['key', 'hell'],
+ ['hello'],
+ ],
+ 'with FUZZY search' => [
+ ['key', 'hello', 2],
+ ['key', 'help', (new SugGetArguments())->fuzzy()],
+ ['hello'],
+ ],
+ 'with WITHSCORES modifier' => [
+ ['key', 'hello', 2],
+ ['key', 'hell', (new SugGetArguments())->withScores()],
+ ['hello', '1.4142135381698608'],
+ ],
+ 'with WITHPAYLOADS modifier' => [
+ ['key', 'hello', 2, (new SugAddArguments())->payload('payload')],
+ ['key', 'hell', (new SugGetArguments())->withPayloads()],
+ ['hello', 'payload'],
+ ],
+ 'with all modifiers' => [
+ ['key', 'hello', 2, (new SugAddArguments())->payload('payload')],
+ ['key', 'hellp', (new SugGetArguments())->fuzzy()->withScores()->withPayloads()],
+ ['hello', '290630304', 'payload'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php b/tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php
new file mode 100644
index 00000000..9d06cb8f
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php
@@ -0,0 +1,71 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testReturnsLengthOfGivenSuggestionDictionary(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->ftsugadd('key', 'foo', 1);
+ $redis->ftsugadd('key', 'bar', 1);
+
+ $this->assertSame(2, $redis->ftsuglen('key'));
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSYNDUMP_Test.php b/tests/Predis/Command/Redis/Search/FTSYNDUMP_Test.php
new file mode 100644
index 00000000..2821f65a
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSYNDUMP_Test.php
@@ -0,0 +1,100 @@
+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
+ * @requiresRediSearchVersion >= 1.2.0
+ */
+ public function testDumpReturnsContentOfSynonymGroupFromGivenIndex(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = ['term1', ['synonym1'], 'term2', ['synonym1']];
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftcreate('index', [new TextField('text_field')])
+ );
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftsynupdate('index', 'synonym1', null, 'term1', 'term2')
+ );
+
+ $this->assertSame($expectedResponse, $redis->ftsyndump('index'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.2.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown index name');
+
+ $redis->ftsyndump('index');
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTSYNUPDATE_Test.php b/tests/Predis/Command/Redis/Search/FTSYNUPDATE_Test.php
new file mode 100644
index 00000000..21c5eb1b
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTSYNUPDATE_Test.php
@@ -0,0 +1,140 @@
+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
+ * @requiresRediSearchVersion >= 1.2.0
+ */
+ public function testCreatesSynonymGroupWithinGivenIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftcreate('index', [new TextField('text')])
+ );
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftsynupdate('index', 'synonym1', null, 'term1', 'term2')
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.2.0
+ */
+ public function testUpdatesAlreadyExistingSynonymGroupWithinGivenIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftcreate('index', [new TextField('text')])
+ );
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftsynupdate('index', 'synonym1', null, 'term1', 'term2')
+ );
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftsynupdate('index', 'synonym1', null, 'term3', 'term4')
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.2.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown index name');
+
+ $redis->ftsynupdate(
+ 'index',
+ 'synonym1',
+ null,
+ 'term1'
+ );
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['index', 'synonymGroupId', null, 'term1', 'term2'],
+ ['index', 'synonymGroupId', 'term1', 'term2'],
+ ],
+ 'with SKIPINITIALSCAN modifier' => [
+ ['index', 'synonymGroupId', (new SynUpdateArguments())->skipInitialScan(), 'term1', 'term2'],
+ ['index', 'synonymGroupId', 'SKIPINITIALSCAN', 'term1', 'term2'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/Search/FTTAGVALS_Test.php b/tests/Predis/Command/Redis/Search/FTTAGVALS_Test.php
new file mode 100644
index 00000000..80a7813a
--- /dev/null
+++ b/tests/Predis/Command/Redis/Search/FTTAGVALS_Test.php
@@ -0,0 +1,110 @@
+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
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testReturnIndexedTagFieldDistinctValues(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = ['hello', 'hey', 'world'];
+
+ $this->assertEquals(
+ 'OK',
+ $redis->ftcreate(
+ 'index',
+ [new TagField('tag_field')],
+ (new CreateArguments())->prefix(['prefix:'])
+ )
+ );
+
+ $this->assertSame(
+ 1,
+ $redis->hset('prefix:1', 'tag_field', 'Hello, World')
+ );
+
+ $this->assertSame(
+ 1,
+ $redis->hset('prefix:2', 'tag_field', 'Hey, World')
+ );
+
+ $this->assertSame($expectedResponse, $redis->fttagvals('index', 'tag_field'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRediSearchVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingIndex(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('Unknown Index name');
+
+ $redis->fttagvals('index', 'fieldName');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTADD_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTADD_Test.php
new file mode 100644
index 00000000..fa76ee6a
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTADD_Test.php
@@ -0,0 +1,94 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testAddObservationsIntoGivenTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('key');
+
+ $actualResponse = $redis->tdigestadd('key', 1, 2, 3);
+ $info = $redis->tdigestinfo('key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame(3, $info['Observations']);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestadd('key', 1, 2, 3);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php
new file mode 100644
index 00000000..47d9841a
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php
@@ -0,0 +1,97 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsValuesEstimatedForGivenRanks(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = ['1', '2', '2', '3', '3', '3', 'inf'];
+
+ $redis->tdigestcreate('key');
+ $redis->tdigestcreate('empty_key');
+
+ $redis->tdigestadd('key', 1, 2, 2, 3, 3, 3);
+
+ $actualResponse = $redis->tdigestbyrank('key', 0, 1, 2, 3, 4, 5, 6);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame(['nan', 'nan'], $redis->tdigestbyrank('empty_key', 0, 1));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestbyrank('key', 1, 2, 3);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php
new file mode 100644
index 00000000..ee3c3040
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php
@@ -0,0 +1,97 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsValuesEstimatedForGivenReverseRanks(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = ['3', '3', '3', '2', '2', '1', '-inf'];
+
+ $redis->tdigestcreate('key');
+ $redis->tdigestcreate('empty_key');
+
+ $redis->tdigestadd('key', 1, 2, 2, 3, 3, 3);
+
+ $actualResponse = $redis->tdigestbyrevrank('key', 0, 1, 2, 3, 4, 5, 6);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame(['nan', 'nan'], $redis->tdigestbyrevrank('empty_key', 0, 1));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestbyrevrank('key', 1, 2, 3);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php
new file mode 100644
index 00000000..5a844523
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php
@@ -0,0 +1,97 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsValuesEstimatedForGivenRanks(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = ['0', '0.083333333333333329', '0.33333333333333331', '0.75', '1'];
+
+ $redis->tdigestcreate('key');
+ $redis->tdigestcreate('empty_key');
+
+ $redis->tdigestadd('key', 1, 2, 2, 3, 3, 3);
+
+ $actualResponse = $redis->tdigestcdf('key', 0, 1, 2, 3, 4);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame(['nan', 'nan'], $redis->tdigestcdf('empty_key', 0, 1));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestcdf('key', 1, 2, 3);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTCREATE_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTCREATE_Test.php
new file mode 100644
index 00000000..39bcc23c
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTCREATE_Test.php
@@ -0,0 +1,134 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider sketchesProvider
+ * @param array $createArguments
+ * @param string $key
+ * @param int $expectedCompression
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testCreateTDigestSketchWithGivenConfiguration(
+ array $createArguments,
+ string $key,
+ int $expectedCompression
+ ): void {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->tdigestcreate(...$createArguments);
+ $info = $redis->tdigestinfo($key);
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame($expectedCompression, $info['Compression']);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnAlreadyCreatedKey(): void
+ {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->tdigestcreate('key');
+ $this->assertEquals('OK', $actualResponse);
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key already exists');
+
+ $redis->tdigestcreate('key');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key'],
+ ['key'],
+ ],
+ 'with 0 compression' => [
+ ['key', 0],
+ ['key'],
+ ],
+ 'with COMPRESSION modifier' => [
+ ['key', 100],
+ ['key', 'COMPRESSION', 100],
+ ],
+ ];
+ }
+
+ public function sketchesProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key'],
+ 'key',
+ 100,
+ ],
+ 'with modified COMPRESSION' => [
+ ['key', 120],
+ 'key',
+ 120,
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTINFO_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTINFO_Test.php
new file mode 100644
index 00000000..86072cc2
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTINFO_Test.php
@@ -0,0 +1,117 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $actualResponse = [
+ 'Compression', 100, 'Capacity', 610, 'Merged nodes', 0, 'Unmerged nodes', 5, 'Merged weight', 0,
+ 'Unmerged weight', 5, 'Observations', 5, 'Total compressions', 0, 'Memory usage', 9768,
+ ];
+ $expectedResponse = [
+ 'Compression' => 100,
+ 'Capacity' => 610,
+ 'Merged nodes' => 0,
+ 'Unmerged nodes' => 5,
+ 'Merged weight' => 0,
+ 'Unmerged weight' => 5,
+ 'Observations' => 5,
+ 'Total compressions' => 0,
+ 'Memory usage' => 9768,
+ ];
+
+ $this->assertSame($expectedResponse, $this->getCommand()->parseResponse($actualResponse));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testInfoReturnsInformationAboutGivenTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [
+ 'Compression' => 100,
+ 'Capacity' => 610,
+ 'Merged nodes' => 0,
+ 'Unmerged nodes' => 0,
+ 'Merged weight' => 0,
+ 'Unmerged weight' => 0,
+ 'Observations' => 0,
+ 'Total compressions' => 0,
+ 'Memory usage' => 9768,
+ ];
+
+ $redis->tdigestcreate('key');
+
+ $this->assertSame($expectedResponse, $redis->tdigestinfo('key'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestinfo('key');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php
new file mode 100644
index 00000000..f3c206ae
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php
@@ -0,0 +1,96 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsMaxValueFromGivenSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('key');
+ $redis->tdigestcreate('empty_key');
+
+ $redis->tdigestadd('key', 3, 2, 4, 5, 1);
+
+ $actualResponse = $redis->tdigestmax('key');
+
+ $this->assertSame('5', $actualResponse);
+ $this->assertSame('nan', $redis->tdigestmax('empty_key'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestmax('key');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php
new file mode 100644
index 00000000..a6575498
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php
@@ -0,0 +1,254 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSame($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group connected
+ * @dataProvider sketchesProvider
+ * @param string $sourceKey1
+ * @param string $sourceKey2
+ * @param array $sourceKey1AddValues
+ * @param array $sourceKey2AddValues
+ * @param array $mergeValues
+ * @param int $expectedCompression
+ * @param array $expectedMergedSketchValues
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testMergeTwoSketchesIntoOneWithinGivenDestinationKey(
+ string $sourceKey1,
+ string $sourceKey2,
+ array $sourceKey1AddValues,
+ array $sourceKey2AddValues,
+ array $mergeValues,
+ int $expectedCompression,
+ array $expectedMergedSketchValues
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate($sourceKey1);
+ $redis->tdigestcreate($sourceKey2);
+
+ $redis->tdigestadd(...$sourceKey1AddValues);
+ $redis->tdigestadd(...$sourceKey2AddValues);
+
+ $actualResponse = $redis->tdigestmerge(...$mergeValues);
+ $info = $redis->tdigestinfo('destination-key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame($expectedCompression, $info['Compression']);
+ $this->assertSame(
+ $expectedMergedSketchValues,
+ $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testMergedSketchHaveCompressionEqualMaxValueAmongAllSourceSketches(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('source-key1');
+ $redis->tdigestcreate('source-key2', 1000);
+
+ $redis->tdigestadd('source-key1', 1, 2);
+ $redis->tdigestadd('source-key2', 3, 4);
+
+ $actualResponse = $redis->tdigestmerge('destination-key', ['source-key1', 'source-key2']);
+ $info = $redis->tdigestinfo('destination-key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame(1000, $info['Compression']);
+ $this->assertSame(
+ ['1', '2', '3', '4', 'inf'],
+ $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testMergeOverrideAlreadyExistingSketchWithOverrideModifier(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('source-key1');
+ $redis->tdigestcreate('source-key2');
+ $redis->tdigestcreate('destination-key');
+
+ $redis->tdigestadd('source-key1', 1, 2);
+ $redis->tdigestadd('source-key2', 3, 4);
+ $redis->tdigestadd('destination-key', 5, 6, 7, 8);
+
+ $this->assertSame(
+ ['5', '6', '7', '8', 'inf'],
+ $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4)
+ );
+
+ $actualResponse = $redis->tdigestmerge(
+ 'destination-key',
+ ['source-key1', 'source-key2'],
+ 0,
+ true
+ );
+ $info = $redis->tdigestinfo('destination-key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame(100, $info['Compression']);
+ $this->assertSame(
+ ['1', '2', '3', '4', 'inf'],
+ $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testMergeWithAlreadyExistingSketchIfNoOverrideModifierGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('source-key1');
+ $redis->tdigestcreate('source-key2');
+ $redis->tdigestcreate('destination-key');
+
+ $redis->tdigestadd('source-key1', 1, 2);
+ $redis->tdigestadd('source-key2', 3, 4);
+ $redis->tdigestadd('destination-key', 5, 6, 7, 8);
+
+ $actualResponse = $redis->tdigestmerge('destination-key', ['source-key1', 'source-key2']);
+ $info = $redis->tdigestinfo('destination-key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame(100, $info['Compression']);
+ $this->assertSame(
+ ['1', '2', '3', '4', '5', '6', '7', '8', 'inf'],
+ $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4, 5, 6, 7, 8)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingSourceKeyGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestmerge('destination-key', ['source-key1', 'source-key2']);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', ['source-key1', 'source-key2']],
+ ['key', 2, 'source-key1', 'source-key2'],
+ ],
+ 'with COMPRESSION argument' => [
+ ['key', ['source-key1', 'source-key2'], 10],
+ ['key', 2, 'source-key1', 'source-key2', 'COMPRESSION', 10],
+ ],
+ 'with OVERRIDE modifier' => [
+ ['key', ['source-key1', 'source-key2'], 0, true],
+ ['key', 2, 'source-key1', 'source-key2', 'OVERRIDE'],
+ ],
+ 'with all arguments' => [
+ ['key', ['source-key1', 'source-key2'], 10, true],
+ ['key', 2, 'source-key1', 'source-key2', 'COMPRESSION', 10, 'OVERRIDE'],
+ ],
+ ];
+ }
+
+ public function sketchesProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ 'source-key1',
+ 'source-key2',
+ ['source-key1', 1, 2],
+ ['source-key2', 3, 4],
+ ['destination-key', ['source-key1', 'source-key2']],
+ 100,
+ ['1', '2', '3', '4', 'inf'],
+ ],
+ 'with non-default COMPRESSION' => [
+ 'source-key1',
+ 'source-key2',
+ ['source-key1', 1, 2],
+ ['source-key2', 3, 4],
+ ['destination-key', ['source-key1', 'source-key2'], 200],
+ 200,
+ ['1', '2', '3', '4', 'inf'],
+ ],
+ 'with OVERRIDE modifier' => [
+ 'source-key1',
+ 'source-key2',
+ ['source-key1', 1, 2],
+ ['source-key2', 3, 4],
+ ['destination-key', ['source-key1', 'source-key2'], 0, true],
+ 100,
+ ['1', '2', '3', '4', 'inf'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php
new file mode 100644
index 00000000..cab0cf19
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php
@@ -0,0 +1,96 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsMinValueFromGivenSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('key');
+ $redis->tdigestcreate('empty_key');
+
+ $redis->tdigestadd('key', 3, 2, 4, 5, 1);
+
+ $actualResponse = $redis->tdigestmin('key');
+
+ $this->assertSame('1', $actualResponse);
+ $this->assertSame('nan', $redis->tdigestmin('empty_key'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestmin('key');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php
new file mode 100644
index 00000000..2e76ab91
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php
@@ -0,0 +1,97 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsValuesBelowGivenQuantile(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('key', 1000);
+
+ $addResponse = $redis->tdigestadd('key', 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5);
+ $quantileResponse = $redis->tdigestquantile('key', 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0);
+
+ $this->assertEquals('OK', $addResponse);
+ $this->assertSame(['1', '2', '3', '3', '4', '4', '4', '5', '5', '5', '5'], $quantileResponse);
+
+ $redis->tdigestcreate('empty_key');
+ $this->assertSame(['nan', 'nan'], $redis->tdigestquantile('empty_key', 0.0, 0.1));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestquantile('key', 1, 2, 3);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTRANK_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTRANK_Test.php
new file mode 100644
index 00000000..b3e2502f
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTRANK_Test.php
@@ -0,0 +1,97 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsRankOfGivenObservationValue(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [-1, 0, 1, 2, 3];
+
+ $redis->tdigestcreate('key');
+ $redis->tdigestcreate('empty_key');
+
+ $redis->tdigestadd('key', 10, 20, 30);
+
+ $actualResponse = $redis->tdigestrank('key', 0, 10, 20, 30, 40);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame([-2, -2, -2], $redis->tdigestrank('empty_key', 1, 2, 3));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestrank('key', 1, 2, 3);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php
new file mode 100644
index 00000000..1a2a0ca1
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php
@@ -0,0 +1,104 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testResetExistingSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('key', 500);
+ $redis->tdigestadd('key', 1, 2, 2, 3, 3, 3);
+
+ $this->assertSame(
+ ['1', '2', '2', '3', '3', '3'],
+ $redis->tdigestbyrank('key', 0, 1, 2, 3, 4, 5)
+ );
+
+ $actualResponse = $redis->tdigestreset('key');
+ $info = $redis->tdigestinfo('key');
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame(500, $info['Compression']);
+ $this->assertSame(
+ ['nan', 'nan', 'nan', 'nan', 'nan', 'nan'],
+ $redis->tdigestbyrank('key', 0, 1, 2, 3, 4, 5)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestreset('key');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTREVRANK_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTREVRANK_Test.php
new file mode 100644
index 00000000..67b4799d
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTREVRANK_Test.php
@@ -0,0 +1,97 @@
+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
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsReversedRanksOfGivenObservationsValue(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [3, 2, 1, 0, -1];
+
+ $redis->tdigestcreate('key');
+ $redis->tdigestcreate('empty_key');
+
+ $redis->tdigestadd('key', 10, 20, 30);
+
+ $actualResponse = $redis->tdigestrevrank('key', 0, 10, 20, 30, 40);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ $this->assertSame([-2, -2, -2], $redis->tdigestrank('empty_key', 1, 2, 3));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigestrevrank('key', 1, 2, 3);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php
new file mode 100644
index 00000000..4d7127de
--- /dev/null
+++ b/tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php
@@ -0,0 +1,147 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider sketchesProvider
+ * @param array $addArguments
+ * @param string $key
+ * @param array $trimmedMeanArguments
+ * @param string $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsMeanValueWithinGivenQuantilesRange(
+ array $addArguments,
+ string $key,
+ array $trimmedMeanArguments,
+ string $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate($key);
+ $redis->tdigestadd(...$addArguments);
+
+ $actualResponse = $redis->tdigesttrimmed_mean(...$trimmedMeanArguments);
+
+ $this->assertSame($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testReturnsNanOnEmptySketchKey(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->tdigestcreate('key');
+
+ $actualResponse = $redis->tdigesttrimmed_mean('key', 0, 1);
+ $this->assertSame('nan', $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.4.0
+ */
+ public function testThrowsExceptionOnNonExistingTDigestSketch(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR T-Digest: key does not exist');
+
+ $redis->tdigesttrimmed_mean('key', 0, 1);
+ }
+
+ public function sketchesProvider(): array
+ {
+ return [
+ 'between low and high cut' => [
+ ['key', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ 'key',
+ ['key', 0.1, 0.6],
+ '4',
+ ],
+ 'without low cut' => [
+ ['key', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ 'key',
+ ['key', 0, 0.6],
+ '3.5',
+ ],
+ 'without high cut' => [
+ ['key', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ 'key',
+ ['key', 0.6, 1],
+ '8.5',
+ ],
+ 'without low and high cut' => [
+ ['key', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ 'key',
+ ['key', 0, 1],
+ '5.5',
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php
new file mode 100644
index 00000000..c4db6892
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php
@@ -0,0 +1,115 @@
+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 testAddSampleIntoTimeSeriesWithGivenConfiguration(): void
+ {
+ $redis = $this->getClient();
+
+ $createArguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $createArguments)
+ );
+
+ $addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsadd('temperature:2:32', 123123123123, 27, $addArguments)
+ );
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 123123121321, 1.0],
+ ['key', 123123121321, 1.0],
+ ],
+ 'with RETENTION modifier' => [
+ ['key', 123123121321, 1.0, (new AddArguments())->retentionMsecs(100)],
+ ['key', 123123121321, 1.0, 'RETENTION', 100],
+ ],
+ 'with ENCODING modifier' => [
+ ['key', 123123121321, 1.0, (new AddArguments())->encoding(CommonArguments::ENCODING_UNCOMPRESSED)],
+ ['key', 123123121321, 1.0, 'ENCODING', CommonArguments::ENCODING_UNCOMPRESSED],
+ ],
+ 'with CHUNK_SIZE modifier' => [
+ ['key', 123123121321, 1.0, (new AddArguments())->chunkSize(100)],
+ ['key', 123123121321, 1.0, 'CHUNK_SIZE', 100],
+ ],
+ 'with ON_DUPLICATE modifier' => [
+ ['key', 123123121321, 1.0, (new AddArguments())->onDuplicate(CommonArguments::POLICY_FIRST)],
+ ['key', 123123121321, 1.0, 'ON_DUPLICATE', CommonArguments::POLICY_FIRST],
+ ],
+ 'with all modifiers' => [
+ ['key', 123123121321, 1.0, (new AddArguments())->retentionMsecs(100)->encoding(CommonArguments::ENCODING_UNCOMPRESSED)->chunkSize(100)->onDuplicate(CommonArguments::POLICY_FIRST)],
+ ['key', 123123121321, 1.0, 'RETENTION', 100, 'ENCODING', CommonArguments::ENCODING_UNCOMPRESSED, 'CHUNK_SIZE', 100, 'ON_DUPLICATE', CommonArguments::POLICY_FIRST],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php
new file mode 100644
index 00000000..e8c5db63
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php
@@ -0,0 +1,127 @@
+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 testAlterUpdatesExistingTimeSeriesConfiguration(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $alterArguments = (new AlterArguments())
+ ->retentionMsecs(10000000);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tsalter('temperature:2:32', $alterArguments)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tsalter('non_existing_key');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key'],
+ ['key'],
+ ],
+ 'with RETENTION modifier' => [
+ ['key', (new AlterArguments())->retentionMsecs(100)],
+ ['key', 'RETENTION', 100],
+ ],
+ 'with CHUNK_SIZE modifier' => [
+ ['key', (new AlterArguments())->chunkSize(100)],
+ ['key', 'CHUNK_SIZE', 100],
+ ],
+ 'with DUPLICATE_POLICY modifier' => [
+ ['key', (new AlterArguments())->duplicatePolicy(CommonArguments::POLICY_FIRST)],
+ ['key', 'DUPLICATE_POLICY', CommonArguments::POLICY_FIRST],
+ ],
+ 'with all modifiers' => [
+ ['key', (new AlterArguments())->retentionMsecs(100)->chunkSize(100)->duplicatePolicy(CommonArguments::POLICY_FIRST)],
+ ['key', 'RETENTION', 100, 'CHUNK_SIZE', 100, 'DUPLICATE_POLICY', CommonArguments::POLICY_FIRST],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php
new file mode 100644
index 00000000..d3eab4e6
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php
@@ -0,0 +1,126 @@
+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 testCreateCompactionRuleWithinGivenTimeSeries(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->labels('type', 'temp', 'location', 'TLV');
+
+ $this->assertEquals('OK', $redis->tscreate('temp:TLV', $arguments));
+ $this->assertEquals('OK', $redis->tscreate('dailyAvgTemp:TLV', $arguments));
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'twa', 86400000)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingDestinationKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'twa', 86400000);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnWrongAggregationTypeGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->labels('type', 'temp', 'location', 'TLV');
+
+ $this->assertEquals('OK', $redis->tscreate('temp:TLV', $arguments));
+ $this->assertEquals('OK', $redis->tscreate('dailyAvgTemp:TLV', $arguments));
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: Unknown aggregation type');
+
+ $redis->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'wrong', 86400000);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['sourceKey', 'destKey', 'sum', 100000],
+ ['sourceKey', 'destKey', 'AGGREGATION', 'sum', 100000],
+ ],
+ 'with alignTimestamp argument' => [
+ ['sourceKey', 'destKey', 'sum', 100000, 10000000],
+ ['sourceKey', 'destKey', 'AGGREGATION', 'sum', 100000, 10000000],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php
new file mode 100644
index 00000000..62409d42
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php
@@ -0,0 +1,122 @@
+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())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tsinfo('non_existing_key');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key'],
+ ['key'],
+ ],
+ 'with RETENTION modifier' => [
+ ['key', (new CreateArguments())->retentionMsecs(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())->retentionMsecs(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],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php
new file mode 100644
index 00000000..b99888a7
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php
@@ -0,0 +1,184 @@
+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 testDecrByDecreasesValueAndTimestampOfExistingSample(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsadd('temperature:2:32', 123123123123, 27, $addArguments)
+ );
+
+ $this->assertEquals(
+ 123123123124,
+ $redis->tsdecrby('temperature:2:32', 1, (new DecrByArguments())->timestamp(123123123124))
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testDecrByCreateNewSampleIfNotExists(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsdecrby('temperature:2:32', 27, (new DecrByArguments())->timestamp(123123123123))
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnOlderTimestampGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsadd('temperature:2:32', 123123123123, 27, $addArguments)
+ );
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TSDB: for incrby/decrby, timestamp should be newer than the');
+
+ $redis->tsdecrby('temperature:2:32', 27, (new DecrByArguments())->timestamp(123123123122));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 1.0],
+ ['key', 1.0],
+ ],
+ 'with TIMESTAMP modifier' => [
+ ['key', 1.0, (new DecrByArguments())->timestamp(10)],
+ ['key', 1.0, 'TIMESTAMP', 10],
+ ],
+ 'with RETENTION modifier' => [
+ ['key', 1.0, (new DecrByArguments())->retentionMsecs(100)],
+ ['key', 1.0, 'RETENTION', 100],
+ ],
+ 'with UNCOMPRESSED modifier' => [
+ ['key', 1.0, (new DecrByArguments())->uncompressed()],
+ ['key', 1.0, 'UNCOMPRESSED'],
+ ],
+ 'with CHUNK_SIZE modifier' => [
+ ['key', 1.0, (new DecrByArguments())->chunkSize(100)],
+ ['key', 1.0, 'CHUNK_SIZE', 100],
+ ],
+ 'with LABELS modifier' => [
+ ['key', 1.0, (new DecrByArguments())->labels('label1', 1, 'label2', 2)],
+ ['key', 1.0, 'LABELS', 'label1', 1, 'label2', 2],
+ ],
+ 'with all modifiers' => [
+ ['key', 1.0, (new DecrByArguments())->timestamp(10)->retentionMsecs(100)->uncompressed()->chunkSize(100)->labels('label1', 1, 'label2', 2)],
+ ['key', 1.0, 'TIMESTAMP', 10, 'RETENTION', 100, 'UNCOMPRESSED', 'CHUNK_SIZE', 100, 'LABELS', 'label1', 1, 'label2', 2],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php
new file mode 100644
index 00000000..8b83840d
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php
@@ -0,0 +1,97 @@
+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 testDeleteCompactionRuleFromGivenTimeSeries(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->labels('type', 'temp', 'location', 'TLV');
+
+ $this->assertEquals('OK', $redis->tscreate('temp:TLV', $arguments));
+ $this->assertEquals('OK', $redis->tscreate('dailyAvgTemp:TLV', $arguments));
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'twa', 86400000)
+ );
+ $this->assertEquals(
+ 'OK',
+ $redis->tsdeleterule('temp:TLV', 'dailyAvgTemp:TLV')
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingDestinationKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tsdeleterule('temp:TLV', 'dailyAvgTemp:TLV');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php
new file mode 100644
index 00000000..671f44ce
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php
@@ -0,0 +1,116 @@
+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.6.0
+ */
+ public function testDelRemovesSamplesWithinGivenTimestampsRange(): void
+ {
+ $redis = $this->getClient();
+
+ $createArguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $createArguments)
+ );
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsadd('temperature:2:32', 123123123123, 27)
+ );
+
+ $this->assertEquals(
+ 123123123124,
+ $redis->tsadd('temperature:2:32', 123123123124, 28)
+ );
+
+ $this->assertEquals(
+ 123123123125,
+ $redis->tsadd('temperature:2:32', 123123123125, 29)
+ );
+
+ $this->assertSame(
+ 2,
+ $redis->tsdel('temperature:2:32', 123123123124, 123123123125)
+ );
+
+ $this->assertEquals([123123123123, '27'], $redis->tsget('temperature:2:32'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.6.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tsdel('non_existing_key', 100, 101);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php
new file mode 100644
index 00000000..099a9f28
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php
@@ -0,0 +1,124 @@
+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 testGetSampleWithHighestTimestampFromGivenTimeSeries(): void
+ {
+ $redis = $this->getClient();
+
+ $createArguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $createArguments)
+ );
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsadd('temperature:2:32', 123123123123, 27)
+ );
+
+ $this->assertEquals(
+ 123123123124,
+ $redis->tsadd('temperature:2:32', 123123123124, 28)
+ );
+
+ $this->assertEquals(
+ 123123123125,
+ $redis->tsadd('temperature:2:32', 123123123125, 29)
+ );
+
+ $this->assertEquals([123123123125, '29'], $redis->tsget('temperature:2:32'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tsget('non_existing_key');
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key'],
+ ['key'],
+ ],
+ 'with LATEST modifier' => [
+ ['key', (new GetArguments())->latest()],
+ ['key', 'LATEST'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php
new file mode 100644
index 00000000..95add6c9
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php
@@ -0,0 +1,184 @@
+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 testIncrByIncreasesValueAndTimestampOfExistingSample(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsadd('temperature:2:32', 123123123123, 27, $addArguments)
+ );
+
+ $this->assertEquals(
+ 123123123124,
+ $redis->tsincrby('temperature:2:32', 28, (new IncrByArguments())->timestamp(123123123124))
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testIncrByCreateNewSampleIfNotExists(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsincrby('temperature:2:32', 27, (new IncrByArguments())->timestamp(123123123123))
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnOlderTimestampGiven(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $addArguments = (new AddArguments())
+ ->retentionMsecs(31536000000);
+
+ $this->assertEquals(
+ 123123123123,
+ $redis->tsadd('temperature:2:32', 123123123123, 27, $addArguments)
+ );
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TSDB: for incrby/decrby, timestamp should be newer than the');
+
+ $redis->tsincrby('temperature:2:32', 27, (new IncrByArguments())->timestamp(123123123122));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 1.0],
+ ['key', 1.0],
+ ],
+ 'with TIMESTAMP modifier' => [
+ ['key', 1.0, (new IncrByArguments())->timestamp(10)],
+ ['key', 1.0, 'TIMESTAMP', 10],
+ ],
+ 'with RETENTION modifier' => [
+ ['key', 1.0, (new IncrByArguments())->retentionMsecs(100)],
+ ['key', 1.0, 'RETENTION', 100],
+ ],
+ 'with UNCOMPRESSED modifier' => [
+ ['key', 1.0, (new IncrByArguments())->uncompressed()],
+ ['key', 1.0, 'UNCOMPRESSED'],
+ ],
+ 'with CHUNK_SIZE modifier' => [
+ ['key', 1.0, (new IncrByArguments())->chunkSize(100)],
+ ['key', 1.0, 'CHUNK_SIZE', 100],
+ ],
+ 'with LABELS modifier' => [
+ ['key', 1.0, (new IncrByArguments())->labels('label1', 1, 'label2', 2)],
+ ['key', 1.0, 'LABELS', 'label1', 1, 'label2', 2],
+ ],
+ 'with all modifiers' => [
+ ['key', 1.0, (new IncrByArguments())->timestamp(10)->retentionMsecs(100)->uncompressed()->chunkSize(100)->labels('label1', 1, 'label2', 2)],
+ ['key', 1.0, 'TIMESTAMP', 10, 'RETENTION', 100, 'UNCOMPRESSED', 'CHUNK_SIZE', 100, 'LABELS', 'label1', 1, 'label2', 2],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php
new file mode 100644
index 00000000..1720cbc4
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php
@@ -0,0 +1,96 @@
+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 testReturnsInformationAboutGivenTimeSeries(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = ['totalSamples', 0, 'memoryUsage', 4239, 'firstTimestamp', 0, 'lastTimestamp', 0,
+ 'retentionTime', 60000, 'chunkCount', 1, 'chunkSize', 4096, 'chunkType', 'compressed', 'duplicatePolicy',
+ 'max', 'labels', [['sensor_id', '2'], ['area_id', '32']], 'sourceKey', null, 'rules', []];
+
+ $arguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $arguments)
+ );
+
+ $this->assertEquals($expectedResponse, $redis->tsinfo('temperature:2:32'));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key'],
+ ['key'],
+ ],
+ 'with DEBUG modifier' => [
+ ['key', (new InfoArguments())->debug()],
+ ['key', 'DEBUG'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php
new file mode 100644
index 00000000..649139b5
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php
@@ -0,0 +1,104 @@
+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 testAddSamplesIntoFewTimeSeries(): void
+ {
+ $redis = $this->getClient();
+
+ $createArguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $createArguments)
+ );
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:33', $createArguments)
+ );
+
+ $this->assertEquals(
+ [123123123123, 123123123124],
+ $redis->tsmadd('temperature:2:32', 123123123123, 27, 'temperature:2:33', 123123123124, 28)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonWrongArgumentsNumber(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage("ERR wrong number of arguments for 'TS.MADD' command");
+
+ $redis->tsmadd('temperature:2:32', 123123123123, 27, 'temperature:2:33', 123123123124);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php
new file mode 100644
index 00000000..6b7acc47
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php
@@ -0,0 +1,120 @@
+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 testGetSampleFromMultipleTimeSeriesMatchingGivenPattern(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [
+ ['temperature:2:32', [['type', 'temp']], [123123123123, '27']],
+ ['temperature:2:33', [['type', 'temp']], [123123123124, '27']],
+ ];
+
+ $createArguments = (new CreateArguments())
+ ->retentionMsecs(60000)
+ ->duplicatePolicy(CommonArguments::POLICY_MAX)
+ ->labels('type', 'temp', 'sensor_id', 2, 'area_id', 32);
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:32', $createArguments)
+ );
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('temperature:2:33', $createArguments)
+ );
+
+ $redis->tsadd('temperature:2:32', 123123123123, 27);
+ $redis->tsadd('temperature:2:33', 123123123124, 27);
+
+ $this->assertEquals(
+ $expectedResponse,
+ $redis->tsmget((new MGetArguments())->selectedLabels('type'), 'type=temp')
+ );
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ [(new MGetArguments())->withLabels(), 'filterExpression1', 'filterExpression2'],
+ ['WITHLABELS', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with LATEST modifier' => [
+ [(new MGetArguments())->latest(), 'filterExpression1', 'filterExpression2'],
+ ['LATEST', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with WITHLABELS modifier' => [
+ [(new MGetArguments())->withLabels(), 'filterExpression1', 'filterExpression2'],
+ ['WITHLABELS', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with SELECTED_LABELS modifier' => [
+ [(new MGetArguments())->selectedLabels('label1', 'label2'), 'filterExpression1', 'filterExpression2'],
+ ['SELECTED_LABELS', 'label1', 'label2', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with all modifiers' => [
+ [(new MGetArguments())->latest()->selectedLabels('label1', 'label2'), 'filterExpression1', 'filterExpression2'],
+ ['LATEST', 'SELECTED_LABELS', 'label1', 'label2', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php
new file mode 100644
index 00000000..25788b5e
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php
@@ -0,0 +1,163 @@
+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 testQueryRangeAcrossMultipleTimeSeriesInForwardDirection(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [
+ [
+ 'type=stock',
+ [
+ ['type', 'stock'],
+ ['__reducer__', 'max'],
+ ['__source__', 'stock:A,stock:B'],
+ ],
+ [
+ [1000, '120'],
+ [1010, '110'],
+ [1020, '120'],
+ ],
+ ],
+ ];
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A'))
+ );
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B'))
+ );
+ $this->assertSame(
+ [1000, 1010, 1020],
+ $redis->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120)
+ );
+ $this->assertSame(
+ [1000, 1010, 1020],
+ $redis->tsmadd('stock:B', 1000, 120, 'stock:B', 1010, 110, 'stock:B', 1020, 100)
+ );
+
+ $mrangeArguments = (new MRangeArguments())
+ ->withLabels()
+ ->filter('type=stock')
+ ->groupBy('type', 'max');
+
+ $this->assertEquals($expectedResponse, $redis->tsmrange('-', '+', $mrangeArguments));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ [1000, 1001, (new MRangeArguments())->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with LATEST modifier' => [
+ [1000, 1001, (new MRangeArguments())->latest()->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'LATEST', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with FILTER_BY_TS modifier' => [
+ [1000, 1001, (new MRangeArguments())->filterByTs(1000, 1001)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'FILTER_BY_TS', 1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with FILTER_BY_VALUE modifier' => [
+ [1000, 1001, (new MRangeArguments())->filterByValue(1000, 1001)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with WITHLABELS modifier' => [
+ [1000, 1001, (new MRangeArguments())->withLabels()->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'WITHLABELS', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with SELECTED_LABELS modifier' => [
+ [1000, 1001, (new MRangeArguments())->selectedLabels('label1', 'label2')->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'SELECTED_LABELS', 'label1', 'label2', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with COUNT modifier' => [
+ [1000, 1001, (new MRangeArguments())->count(2)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'COUNT', 2, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - default arguments' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'AGGREGATION', 'sum', 2, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - with ALIGN' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2, 2)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'ALIGN', 2, 'AGGREGATION', 'sum', 2, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - with BUCKETTIMESTAMP' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2, 0, 10000)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'AGGREGATION', 'sum', 2, 'BUCKETTIMESTAMP', 10000, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - with EMPTY' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2, 0, 0, true)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'AGGREGATION', 'sum', 2, 'EMPTY', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with GROUPBY modifier' => [
+ [1000, 1001, (new MRangeArguments())->filter('filterExpression1', 'filterExpression2')->groupBy('label', 'reducer')],
+ [1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2', 'GROUPBY', 'label', 'REDUCE', 'reducer'],
+ ],
+ 'with all modifiers' => [
+ [1000, 1001, (new MRangeArguments())->latest()->filterByTs(1000, 1001)->filterByValue(1000, 1001)->withLabels()->count(2)->aggregation('sum', 2)->filter('filterExpression1', 'filterExpression2')->groupBy('label', 'reducer'), 'filterExpression1', 'filterExpression2'],
+ [1000, 1001, 'LATEST', 'FILTER_BY_TS', 1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'WITHLABELS', 'COUNT', 2, 'AGGREGATION', 'sum', 2, 'FILTER', 'filterExpression1', 'filterExpression2', 'GROUPBY', 'label', 'REDUCE', 'reducer'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php
new file mode 100644
index 00000000..214a4476
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php
@@ -0,0 +1,163 @@
+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.4.0
+ */
+ public function testQueryRangeAcrossMultipleTimeSeriesInReverseDirection(): void
+ {
+ $redis = $this->getClient();
+ $expectedResponse = [
+ [
+ 'type=stock',
+ [
+ ['type', 'stock'],
+ ['__reducer__', 'max'],
+ ['__source__', 'stock:A,stock:B'],
+ ],
+ [
+ [1020, '120'],
+ [1010, '110'],
+ [1000, '120'],
+ ],
+ ],
+ ];
+
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A'))
+ );
+ $this->assertEquals(
+ 'OK',
+ $redis->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B'))
+ );
+ $this->assertSame(
+ [1000, 1010, 1020],
+ $redis->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120)
+ );
+ $this->assertSame(
+ [1000, 1010, 1020],
+ $redis->tsmadd('stock:B', 1000, 120, 'stock:B', 1010, 110, 'stock:B', 1020, 100)
+ );
+
+ $mrangeArguments = (new MRangeArguments())
+ ->withLabels()
+ ->filter('type=stock')
+ ->groupBy('type', 'max');
+
+ $this->assertEquals($expectedResponse, $redis->tsmrevrange('-', '+', $mrangeArguments));
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ [1000, 1001, (new MRangeArguments())->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with LATEST modifier' => [
+ [1000, 1001, (new MRangeArguments())->latest()->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'LATEST', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with FILTER_BY_TS modifier' => [
+ [1000, 1001, (new MRangeArguments())->filterByTs(1000, 1001)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'FILTER_BY_TS', 1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with FILTER_BY_VALUE modifier' => [
+ [1000, 1001, (new MRangeArguments())->filterByValue(1000, 1001)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with WITHLABELS modifier' => [
+ [1000, 1001, (new MRangeArguments())->withLabels()->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'WITHLABELS', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with SELECTED_LABELS modifier' => [
+ [1000, 1001, (new MRangeArguments())->selectedLabels('label1', 'label2')->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'SELECTED_LABELS', 'label1', 'label2', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with COUNT modifier' => [
+ [1000, 1001, (new MRangeArguments())->count(2)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'COUNT', 2, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - default arguments' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'AGGREGATION', 'sum', 2, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - with ALIGN' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2, 2)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'ALIGN', 2, 'AGGREGATION', 'sum', 2, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - with BUCKETTIMESTAMP' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2, 0, 10000)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'AGGREGATION', 'sum', 2, 'BUCKETTIMESTAMP', 10000, 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with AGGREGATION modifier - with EMPTY' => [
+ [1000, 1001, (new MRangeArguments())->aggregation('sum', 2, 0, 0, true)->filter('filterExpression1', 'filterExpression2')],
+ [1000, 1001, 'AGGREGATION', 'sum', 2, 'EMPTY', 'FILTER', 'filterExpression1', 'filterExpression2'],
+ ],
+ 'with GROUPBY modifier' => [
+ [1000, 1001, (new MRangeArguments())->filter('filterExpression1', 'filterExpression2')->groupBy('label', 'reducer')],
+ [1000, 1001, 'FILTER', 'filterExpression1', 'filterExpression2', 'GROUPBY', 'label', 'REDUCE', 'reducer'],
+ ],
+ 'with all modifiers' => [
+ [1000, 1001, (new MRangeArguments())->latest()->filterByTs(1000, 1001)->filterByValue(1000, 1001)->withLabels()->count(2)->aggregation('sum', 2)->filter('filterExpression1', 'filterExpression2')->groupBy('label', 'reducer'), 'filterExpression1', 'filterExpression2'],
+ [1000, 1001, 'LATEST', 'FILTER_BY_TS', 1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'WITHLABELS', 'COUNT', 2, 'AGGREGATION', 'sum', 2, 'FILTER', 'filterExpression1', 'filterExpression2', 'GROUPBY', 'label', 'REDUCE', 'reducer'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php
new file mode 100644
index 00000000..927875c9
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php
@@ -0,0 +1,87 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testQueryReturnsKeysMatchingGivenFilterExpression(): void
+ {
+ $redis = $this->getClient();
+
+ $arguments = (new CreateArguments())
+ ->labels('type', 'temp', 'location', 'TLV');
+
+ $this->assertEquals('OK', $redis->tscreate('temp:TLV', $arguments));
+
+ $anotherArguments = (new CreateArguments())
+ ->labels('type', 'temp', 'location', 'JER');
+
+ $this->assertEquals('OK', $redis->tscreate('temp:JER', $anotherArguments));
+
+ $this->assertSame(['temp:TLV'], $redis->tsqueryindex('location=TLV'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnInvalidFilterExpression(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: failed parsing labels');
+
+ $redis->tsqueryindex('wrong');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php
new file mode 100644
index 00000000..ae47a21f
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php
@@ -0,0 +1,142 @@
+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 testReturnsQueriedRangeInForwardDirectionFromGivenTimeSeries(): void
+ {
+ $redis = $this->getClient();
+
+ $createArguments = (new CreateArguments())->labels('type', 'temp', 'location', 'TLV');
+ $this->assertEquals('OK', $redis->tscreate('temp:TLV', $createArguments));
+
+ $this->assertSame(
+ [1000, 1010, 1020, 1030],
+ $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 9999, 'temp:TLV', 1030, 40)
+ );
+
+ $rangeArguments = (new RangeArguments())->filterByValue(-100, 100);
+ $this->assertEquals(
+ [[1000, '30'], [1010, '35'], [1030, '40']],
+ $redis->tsrange('temp:TLV', '-', '+', $rangeArguments)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tsrange('non_existing_key', 1000, 1000);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 10000, 10001],
+ ['key', 10000, 10001],
+ ],
+ 'with LATEST modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->latest()],
+ ['key', 10000, 10001, 'LATEST'],
+ ],
+ 'with FILTER_BY_TS modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->filterByTs(1000, 1001)],
+ ['key', 10000, 10001, 'FILTER_BY_TS', 1000, 1001],
+ ],
+ 'with FILTER_BY_VALUE modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->filterByValue(1000, 1001)],
+ ['key', 10000, 10001, 'FILTER_BY_VALUE', 1000, 1001],
+ ],
+ 'with COUNT modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->count(100)],
+ ['key', 10000, 10001, 'COUNT', 100],
+ ],
+ 'with AGGREGATION modifier - default arguments' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100)],
+ ['key', 10000, 10001, 'AGGREGATION', 'sum', 100],
+ ],
+ 'with AGGREGATION modifier - with ALIGN' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100, 100)],
+ ['key', 10000, 10001, 'ALIGN', 100, 'AGGREGATION', 'sum', 100],
+ ],
+ 'with AGGREGATION modifier - with BUCKETTIMESTAMP' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100, 0, 1000)],
+ ['key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'BUCKETTIMESTAMP', 1000],
+ ],
+ 'with AGGREGATION modifier - with EMPTY' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100, 0, 0, true)],
+ ['key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'EMPTY'],
+ ],
+ 'with all modifiers' => [
+ ['key', 10000, 10001, (new RangeArguments())->latest()->filterByTs(1000, 1001)->filterByValue(1000, 1001)->count(100)->aggregation('sum', 100)],
+ ['key', 10000, 10001, 'LATEST', 'FILTER_BY_TS', 1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'COUNT', 100, 'AGGREGATION', 'sum', 100],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php
new file mode 100644
index 00000000..b607f608
--- /dev/null
+++ b/tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php
@@ -0,0 +1,142 @@
+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 testReturnsQueriedRangeInReverseDirectionFromGivenTimeSeries(): void
+ {
+ $redis = $this->getClient();
+
+ $createArguments = (new CreateArguments())->labels('type', 'temp', 'location', 'TLV');
+ $this->assertEquals('OK', $redis->tscreate('temp:TLV', $createArguments));
+
+ $this->assertSame(
+ [1000, 1010, 1020, 1030],
+ $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 9999, 'temp:TLV', 1030, 40)
+ );
+
+ $rangeArguments = (new RangeArguments())->filterByValue(-100, 100);
+ $this->assertEquals(
+ [[1030, '40'], [1010, '35'], [1000, '30']],
+ $redis->tsrevrange('temp:TLV', '-', '+', $rangeArguments)
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisTimeSeriesVersion >= 1.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('ERR TSDB: the key does not exist');
+
+ $redis->tsrevrange('non_existing_key', 1000, 1000);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 10000, 10001],
+ ['key', 10000, 10001],
+ ],
+ 'with LATEST modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->latest()],
+ ['key', 10000, 10001, 'LATEST'],
+ ],
+ 'with FILTER_BY_TS modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->filterByTs(1000, 1001)],
+ ['key', 10000, 10001, 'FILTER_BY_TS', 1000, 1001],
+ ],
+ 'with FILTER_BY_VALUE modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->filterByValue(1000, 1001)],
+ ['key', 10000, 10001, 'FILTER_BY_VALUE', 1000, 1001],
+ ],
+ 'with COUNT modifier' => [
+ ['key', 10000, 10001, (new RangeArguments())->count(100)],
+ ['key', 10000, 10001, 'COUNT', 100],
+ ],
+ 'with AGGREGATION modifier - default arguments' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100)],
+ ['key', 10000, 10001, 'AGGREGATION', 'sum', 100],
+ ],
+ 'with AGGREGATION modifier - with ALIGN' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100, 100)],
+ ['key', 10000, 10001, 'ALIGN', 100, 'AGGREGATION', 'sum', 100],
+ ],
+ 'with AGGREGATION modifier - with BUCKETTIMESTAMP' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100, 0, 1000)],
+ ['key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'BUCKETTIMESTAMP', 1000],
+ ],
+ 'with AGGREGATION modifier - with EMPTY' => [
+ ['key', 10000, 10001, (new RangeArguments())->aggregation('sum', 100, 0, 0, true)],
+ ['key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'EMPTY'],
+ ],
+ 'with all modifiers' => [
+ ['key', 10000, 10001, (new RangeArguments())->latest()->filterByTs(1000, 1001)->filterByValue(1000, 1001)->count(100)->aggregation('sum', 100)],
+ ['key', 10000, 10001, 'LATEST', 'FILTER_BY_TS', 1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'COUNT', 100, 'AGGREGATION', 'sum', 100],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TopK/TOPKADD_Test.php b/tests/Predis/Command/Redis/TopK/TOPKADD_Test.php
new file mode 100644
index 00000000..19dd7e03
--- /dev/null
+++ b/tests/Predis/Command/Redis/TopK/TOPKADD_Test.php
@@ -0,0 +1,114 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider structuresProvider
+ * @param array $reserveArguments
+ * @param array $addArguments
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testAddItemsIntoGivenTopKStructure(
+ array $reserveArguments,
+ array $addArguments,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->topkreserve(...$reserveArguments);
+
+ $actualResponse = $redis->topkadd(...$addArguments);
+ $this->assertEquals($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TopK: key does not exist');
+
+ $redis->topkadd('key', 0, 1);
+ }
+
+ public function structuresProvider(): array
+ {
+ return [
+ 'without dropped items' => [
+ ['key', 4],
+ ['key', 0, 1, 2],
+ [null, null, null],
+ ],
+ 'with dropped items' => [
+ ['key', 2],
+ ['key', 0, 1, 2],
+ [null, null, '0'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TopK/TOPKINCRBY_Test.php b/tests/Predis/Command/Redis/TopK/TOPKINCRBY_Test.php
new file mode 100644
index 00000000..06f605f2
--- /dev/null
+++ b/tests/Predis/Command/Redis/TopK/TOPKINCRBY_Test.php
@@ -0,0 +1,94 @@
+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
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testIncrementItemsScoreOnGivenAmount(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->topkreserve('key', 2);
+ $this->assertSame([null, null], $redis->topkadd('key', 'item1', 'item2'));
+
+ $actualResponse = $redis->topkincrby('key', 'item1', 1, 'item2', 2, 'item3', 3);
+
+ $this->assertEquals([null, null, 'item1'], $actualResponse);
+ $this->assertEquals(['item2' => 3, 'item3' => 3], $redis->topklist('key', true));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TopK: key does not exist');
+
+ $redis->topkincrby('key', 'item', 1);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php b/tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php
new file mode 100644
index 00000000..2b83eb06
--- /dev/null
+++ b/tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php
@@ -0,0 +1,96 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $actualResponse = ['k', 50, 'width', 8, 'depth', 7, 'decay', '0.90000000000000002'];
+ $expectedResponse = ['k' => 50, 'width' => 8, 'depth' => 7, 'decay' => '0.90000000000000002'];
+
+ $this->assertSame($expectedResponse, $this->getCommand()->parseResponse($actualResponse));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testReturnsInfoAboutGivenTopKStructure(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->topkreserve('key', 50);
+
+ $this->assertSame(
+ ['k' => 50, 'width' => 8, 'depth' => 7, 'decay' => '0.90000000000000002'],
+ $redis->topkinfo('key')
+ );
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TopK: key does not exist');
+
+ $redis->topkinfo('key');
+ }
+}
diff --git a/tests/Predis/Command/Redis/TopK/TOPKLIST_Test.php b/tests/Predis/Command/Redis/TopK/TOPKLIST_Test.php
new file mode 100644
index 00000000..795a0de3
--- /dev/null
+++ b/tests/Predis/Command/Redis/TopK/TOPKLIST_Test.php
@@ -0,0 +1,156 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ * @dataProvider responsesProvider
+ */
+ public function testParseResponse(array $arguments, array $actualResponse, array $expectedResponse): void
+ {
+ $command = $this->getCommand();
+ $command->setArguments($arguments);
+
+ $this->assertSame($expectedResponse, $command->parseResponse($actualResponse));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider structureProvider
+ * @param array $reserveArguments
+ * @param array $addArguments
+ * @param array $listArguments
+ * @param array $expectedResponse
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testReturnsListOfItemsWithinTopKStructure(
+ array $reserveArguments,
+ array $addArguments,
+ array $listArguments,
+ array $expectedResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $redis->topkreserve(...$reserveArguments);
+ $redis->topkadd(...$addArguments);
+
+ $actualResponse = $redis->topklist(...$listArguments);
+
+ $this->assertEquals($expectedResponse, $actualResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TopK: key does not exist');
+
+ $redis->topklist('key', true);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default argument' => [
+ ['key'],
+ ['key'],
+ ],
+ 'with WITHCOUNT modifier equals true' => [
+ ['key', true],
+ ['key', 'WITHCOUNT'],
+ ],
+ 'with WITHCOUNT modifier equals false' => [
+ ['key', false],
+ ['key'],
+ ],
+ ];
+ }
+
+ public function responsesProvider(): array
+ {
+ return [
+ 'without WITHCOUNT modifier' => [
+ ['foo'],
+ ['foo', 'bar'],
+ ['foo', 'bar'],
+ ],
+ 'with WITHCOUNT modifier' => [
+ ['foo', true],
+ ['foo', 12, 'bar', 13],
+ ['foo' => 12, 'bar' => 13],
+ ],
+ ];
+ }
+
+ public function structureProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 50],
+ ['key', 1, 2, 2, 3, 3, 3],
+ ['key', false],
+ ['3', '2', '1'],
+ ],
+ 'with WITHCOUNT modifier' => [
+ ['key', 50],
+ ['key', 1, 2, 2, 3, 3, 3],
+ ['key', true],
+ ['3' => 3, '2' => 2, '1' => 1],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Redis/TopK/TOPKQUERY_Test.php b/tests/Predis/Command/Redis/TopK/TOPKQUERY_Test.php
new file mode 100644
index 00000000..721150e0
--- /dev/null
+++ b/tests/Predis/Command/Redis/TopK/TOPKQUERY_Test.php
@@ -0,0 +1,91 @@
+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
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testQueryChecksIfItemExistsWithinGivenTopKStructure(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->topkreserve('key', 50);
+ $redis->topkadd('key', 'item1');
+
+ $this->assertSame([1, 0], $redis->topkquery('key', 'item1', 'item2'));
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnNonExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TopK: key does not exist');
+
+ $redis->topkquery('key', 0, 1);
+ }
+}
diff --git a/tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php b/tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php
new file mode 100644
index 00000000..66ae6739
--- /dev/null
+++ b/tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php
@@ -0,0 +1,152 @@
+getCommand();
+ $command->setArguments($actualArguments);
+
+ $this->assertSameValues($expectedArguments, $command->getArguments());
+ }
+
+ /**
+ * @group disconnected
+ */
+ public function testParseResponse(): void
+ {
+ $this->assertSame(1, $this->getCommand()->parseResponse(1));
+ }
+
+ /**
+ * @group connected
+ * @dataProvider structureProvider
+ * @param array $topKArguments
+ * @param string $key
+ * @param array $expectedInfoResponse
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testReserveInitializeTopKStructureWithGivenConfiguration(
+ array $topKArguments,
+ string $key,
+ array $expectedInfoResponse
+ ): void {
+ $redis = $this->getClient();
+
+ $actualResponse = $redis->topkreserve(...$topKArguments);
+ $actualInfoResponse = $redis->topkinfo($key);
+
+ $this->assertEquals('OK', $actualResponse);
+ $this->assertSame($expectedInfoResponse, $actualInfoResponse);
+ }
+
+ /**
+ * @group connected
+ * @return void
+ * @requiresRedisBfVersion >= 2.0.0
+ */
+ public function testThrowsExceptionOnAlreadyExistingKey(): void
+ {
+ $redis = $this->getClient();
+
+ $redis->topkreserve('key', 50);
+
+ $this->expectException(ServerException::class);
+ $this->expectExceptionMessage('TopK: key already exists');
+
+ $redis->topkreserve('key', 50);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with default arguments' => [
+ ['key', 10],
+ ['key', 10],
+ ],
+ 'with non-default width' => [
+ ['key', 10, 5],
+ ['key', 10, 5, 7, 0.9],
+ ],
+ 'with non-default depth' => [
+ ['key', 10, 8, 9],
+ ['key', 10, 8, 9, 0.9],
+ ],
+ 'with non-default decay' => [
+ ['key', 10, 8, 7, 0.8],
+ ['key', 10, 8, 7, 0.8],
+ ],
+ ];
+ }
+
+ public function structureProvider(): array
+ {
+ return [
+ 'with default configuration' => [
+ ['key', 50],
+ 'key',
+ ['k' => 50, 'width' => 8, 'depth' => 7, 'decay' => '0.90000000000000002'],
+ ],
+ 'with non-default width' => [
+ ['key', 50, 9],
+ 'key',
+ ['k' => 50, 'width' => 9, 'depth' => 7, 'decay' => '0.90000000000000002'],
+ ],
+ 'with non-default depth' => [
+ ['key', 50, 8, 9],
+ 'key',
+ ['k' => 50, 'width' => 8, 'depth' => 9, 'decay' => '0.90000000000000002'],
+ ],
+ 'with non-default decay' => [
+ ['key', 50, 8, 7, 0.8],
+ 'key',
+ ['k' => 50, 'width' => 8, 'depth' => 7, 'decay' => '0.80000000000000004'],
+ ],
+ 'with all arguments non-default' => [
+ ['key', 50, 9, 6, 0.8],
+ 'key',
+ ['k' => 50, 'width' => 9, 'depth' => 6, 'decay' => '0.80000000000000004'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/RedisFactoryTest.php b/tests/Predis/Command/RedisFactoryTest.php
index 81cfac33..b043ffb1 100644
--- a/tests/Predis/Command/RedisFactoryTest.php
+++ b/tests/Predis/Command/RedisFactoryTest.php
@@ -450,6 +450,95 @@ class RedisFactoryTest extends PredisTestCase
156 => 'GEODIST',
157 => 'GEORADIUS',
158 => 'GEORADIUSBYMEMBER',
+ 159 => 'JSONSET',
+ 160 => 'JSONGET',
+ 161 => 'JSONARRAPPEND',
+ 162 => 'JSONARRINDEX',
+ 163 => 'JSONARRINSERT',
+ 164 => 'JSONARRLEN',
+ 165 => 'JSONARRPOP',
+ 166 => 'JSONARRTRIM',
+ 167 => 'JSONCLEAR',
+ 168 => 'JSONDEBUG',
+ 169 => 'JSONDEL',
+ 170 => 'JSONFORGET',
+ 171 => 'JSONMGET',
+ 172 => 'JSONNUMINCRBY',
+ 173 => 'JSONOBJKEYS',
+ 174 => 'JSONOBJLEN',
+ 175 => 'JSONRESP',
+ 176 => 'JSONSTRAPPEND',
+ 177 => 'JSONSTRLEN',
+ 178 => 'JSONTOGGLE',
+ 179 => 'JSONTYPE',
+ 180 => 'BFADD',
+ 181 => 'BFEXISTS',
+ 182 => 'BFINFO',
+ 183 => 'BFINSERT',
+ 184 => 'BFLOADCHUNK',
+ 185 => 'BFMADD',
+ 186 => 'BFMEXISTS',
+ 187 => 'BFRESERVE',
+ 188 => 'BFSCANDUMP',
+ 189 => 'CMSINCRBY',
+ 190 => 'CMSINFO',
+ 191 => 'CMSINITBYDIM',
+ 192 => 'CMSINITBYPROB',
+ 193 => 'CMSMERGE',
+ 194 => 'CMSQUERY',
+ 195 => 'CFADD',
+ 196 => 'CFADDNX',
+ 197 => 'CFCOUNT',
+ 198 => 'CFDEL',
+ 199 => 'CFEXISTS',
+ 200 => 'CFINFO',
+ 201 => 'CFINSERT',
+ 202 => 'CFINSERTNX',
+ 203 => 'CFLOADCHUNK',
+ 204 => 'CFMEXISTS',
+ 205 => 'CFRESERVE',
+ 206 => 'CFSCANDUMP',
+ 207 => 'TDIGESTADD',
+ 208 => 'TDIGESTBYRANK',
+ 209 => 'TDIGESTBYREVRANK',
+ 210 => 'TDIGESTCDF',
+ 211 => 'TDIGESTCREATE',
+ 212 => 'TDIGESTINFO',
+ 213 => 'TDIGESTMAX',
+ 214 => 'TDIGESTMERGE',
+ 215 => 'TDIGESTMIN',
+ 216 => 'TDIGESTQUANTILE',
+ 217 => 'TDIGESTRANK',
+ 218 => 'TDIGESTRESET',
+ 219 => 'TDIGESTREVRANK',
+ 220 => 'TDIGESTTRIMMED_MEAN',
+ 221 => 'TOPKADD',
+ 222 => 'TOPKINCRBY',
+ 223 => 'TOPKINFO',
+ 224 => 'TOPKLIST',
+ 225 => 'TOPKQUERY',
+ 226 => 'TOPKRESERVE',
+ 227 => 'FTALIASADD',
+ 228 => 'FTALIASDEL',
+ 229 => 'FTALIASUPDATE',
+ 230 => 'FTALTER',
+ 231 => 'FTCONFIG',
+ 232 => 'FTCREATE',
+ 233 => 'FTDICTADD',
+ 234 => 'FTDICTDEL',
+ 235 => 'FTDICTDUMP',
+ 236 => 'FTDROPINDEX',
+ 237 => 'FTINFO',
+ 238 => 'FTPROFILE',
+ 239 => 'FTSEARCH',
+ 240 => 'FTSPELLCHECK',
+ 241 => 'FTSUGADD',
+ 242 => 'FTSUGDEL',
+ 243 => 'FTSUGGET',
+ 244 => 'FTSUGLEN',
+ 245 => 'FTSYNDUMP',
+ 246 => 'FTSYNUPDATE',
+ 247 => 'FTTAGVALS',
];
}
}
diff --git a/tests/Predis/Command/Traits/BloomFilters/BucketSizeTest.php b/tests/Predis/Command/Traits/BloomFilters/BucketSizeTest.php
new file mode 100644
index 00000000..17cecf47
--- /dev/null
+++ b/tests/Predis/Command/Traits/BloomFilters/BucketSizeTest.php
@@ -0,0 +1,86 @@
+testClass = new class() extends RedisCommand {
+ use BucketSize;
+
+ public static $bucketSizeArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(int $offset, array $arguments, array $expectedResponse): void
+ {
+ $this->testClass::$bucketSizeArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($arguments);
+
+ $this->assertSameValues($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsErrorOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong bucket size argument value or position offset');
+
+ $this->testClass->setArguments([-5]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 1,
+ [],
+ [],
+ ],
+ 'with default argument' => [
+ 0,
+ [-1],
+ [false],
+ ],
+ 'with non-default argument' => [
+ 0,
+ [10],
+ ['BUCKETSIZE', 10],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/BloomFilters/CapacityTest.php b/tests/Predis/Command/Traits/BloomFilters/CapacityTest.php
new file mode 100644
index 00000000..4490c1e1
--- /dev/null
+++ b/tests/Predis/Command/Traits/BloomFilters/CapacityTest.php
@@ -0,0 +1,86 @@
+testClass = new class() extends RedisCommand {
+ use Capacity;
+
+ public static $capacityArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(int $offset, array $arguments, array $expectedResponse): void
+ {
+ $this->testClass::$capacityArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($arguments);
+
+ $this->assertSameValues($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsErrorOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong capacity argument value or position offset');
+
+ $this->testClass->setArguments([-5]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 1,
+ [],
+ [],
+ ],
+ 'with default argument' => [
+ 0,
+ [-1],
+ [false],
+ ],
+ 'with non-default argument' => [
+ 0,
+ [10],
+ ['CAPACITY', 10],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/BloomFilters/ErrorTest.php b/tests/Predis/Command/Traits/BloomFilters/ErrorTest.php
new file mode 100644
index 00000000..e8b25e6d
--- /dev/null
+++ b/tests/Predis/Command/Traits/BloomFilters/ErrorTest.php
@@ -0,0 +1,86 @@
+testClass = new class() extends RedisCommand {
+ use Error;
+
+ public static $errorArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(int $offset, array $arguments, array $expectedResponse): void
+ {
+ $this->testClass::$errorArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($arguments);
+
+ $this->assertSameValues($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsErrorOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong error argument value or position offset');
+
+ $this->testClass->setArguments([-5]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 1,
+ [],
+ [],
+ ],
+ 'with default argument' => [
+ 0,
+ [-1],
+ [false],
+ ],
+ 'with non-default argument' => [
+ 0,
+ [0.01],
+ ['ERROR', 0.01],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/BloomFilters/ExpansionTest.php b/tests/Predis/Command/Traits/BloomFilters/ExpansionTest.php
new file mode 100644
index 00000000..0e61641e
--- /dev/null
+++ b/tests/Predis/Command/Traits/BloomFilters/ExpansionTest.php
@@ -0,0 +1,72 @@
+testClass = new class() extends RedisCommand {
+ use Expansion;
+
+ public static $expansionArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $actualArguments
+ * @param array $expectedArguments
+ * @return void
+ */
+ public function testReturnsCorrectArguments(int $offset, array $actualArguments, array $expectedArguments): void
+ {
+ $this->testClass::$expansionArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($actualArguments);
+
+ $this->assertSame($expectedArguments, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong expansion argument value or position offset');
+
+ $this->testClass->setArguments([-2]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [2, ['argument1'], ['argument1']],
+ 'with value equals -1' => [0, [-1], [false]],
+ 'with correct value' => [0, [1], ['EXPANSION', 1]],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/BloomFilters/ItemsTest.php b/tests/Predis/Command/Traits/BloomFilters/ItemsTest.php
new file mode 100644
index 00000000..bf165ba1
--- /dev/null
+++ b/tests/Predis/Command/Traits/BloomFilters/ItemsTest.php
@@ -0,0 +1,69 @@
+testClass = new class() extends RedisCommand {
+ use Items;
+
+ public static $itemsArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(int $offset, array $arguments, array $expectedResponse): void
+ {
+ $this->testClass::$itemsArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($arguments);
+
+ $this->assertSameValues($expectedResponse, $this->testClass->getArguments());
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 1,
+ [],
+ [],
+ ],
+ 'with non-default argument' => [
+ 0,
+ ['item1', 'item2'],
+ ['ITEMS', 'item1', 'item2'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/BloomFilters/MaxIterationsTest.php b/tests/Predis/Command/Traits/BloomFilters/MaxIterationsTest.php
new file mode 100644
index 00000000..b2b04707
--- /dev/null
+++ b/tests/Predis/Command/Traits/BloomFilters/MaxIterationsTest.php
@@ -0,0 +1,86 @@
+testClass = new class() extends RedisCommand {
+ use MaxIterations;
+
+ public static $maxIterationsArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $arguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(int $offset, array $arguments, array $expectedResponse): void
+ {
+ $this->testClass::$maxIterationsArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($arguments);
+
+ $this->assertSameValues($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsErrorOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong max iterations argument value or position offset');
+
+ $this->testClass->setArguments([-5]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 1,
+ [],
+ [],
+ ],
+ 'with default argument' => [
+ 0,
+ [-1],
+ [false],
+ ],
+ 'with non-default argument' => [
+ 0,
+ [10],
+ ['MAXITERATIONS', 10],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php b/tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php
new file mode 100644
index 00000000..e3fb3c45
--- /dev/null
+++ b/tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php
@@ -0,0 +1,93 @@
+testClass = new class() extends RedisCommand {
+ use NoCreate;
+
+ public static $noCreateArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $actualArguments
+ * @param array $expectedArguments
+ * @return void
+ */
+ public function testReturnsCorrectArguments(int $offset, array $actualArguments, array $expectedArguments): void
+ {
+ $this->testClass::$noCreateArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($actualArguments);
+
+ $this->assertSame($expectedArguments, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnUnexpectedValue(): void
+ {
+ $this->testClass::$noCreateArgumentPositionOffset = 0;
+
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Wrong NOCREATE argument type');
+
+ $this->testClass->setArguments(['test']);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'NOCREATE false argument' => [
+ 0,
+ [false, 'second argument', 'third argument'],
+ [false, 'second argument', 'third argument'],
+ ],
+ 'NOCREATE argument first and there is arguments after' => [
+ 0,
+ [true, 'second argument', 'third argument'],
+ ['NOCREATE', 'second argument', 'third argument'],
+ ],
+ 'NOCREATE argument last and there is arguments before' => [
+ 2,
+ ['first argument', 'second argument', true],
+ ['first argument', 'second argument', 'NOCREATE'],
+ ],
+ 'NOCREATE argument not the first and not the last' => [
+ 1,
+ ['first argument', true, 'third argument'],
+ ['first argument', 'NOCREATE', 'third argument'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/Json/IndentTest.php b/tests/Predis/Command/Traits/Json/IndentTest.php
new file mode 100644
index 00000000..1b31bed0
--- /dev/null
+++ b/tests/Predis/Command/Traits/Json/IndentTest.php
@@ -0,0 +1,87 @@
+testClass = new class() extends RedisCommand {
+ use Indent;
+
+ public static $indentArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $actualArguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(
+ int $offset,
+ array $actualArguments,
+ array $expectedResponse
+ ): void {
+ $this->testClass::$indentArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($actualArguments);
+
+ $this->assertSame($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Indent argument value should be a string');
+
+ $this->testClass->setArguments([1]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 2,
+ ['argument1'],
+ ['argument1'],
+ ],
+ 'with default value' => [
+ 0,
+ [''],
+ [false],
+ ],
+ 'with correct argument' => [
+ 0,
+ ['\t'],
+ ['INDENT', '\t'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/Json/NewlineTest.php b/tests/Predis/Command/Traits/Json/NewlineTest.php
new file mode 100644
index 00000000..f5bacefa
--- /dev/null
+++ b/tests/Predis/Command/Traits/Json/NewlineTest.php
@@ -0,0 +1,87 @@
+testClass = new class() extends RedisCommand {
+ use Newline;
+
+ public static $newlineArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $actualArguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(
+ int $offset,
+ array $actualArguments,
+ array $expectedResponse
+ ): void {
+ $this->testClass::$newlineArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($actualArguments);
+
+ $this->assertSame($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Newline argument value should be a string');
+
+ $this->testClass->setArguments([1]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 2,
+ ['argument1'],
+ ['argument1'],
+ ],
+ 'with default value' => [
+ 0,
+ [''],
+ [false],
+ ],
+ 'with correct argument' => [
+ 0,
+ ['\n'],
+ ['NEWLINE', '\n'],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/Json/NxXxArgumentTest.php b/tests/Predis/Command/Traits/Json/NxXxArgumentTest.php
new file mode 100644
index 00000000..04ad1c3f
--- /dev/null
+++ b/tests/Predis/Command/Traits/Json/NxXxArgumentTest.php
@@ -0,0 +1,92 @@
+testClass = new class() extends RedisCommand {
+ use NxXxArgument;
+
+ public static $nxXxArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $actualArguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(
+ int $offset,
+ array $actualArguments,
+ array $expectedResponse
+ ): void {
+ $this->testClass::$nxXxArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($actualArguments);
+
+ $this->assertSame($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Argument accepts only: nx, xx values');
+
+ $this->testClass->setArguments(['wrong']);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with correct argument - NX' => [
+ 0,
+ ['nx'],
+ ['NX'],
+ ],
+ 'with correct argument - XX' => [
+ 0,
+ ['xx'],
+ ['XX'],
+ ],
+ 'with wrong offset' => [
+ 2,
+ ['argument1'],
+ ['argument1'],
+ ],
+ 'with null argument' => [
+ 0,
+ [null],
+ [false],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Command/Traits/Json/SpaceTest.php b/tests/Predis/Command/Traits/Json/SpaceTest.php
new file mode 100644
index 00000000..ef5c456b
--- /dev/null
+++ b/tests/Predis/Command/Traits/Json/SpaceTest.php
@@ -0,0 +1,87 @@
+testClass = new class() extends RedisCommand {
+ use Space;
+
+ public static $spaceArgumentPositionOffset = 0;
+
+ public function getId()
+ {
+ return 'test';
+ }
+ };
+ }
+
+ /**
+ * @dataProvider argumentsProvider
+ * @param int $offset
+ * @param array $actualArguments
+ * @param array $expectedResponse
+ * @return void
+ */
+ public function testReturnsCorrectArguments(
+ int $offset,
+ array $actualArguments,
+ array $expectedResponse
+ ): void {
+ $this->testClass::$spaceArgumentPositionOffset = $offset;
+
+ $this->testClass->setArguments($actualArguments);
+
+ $this->assertSame($expectedResponse, $this->testClass->getArguments());
+ }
+
+ /**
+ * @return void
+ */
+ public function testThrowsExceptionOnUnexpectedValueGiven(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+ $this->expectExceptionMessage('Space argument value should be a string');
+
+ $this->testClass->setArguments([1]);
+ }
+
+ public function argumentsProvider(): array
+ {
+ return [
+ 'with wrong offset' => [
+ 2,
+ ['argument1'],
+ ['argument1'],
+ ],
+ 'with default value' => [
+ 0,
+ [''],
+ [false],
+ ],
+ 'with correct argument' => [
+ 0,
+ [' '],
+ ['SPACE', ' '],
+ ],
+ ];
+ }
+}
diff --git a/tests/Predis/Transaction/MultiExecTest.php b/tests/Predis/Transaction/MultiExecTest.php
index 95cac4a4..e42d0251 100644
--- a/tests/Predis/Transaction/MultiExecTest.php
+++ b/tests/Predis/Transaction/MultiExecTest.php
@@ -886,8 +886,7 @@ class MultiExecTest extends PredisTestCase
?array $expected = [],
?array &$commands = [],
?array &$cas = []
- ): callable
- {
+ ): callable {
$multi = $watch = $abort = false;
return function (CommandInterface $command) use (&$expected, &$commands, &$cas, &$multi, &$watch, &$abort) {