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) {