valkey-namespace 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +24 -0
- data/NOTICE +25 -0
- data/README.md +130 -0
- data/Rakefile +6 -0
- data/lib/valkey/namespace/version.rb +5 -0
- data/lib/valkey/namespace.rb +625 -0
- data/lib/valkey-namespace.rb +1 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/valkey_spec.rb +122 -0
- metadata +131 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a385b6f2e0f44403c0fe05fecc1b03823583fc5a8ae177e1b2e0a78ac6e83d31
|
|
4
|
+
data.tar.gz: 26a7e394ad8a9e6609f2397922a6084a162305fe372045faecbfff6c2f97efb1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fb54cb5c43cbec71678dfedf72c1d42b041813bf5f038d0efb80e9f7335c84965d22f4fb99a35b2d1dd9675f754de468781ffa247e93878af4e863b5590bddb2
|
|
7
|
+
data.tar.gz: 7afd85a87386b5d52f7f1cddc2be5f49f9a39156cf75f1751083ed1998e63997a8c55c78dce0026691602cac85a6f9b9a5aee6754a33d660364074259cd91564
|
data/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Valkey Community
|
|
4
|
+
|
|
5
|
+
This project is based on redis-namespace:
|
|
6
|
+
Copyright (c) 2009 Chris Wanstrath
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
data/NOTICE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
valkey-namespace
|
|
2
|
+
Copyright (c) 2026 Valkey Community
|
|
3
|
+
|
|
4
|
+
This project is a derivative work based on redis-namespace:
|
|
5
|
+
https://github.com/resque/redis-namespace
|
|
6
|
+
|
|
7
|
+
redis-namespace
|
|
8
|
+
Copyright (c) 2009 Chris Wanstrath
|
|
9
|
+
|
|
10
|
+
Both projects are licensed under the MIT License.
|
|
11
|
+
|
|
12
|
+
The original redis-namespace implementation has been adapted to work with
|
|
13
|
+
the Valkey database and the valkey-glide-ruby client library. The core
|
|
14
|
+
architecture, command registry patterns, and namespacing logic are derived
|
|
15
|
+
from redis-namespace.
|
|
16
|
+
|
|
17
|
+
Key changes in valkey-namespace:
|
|
18
|
+
- Adapted to work with Valkey instead of Redis
|
|
19
|
+
- Uses valkey-glide-ruby client instead of redis-rb
|
|
20
|
+
- Updated naming conventions (Redis → Valkey)
|
|
21
|
+
- Updated environment variables (REDIS_NAMESPACE_* → VALKEY_NAMESPACE_*)
|
|
22
|
+
- Added examples and additional documentation
|
|
23
|
+
|
|
24
|
+
We are grateful to the redis-namespace project and its contributors for
|
|
25
|
+
their excellent work that made this adaptation possible.
|
data/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
valkey-namespace
|
|
2
|
+
===============
|
|
3
|
+
|
|
4
|
+
Valkey::Namespace provides an interface to a namespaced subset of your [Valkey][] keyspace (e.g., keys with a common beginning), and requires the [valkey-glide-ruby][] gem.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
require 'valkey-namespace'
|
|
8
|
+
# => true
|
|
9
|
+
|
|
10
|
+
valkey_connection = Valkey.new
|
|
11
|
+
# => #<Valkey client>
|
|
12
|
+
namespaced_valkey = Valkey::Namespace.new(:ns, valkey: valkey_connection)
|
|
13
|
+
# => #<Valkey::Namespace v1.0.0 with client for ns>
|
|
14
|
+
|
|
15
|
+
namespaced_valkey.set('foo', 'bar') # valkey_connection.set('ns:foo', 'bar')
|
|
16
|
+
# => "OK"
|
|
17
|
+
|
|
18
|
+
# Valkey::Namespace automatically prepended our namespace to the key
|
|
19
|
+
# before sending it to our valkey client.
|
|
20
|
+
|
|
21
|
+
namespaced_valkey.get('foo')
|
|
22
|
+
# => "bar"
|
|
23
|
+
valkey_connection.get('foo')
|
|
24
|
+
# => nil
|
|
25
|
+
valkey_connection.get('ns:foo')
|
|
26
|
+
# => "bar"
|
|
27
|
+
|
|
28
|
+
namespaced_valkey.del('foo')
|
|
29
|
+
# => 1
|
|
30
|
+
namespaced_valkey.get('foo')
|
|
31
|
+
# => nil
|
|
32
|
+
valkey_connection.get('ns:foo')
|
|
33
|
+
# => nil
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Valkey::Namespace also supports `Proc` as a namespace and will take the result string as namespace at runtime.
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
valkey_connection = Valkey.new
|
|
40
|
+
namespaced_valkey = Valkey::Namespace.new(Proc.new { Tenant.current_tenant }, valkey: valkey_connection)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Installation
|
|
44
|
+
============
|
|
45
|
+
|
|
46
|
+
Valkey::Namespace is packaged as the valkey-namespace gem, and hosted on rubygems.org.
|
|
47
|
+
|
|
48
|
+
From the command line:
|
|
49
|
+
|
|
50
|
+
$ gem install valkey-namespace
|
|
51
|
+
|
|
52
|
+
Or in your Gemfile:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
gem 'valkey-namespace'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Caveats
|
|
59
|
+
=======
|
|
60
|
+
|
|
61
|
+
`Valkey::Namespace` provides a namespaced interface to `Valkey` by keeping an internal registry of the method signatures in `Valkey` provided by the [valkey-glide-ruby][] gem; we keep track of which arguments need the namespace added, and which return values need the namespace removed.
|
|
62
|
+
|
|
63
|
+
Blind Passthrough
|
|
64
|
+
-----------------
|
|
65
|
+
If your version of this gem doesn't know about a particular command, it can't namespace it. Historically, this has meant that Valkey::Namespace blindly passes unknown commands on to the underlying valkey connection without modification which can lead to surprising effects.
|
|
66
|
+
|
|
67
|
+
As of v1.0.0, blind passthrough has been deprecated, and the functionality will be removed entirely in 2.0.
|
|
68
|
+
|
|
69
|
+
If you come across a command that is not yet supported, please open an issue on the [issue tracker][] or submit a pull-request.
|
|
70
|
+
|
|
71
|
+
Administrative Commands
|
|
72
|
+
-----------------------
|
|
73
|
+
The effects of some valkey commands cannot be limited to a particular namespace (e.g., `FLUSHALL`, which literally truncates all databases in your valkey server, regardless of keyspace). Historically, this has meant that Valkey::Namespace intentionally passes administrative commands on to the underlying valkey connection without modification, which can lead to surprising effects.
|
|
74
|
+
|
|
75
|
+
As of v1.0.0, the direct use of administrative commands has been deprecated, and the functionality will be removed entirely in 2.0; while such commands are often useful for testing or administration, their meaning is inherently hidden when placed behind an interface that implies it will namespace everything.
|
|
76
|
+
|
|
77
|
+
The preferred way to send an administrative command is on the valkey connection itself, which is publicly exposed as `Valkey::Namespace#valkey`:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
namespaced.valkey.flushall()
|
|
81
|
+
# => "OK"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2.x Planned Breaking Changes
|
|
85
|
+
============================
|
|
86
|
+
|
|
87
|
+
As mentioned above, 2.0 will remove blind passthrough and the administrative command passthrough.
|
|
88
|
+
By default in 1.0+, deprecation warnings are present and enabled;
|
|
89
|
+
they can be silenced by initializing `Valkey::Namespace` with `warning: false` or by setting the `VALKEY_NAMESPACE_QUIET` environment variable.
|
|
90
|
+
|
|
91
|
+
Early opt-in
|
|
92
|
+
------------
|
|
93
|
+
|
|
94
|
+
To enable testing against the 2.x interface before its release, in addition to deprecation warnings, early opt-in to these changes can be enabled by initializing `Valkey::Namespace` with `deprecations: true` or by setting the `VALKEY_NAMESPACE_DEPRECATIONS` environment variable.
|
|
95
|
+
This should only be done once all warnings have been addressed.
|
|
96
|
+
|
|
97
|
+
Compatibility
|
|
98
|
+
=============
|
|
99
|
+
|
|
100
|
+
This gem is designed to work with [valkey-glide-ruby][], which provides a drop-in replacement for redis-rb. It supports all Valkey commands and is API-compatible with Redis 6.2, 7.0, 7.1, and 7.2.
|
|
101
|
+
|
|
102
|
+
Authors
|
|
103
|
+
=======
|
|
104
|
+
|
|
105
|
+
This project is based on [redis-namespace][] and adapted for Valkey.
|
|
106
|
+
|
|
107
|
+
Original redis-namespace authors who contributed significantly:
|
|
108
|
+
- Chris Wanstrath (@defunkt)
|
|
109
|
+
- Ryan Biesemeyer (@yaauie)
|
|
110
|
+
- Steve Klabnik (@steveklabnik)
|
|
111
|
+
- Terence Lee (@hone)
|
|
112
|
+
- Eoin Coffey (@ecoffey)
|
|
113
|
+
|
|
114
|
+
valkey-namespace adaptation:
|
|
115
|
+
- Valkey Community
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
This project is licensed under the MIT License, the same as redis-namespace.
|
|
120
|
+
See the LICENSE file for details.
|
|
121
|
+
|
|
122
|
+
## Acknowledgments
|
|
123
|
+
|
|
124
|
+
Special thanks to the redis-namespace project and its contributors for creating
|
|
125
|
+
the original implementation that this project is based on.
|
|
126
|
+
|
|
127
|
+
[Valkey]: https://valkey.io
|
|
128
|
+
[valkey-glide-ruby]: https://github.com/valkey-io/valkey-glide-ruby
|
|
129
|
+
[redis-namespace]: https://github.com/resque/redis-namespace
|
|
130
|
+
[issue tracker]: https://github.com/valkey-io/valkey-namespace/issues
|
data/Rakefile
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This file is based on redis-namespace (https://github.com/resque/redis-namespace)
|
|
4
|
+
# Copyright (c) 2009 Chris Wanstrath
|
|
5
|
+
# Adapted for Valkey by the Valkey Community
|
|
6
|
+
# Licensed under the MIT License
|
|
7
|
+
|
|
8
|
+
require 'valkey'
|
|
9
|
+
require 'valkey/namespace/version'
|
|
10
|
+
|
|
11
|
+
class Valkey
|
|
12
|
+
class Namespace
|
|
13
|
+
# The following tables define how input parameters and result
|
|
14
|
+
# values should be modified for the namespace.
|
|
15
|
+
#
|
|
16
|
+
# COMMANDS is a hash. Each key is the name of a command and each
|
|
17
|
+
# value is a two element array.
|
|
18
|
+
#
|
|
19
|
+
# The first element in the value array describes how to modify the
|
|
20
|
+
# arguments passed. It can be one of:
|
|
21
|
+
#
|
|
22
|
+
# nil
|
|
23
|
+
# Do nothing.
|
|
24
|
+
# :first
|
|
25
|
+
# Add the namespace to the first argument passed, e.g.
|
|
26
|
+
# GET key => GET namespace:key
|
|
27
|
+
# :all
|
|
28
|
+
# Add the namespace to all arguments passed, e.g.
|
|
29
|
+
# MGET key1 key2 => MGET namespace:key1 namespace:key2
|
|
30
|
+
# :exclude_first
|
|
31
|
+
# Add the namespace to all arguments but the first, e.g.
|
|
32
|
+
# :exclude_last
|
|
33
|
+
# Add the namespace to all arguments but the last, e.g.
|
|
34
|
+
# BLPOP key1 key2 timeout =>
|
|
35
|
+
# BLPOP namespace:key1 namespace:key2 timeout
|
|
36
|
+
# :exclude_options
|
|
37
|
+
# Add the namespace to all arguments, except the last argument,
|
|
38
|
+
# if the last argument is a hash of options.
|
|
39
|
+
# ZUNIONSTORE key1 2 key2 key3 WEIGHTS 2 1 =>
|
|
40
|
+
# ZUNIONSTORE namespace:key1 2 namespace:key2 namespace:key3 WEIGHTS 2 1
|
|
41
|
+
# :alternate
|
|
42
|
+
# Add the namespace to every other argument, e.g.
|
|
43
|
+
# MSET key1 value1 key2 value2 =>
|
|
44
|
+
# MSET namespace:key1 value1 namespace:key2 value2
|
|
45
|
+
# :sort
|
|
46
|
+
# Add namespace to first argument if it is non-nil
|
|
47
|
+
# Add namespace to second arg's :by and :store if second arg is a Hash
|
|
48
|
+
# Add namespace to each element in second arg's :get if second arg is
|
|
49
|
+
# a Hash; forces second arg's :get to be an Array if present.
|
|
50
|
+
# :eval_style
|
|
51
|
+
# Add namespace to each element in keys argument (via options hash or multi-args)
|
|
52
|
+
# :scan_style
|
|
53
|
+
# Add namespace to :match option, or supplies "#{namespace}:*" if not present.
|
|
54
|
+
#
|
|
55
|
+
# The second element in the value array describes how to modify
|
|
56
|
+
# the return value of the Valkey call. It can be one of:
|
|
57
|
+
#
|
|
58
|
+
# nil
|
|
59
|
+
# Do nothing.
|
|
60
|
+
# :all
|
|
61
|
+
# Add the namespace to all elements returned, e.g.
|
|
62
|
+
# key1 key2 => namespace:key1 namespace:key2
|
|
63
|
+
NAMESPACED_COMMANDS = {
|
|
64
|
+
"append" => [ :first ],
|
|
65
|
+
"bitcount" => [ :first ],
|
|
66
|
+
"bitfield" => [ :first ],
|
|
67
|
+
"bitop" => [ :exclude_first ],
|
|
68
|
+
"bitpos" => [ :first ],
|
|
69
|
+
"blpop" => [ :exclude_last, :first ],
|
|
70
|
+
"brpop" => [ :exclude_last, :first ],
|
|
71
|
+
"brpoplpush" => [ :exclude_last ],
|
|
72
|
+
"bzpopmin" => [ :first ],
|
|
73
|
+
"bzpopmax" => [ :first ],
|
|
74
|
+
"debug" => [ :exclude_first ],
|
|
75
|
+
"decr" => [ :first ],
|
|
76
|
+
"decrby" => [ :first ],
|
|
77
|
+
"del" => [ :all ],
|
|
78
|
+
"dump" => [ :first ],
|
|
79
|
+
"exists" => [ :all ],
|
|
80
|
+
"exists?" => [ :all ],
|
|
81
|
+
"expire" => [ :first ],
|
|
82
|
+
"expireat" => [ :first ],
|
|
83
|
+
"expiretime" => [ :first ],
|
|
84
|
+
"eval" => [ :eval_style ],
|
|
85
|
+
"evalsha" => [ :eval_style ],
|
|
86
|
+
"get" => [ :first ],
|
|
87
|
+
"getex" => [ :first ],
|
|
88
|
+
"getbit" => [ :first ],
|
|
89
|
+
"getrange" => [ :first ],
|
|
90
|
+
"getset" => [ :first ],
|
|
91
|
+
"hset" => [ :first ],
|
|
92
|
+
"hsetnx" => [ :first ],
|
|
93
|
+
"hget" => [ :first ],
|
|
94
|
+
"hincrby" => [ :first ],
|
|
95
|
+
"hincrbyfloat" => [ :first ],
|
|
96
|
+
"hmget" => [ :first ],
|
|
97
|
+
"hmset" => [ :first ],
|
|
98
|
+
"hdel" => [ :first ],
|
|
99
|
+
"hexists" => [ :first ],
|
|
100
|
+
"hlen" => [ :first ],
|
|
101
|
+
"hkeys" => [ :first ],
|
|
102
|
+
"hscan" => [ :first ],
|
|
103
|
+
"hscan_each" => [ :first ],
|
|
104
|
+
"hvals" => [ :first ],
|
|
105
|
+
"hgetall" => [ :first ],
|
|
106
|
+
"incr" => [ :first ],
|
|
107
|
+
"incrby" => [ :first ],
|
|
108
|
+
"incrbyfloat" => [ :first ],
|
|
109
|
+
"keys" => [ :first, :all ],
|
|
110
|
+
"lindex" => [ :first ],
|
|
111
|
+
"linsert" => [ :first ],
|
|
112
|
+
"llen" => [ :first ],
|
|
113
|
+
"lpop" => [ :first ],
|
|
114
|
+
"lpos" => [ :first ],
|
|
115
|
+
"lpush" => [ :first ],
|
|
116
|
+
"lpushx" => [ :first ],
|
|
117
|
+
"lrange" => [ :first ],
|
|
118
|
+
"lrem" => [ :first ],
|
|
119
|
+
"lset" => [ :first ],
|
|
120
|
+
"ltrim" => [ :first ],
|
|
121
|
+
"mapped_hmset" => [ :first ],
|
|
122
|
+
"mapped_hmget" => [ :first ],
|
|
123
|
+
"mapped_mget" => [ :all, :all ],
|
|
124
|
+
"mapped_mset" => [ :all ],
|
|
125
|
+
"mapped_msetnx" => [ :all ],
|
|
126
|
+
"mget" => [ :all ],
|
|
127
|
+
"monitor" => [ :monitor ],
|
|
128
|
+
"move" => [ :first ],
|
|
129
|
+
"mset" => [ :alternate ],
|
|
130
|
+
"msetnx" => [ :alternate ],
|
|
131
|
+
"object" => [ :exclude_first ],
|
|
132
|
+
"persist" => [ :first ],
|
|
133
|
+
"pexpire" => [ :first ],
|
|
134
|
+
"pexpireat" => [ :first ],
|
|
135
|
+
"pexpiretime" => [ :first ],
|
|
136
|
+
"pfadd" => [ :first ],
|
|
137
|
+
"pfcount" => [ :all ],
|
|
138
|
+
"pfmerge" => [ :all ],
|
|
139
|
+
"psetex" => [ :first ],
|
|
140
|
+
"psubscribe" => [ :all ],
|
|
141
|
+
"pttl" => [ :first ],
|
|
142
|
+
"publish" => [ :first ],
|
|
143
|
+
"punsubscribe" => [ :all ],
|
|
144
|
+
"rename" => [ :all ],
|
|
145
|
+
"renamenx" => [ :all ],
|
|
146
|
+
"restore" => [ :first ],
|
|
147
|
+
"rpop" => [ :first ],
|
|
148
|
+
"rpoplpush" => [ :all ],
|
|
149
|
+
"rpush" => [ :first ],
|
|
150
|
+
"rpushx" => [ :first ],
|
|
151
|
+
"sadd" => [ :first ],
|
|
152
|
+
"sadd?" => [ :first ],
|
|
153
|
+
"scard" => [ :first ],
|
|
154
|
+
"scan" => [ :scan_style, :second ],
|
|
155
|
+
"scan_each" => [ :scan_style, :all ],
|
|
156
|
+
"sdiff" => [ :all ],
|
|
157
|
+
"sdiffstore" => [ :all ],
|
|
158
|
+
"set" => [ :first ],
|
|
159
|
+
"setbit" => [ :first ],
|
|
160
|
+
"setex" => [ :first ],
|
|
161
|
+
"setnx" => [ :first ],
|
|
162
|
+
"setrange" => [ :first ],
|
|
163
|
+
"sinter" => [ :all ],
|
|
164
|
+
"sinterstore" => [ :all ],
|
|
165
|
+
"sismember" => [ :first ],
|
|
166
|
+
"smembers" => [ :first ],
|
|
167
|
+
"smismember" => [ :first ],
|
|
168
|
+
"smove" => [ :exclude_last ],
|
|
169
|
+
"sort" => [ :sort ],
|
|
170
|
+
"spop" => [ :first ],
|
|
171
|
+
"srandmember" => [ :first ],
|
|
172
|
+
"srem" => [ :first ],
|
|
173
|
+
"srem?" => [ :first ],
|
|
174
|
+
"sscan" => [ :first ],
|
|
175
|
+
"sscan_each" => [ :first ],
|
|
176
|
+
"strlen" => [ :first ],
|
|
177
|
+
"subscribe" => [ :all ],
|
|
178
|
+
"sunion" => [ :all ],
|
|
179
|
+
"sunionstore" => [ :all ],
|
|
180
|
+
"ttl" => [ :first ],
|
|
181
|
+
"type" => [ :first ],
|
|
182
|
+
"unlink" => [ :all ],
|
|
183
|
+
"unsubscribe" => [ :all ],
|
|
184
|
+
"zadd" => [ :first ],
|
|
185
|
+
"zcard" => [ :first ],
|
|
186
|
+
"zcount" => [ :first ],
|
|
187
|
+
"zincrby" => [ :first ],
|
|
188
|
+
"zinterstore" => [ :exclude_options ],
|
|
189
|
+
"zpopmin" => [ :first ],
|
|
190
|
+
"zpopmax" => [ :first ],
|
|
191
|
+
"zrange" => [ :first ],
|
|
192
|
+
"zrangebyscore" => [ :first ],
|
|
193
|
+
"zrangebylex" => [ :first ],
|
|
194
|
+
"zrank" => [ :first ],
|
|
195
|
+
"zrem" => [ :first ],
|
|
196
|
+
"zremrangebyrank" => [ :first ],
|
|
197
|
+
"zremrangebyscore" => [ :first ],
|
|
198
|
+
"zremrangebylex" => [ :first ],
|
|
199
|
+
"zrevrange" => [ :first ],
|
|
200
|
+
"zrevrangebyscore" => [ :first ],
|
|
201
|
+
"zrevrangebylex" => [ :first ],
|
|
202
|
+
"zrevrank" => [ :first ],
|
|
203
|
+
"zscan" => [ :first ],
|
|
204
|
+
"zscan_each" => [ :first ],
|
|
205
|
+
"zscore" => [ :first ],
|
|
206
|
+
"zunionstore" => [ :exclude_options ]
|
|
207
|
+
}
|
|
208
|
+
TRANSACTION_COMMANDS = {
|
|
209
|
+
"discard" => [],
|
|
210
|
+
"exec" => [],
|
|
211
|
+
"multi" => [],
|
|
212
|
+
"unwatch" => [ :all ],
|
|
213
|
+
"watch" => [ :all ],
|
|
214
|
+
}
|
|
215
|
+
HELPER_COMMANDS = {
|
|
216
|
+
"auth" => [],
|
|
217
|
+
"disconnect!" => [],
|
|
218
|
+
"close" => [],
|
|
219
|
+
"echo" => [],
|
|
220
|
+
"ping" => [],
|
|
221
|
+
"time" => [],
|
|
222
|
+
}
|
|
223
|
+
ADMINISTRATIVE_COMMANDS = {
|
|
224
|
+
"bgrewriteaof" => [],
|
|
225
|
+
"bgsave" => [],
|
|
226
|
+
"config" => [],
|
|
227
|
+
"dbsize" => [],
|
|
228
|
+
"flushall" => [],
|
|
229
|
+
"flushdb" => [],
|
|
230
|
+
"info" => [],
|
|
231
|
+
"lastsave" => [],
|
|
232
|
+
"quit" => [],
|
|
233
|
+
"randomkey" => [],
|
|
234
|
+
"save" => [],
|
|
235
|
+
"script" => [],
|
|
236
|
+
"select" => [],
|
|
237
|
+
"shutdown" => [],
|
|
238
|
+
"slaveof" => [],
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
DEPRECATED_COMMANDS = [
|
|
242
|
+
ADMINISTRATIVE_COMMANDS
|
|
243
|
+
].compact.reduce(:merge)
|
|
244
|
+
|
|
245
|
+
COMMANDS = [
|
|
246
|
+
NAMESPACED_COMMANDS,
|
|
247
|
+
TRANSACTION_COMMANDS,
|
|
248
|
+
HELPER_COMMANDS,
|
|
249
|
+
ADMINISTRATIVE_COMMANDS,
|
|
250
|
+
].compact.reduce(:merge)
|
|
251
|
+
|
|
252
|
+
# Support 1.8.7 by providing a namespaced reference to Enumerable::Enumerator
|
|
253
|
+
Enumerator = Enumerable::Enumerator unless defined?(::Enumerator)
|
|
254
|
+
|
|
255
|
+
attr_writer :namespace
|
|
256
|
+
attr_reader :valkey
|
|
257
|
+
attr_accessor :warning
|
|
258
|
+
|
|
259
|
+
def initialize(namespace, options = {})
|
|
260
|
+
@namespace = namespace
|
|
261
|
+
@valkey = options[:valkey] || Valkey.new
|
|
262
|
+
@warning = !!options.fetch(:warning) do
|
|
263
|
+
!ENV['VALKEY_NAMESPACE_QUIET']
|
|
264
|
+
end
|
|
265
|
+
@deprecations = !!options.fetch(:deprecations) do
|
|
266
|
+
ENV['VALKEY_NAMESPACE_DEPRECATIONS']
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def deprecations?
|
|
271
|
+
@deprecations
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def warning?
|
|
275
|
+
@warning
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Ruby defines a now deprecated type method so we need to override it here
|
|
279
|
+
# since it will never hit method_missing
|
|
280
|
+
def type(key)
|
|
281
|
+
call_with_namespace(:type, key)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
alias_method :self_respond_to?, :respond_to?
|
|
285
|
+
|
|
286
|
+
# emulate Ruby 1.9+ and keep respond_to_missing? logic together.
|
|
287
|
+
def respond_to?(command, include_private=false)
|
|
288
|
+
return !deprecations? if DEPRECATED_COMMANDS.include?(command.to_s.downcase)
|
|
289
|
+
|
|
290
|
+
respond_to_missing?(command, include_private) or super
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def keys(query = nil)
|
|
294
|
+
call_with_namespace(:keys, query || '*')
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def multi(&block)
|
|
298
|
+
if block_given?
|
|
299
|
+
namespaced_block(:multi, &block)
|
|
300
|
+
else
|
|
301
|
+
call_with_namespace(:multi)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def pipelined(&block)
|
|
306
|
+
namespaced_block(:pipelined, &block)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def namespace(desired_namespace = nil)
|
|
310
|
+
if desired_namespace
|
|
311
|
+
yield Valkey::Namespace.new(desired_namespace,
|
|
312
|
+
:valkey => @valkey)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
@namespace.respond_to?(:call) ? @namespace.call : @namespace
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def full_namespace
|
|
319
|
+
valkey.is_a?(Namespace) ? "#{valkey.full_namespace}:#{namespace}" : namespace.to_s
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def connection
|
|
323
|
+
@valkey.connection.tap { |info| info[:namespace] = namespace }
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def exec
|
|
327
|
+
call_with_namespace(:exec)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def eval(*args)
|
|
331
|
+
call_with_namespace(:eval, *args)
|
|
332
|
+
end
|
|
333
|
+
ruby2_keywords(:eval) if respond_to?(:ruby2_keywords, true)
|
|
334
|
+
|
|
335
|
+
# This operation can run for a very long time if the namespace contains lots of keys!
|
|
336
|
+
# It should be used in tests, or when the namespace is small enough
|
|
337
|
+
# and you are sure you know what you are doing.
|
|
338
|
+
def clear
|
|
339
|
+
if warning?
|
|
340
|
+
warn("This operation can run for a very long time if the namespace contains lots of keys! " +
|
|
341
|
+
"It should be used in tests, or when the namespace is small enough " +
|
|
342
|
+
"and you are sure you know what you are doing.")
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
batch_size = 1000
|
|
346
|
+
|
|
347
|
+
if supports_scan?
|
|
348
|
+
cursor = "0"
|
|
349
|
+
begin
|
|
350
|
+
cursor, keys = scan(cursor, count: batch_size)
|
|
351
|
+
del(*keys) unless keys.empty?
|
|
352
|
+
end until cursor == "0"
|
|
353
|
+
else
|
|
354
|
+
all_keys = keys("*")
|
|
355
|
+
all_keys.each_slice(batch_size) do |keys|
|
|
356
|
+
del(*keys)
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
ADMINISTRATIVE_COMMANDS.keys.each do |command|
|
|
362
|
+
define_method(command) do |*args, &block|
|
|
363
|
+
raise NoMethodError if deprecations?
|
|
364
|
+
|
|
365
|
+
if warning?
|
|
366
|
+
warn("Passing '#{command}' command to valkey as is; " +
|
|
367
|
+
"administrative commands cannot be effectively namespaced " +
|
|
368
|
+
"and should be called on the valkey connection directly; " +
|
|
369
|
+
"passthrough has been deprecated and will be removed in " +
|
|
370
|
+
"valkey-namespace 2.0 (at #{call_site})"
|
|
371
|
+
)
|
|
372
|
+
end
|
|
373
|
+
call_with_namespace(command, *args, &block)
|
|
374
|
+
end
|
|
375
|
+
ruby2_keywords(command) if respond_to?(:ruby2_keywords, true)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
COMMANDS.keys.each do |command|
|
|
379
|
+
next if ADMINISTRATIVE_COMMANDS.include?(command)
|
|
380
|
+
next if method_defined?(command)
|
|
381
|
+
|
|
382
|
+
define_method(command) do |*args, &block|
|
|
383
|
+
call_with_namespace(command, *args, &block)
|
|
384
|
+
end
|
|
385
|
+
ruby2_keywords(command) if respond_to?(:ruby2_keywords, true)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def method_missing(command, *args, &block)
|
|
389
|
+
normalized_command = command.to_s.downcase
|
|
390
|
+
|
|
391
|
+
if COMMANDS.include?(normalized_command)
|
|
392
|
+
send(normalized_command, *args, &block)
|
|
393
|
+
elsif @valkey.respond_to?(normalized_command) && !deprecations?
|
|
394
|
+
# blind passthrough is deprecated and will be removed in 2.0
|
|
395
|
+
# valkey-namespace does not know how to handle this command.
|
|
396
|
+
# Passing it to @valkey as is, where valkey-namespace shows
|
|
397
|
+
# a warning message if @warning is set.
|
|
398
|
+
if warning?
|
|
399
|
+
warn("Passing '#{command}' command to valkey as is; blind " +
|
|
400
|
+
"passthrough has been deprecated and will be removed in " +
|
|
401
|
+
"valkey-namespace 2.0 (at #{call_site})")
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
wrapped_send(@valkey, command, args, &block)
|
|
405
|
+
else
|
|
406
|
+
super
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
|
410
|
+
|
|
411
|
+
def inspect
|
|
412
|
+
"<#{self.class.name} v#{VERSION} with client "\
|
|
413
|
+
"for #{full_namespace}>"
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def respond_to_missing?(command, include_all=false)
|
|
417
|
+
normalized_command = command.to_s.downcase
|
|
418
|
+
|
|
419
|
+
case
|
|
420
|
+
when COMMANDS.include?(normalized_command)
|
|
421
|
+
true
|
|
422
|
+
when !deprecations? && valkey.respond_to?(command, include_all)
|
|
423
|
+
true
|
|
424
|
+
else
|
|
425
|
+
defined?(super) && super
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def call_with_namespace(command, *args, &block)
|
|
430
|
+
handling = COMMANDS[command.to_s.downcase]
|
|
431
|
+
|
|
432
|
+
if handling.nil?
|
|
433
|
+
fail("Valkey::Namespace does not know how to handle '#{command}'.")
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
(before, after) = handling
|
|
437
|
+
|
|
438
|
+
# Modify the local *args array in-place, no need to copy it.
|
|
439
|
+
args.map! {|arg| clone_args(arg)}
|
|
440
|
+
|
|
441
|
+
# Add the namespace to any parameters that are keys.
|
|
442
|
+
case before
|
|
443
|
+
when :first
|
|
444
|
+
args[0] = add_namespace(args[0]) if args[0]
|
|
445
|
+
args[-1] = ruby2_keywords_hash(args[-1]) if args[-1].is_a?(Hash)
|
|
446
|
+
when :all
|
|
447
|
+
args = add_namespace(args)
|
|
448
|
+
when :exclude_first
|
|
449
|
+
first = args.shift
|
|
450
|
+
args = add_namespace(args)
|
|
451
|
+
args.unshift(first) if first
|
|
452
|
+
when :exclude_last
|
|
453
|
+
last = args.pop unless args.length == 1
|
|
454
|
+
args = add_namespace(args)
|
|
455
|
+
args.push(last) if last
|
|
456
|
+
when :exclude_options
|
|
457
|
+
if args.last.is_a?(Hash)
|
|
458
|
+
last = ruby2_keywords_hash(args.pop)
|
|
459
|
+
args = add_namespace(args)
|
|
460
|
+
args.push(last)
|
|
461
|
+
else
|
|
462
|
+
args = add_namespace(args)
|
|
463
|
+
end
|
|
464
|
+
when :alternate
|
|
465
|
+
args = args.flatten
|
|
466
|
+
args.each_with_index { |a, i| args[i] = add_namespace(a) if i.even? }
|
|
467
|
+
when :sort
|
|
468
|
+
args[0] = add_namespace(args[0]) if args[0]
|
|
469
|
+
if args[1].is_a?(Hash)
|
|
470
|
+
[:by, :store].each do |key|
|
|
471
|
+
args[1][key] = add_namespace(args[1][key]) if args[1][key]
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
args[1][:get] = Array(args[1][:get])
|
|
475
|
+
|
|
476
|
+
args[1][:get].each_index do |i|
|
|
477
|
+
args[1][:get][i] = add_namespace(args[1][:get][i]) unless args[1][:get][i] == "#"
|
|
478
|
+
end
|
|
479
|
+
args[1] = ruby2_keywords_hash(args[1])
|
|
480
|
+
end
|
|
481
|
+
when :eval_style
|
|
482
|
+
# valkey.eval() and evalsha() can either take the form:
|
|
483
|
+
#
|
|
484
|
+
# valkey.eval(script, [key1, key2], [argv1, argv2])
|
|
485
|
+
#
|
|
486
|
+
# Or:
|
|
487
|
+
#
|
|
488
|
+
# valkey.eval(script, :keys => ['k1', 'k2'], :argv => ['arg1', 'arg2'])
|
|
489
|
+
#
|
|
490
|
+
# This is a tricky + annoying special case, where we only want the `keys`
|
|
491
|
+
# argument to be namespaced.
|
|
492
|
+
if args.last.is_a?(Hash)
|
|
493
|
+
args.last[:keys] = add_namespace(args.last[:keys])
|
|
494
|
+
else
|
|
495
|
+
args[1] = add_namespace(args[1])
|
|
496
|
+
end
|
|
497
|
+
when :scan_style
|
|
498
|
+
options = (args.last.kind_of?(Hash) ? args.pop : {})
|
|
499
|
+
options[:match] = add_namespace(options.fetch(:match, '*'))
|
|
500
|
+
args << ruby2_keywords_hash(options)
|
|
501
|
+
|
|
502
|
+
if block
|
|
503
|
+
original_block = block
|
|
504
|
+
block = proc { |key| original_block.call rem_namespace(key) }
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# Dispatch the command to Valkey and store the result.
|
|
509
|
+
result = wrapped_send(@valkey, command, args, &block)
|
|
510
|
+
|
|
511
|
+
# Remove the namespace from results that are keys.
|
|
512
|
+
case after
|
|
513
|
+
when :all
|
|
514
|
+
result = rem_namespace(result)
|
|
515
|
+
when :first
|
|
516
|
+
result[0] = rem_namespace(result[0]) if result
|
|
517
|
+
when :second
|
|
518
|
+
result[1] = rem_namespace(result[1]) if result
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
result
|
|
522
|
+
end
|
|
523
|
+
ruby2_keywords(:call_with_namespace) if respond_to?(:ruby2_keywords, true)
|
|
524
|
+
|
|
525
|
+
protected
|
|
526
|
+
|
|
527
|
+
def valkey=(valkey)
|
|
528
|
+
@valkey = valkey
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
private
|
|
532
|
+
|
|
533
|
+
if Hash.respond_to?(:ruby2_keywords_hash)
|
|
534
|
+
def ruby2_keywords_hash(kwargs)
|
|
535
|
+
Hash.ruby2_keywords_hash(kwargs)
|
|
536
|
+
end
|
|
537
|
+
else
|
|
538
|
+
def ruby2_keywords_hash(kwargs)
|
|
539
|
+
kwargs
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def wrapped_send(valkey_client, command, args = [], &block)
|
|
544
|
+
if valkey_client.class.name == "ConnectionPool"
|
|
545
|
+
valkey_client.with do |pool_connection|
|
|
546
|
+
pool_connection.send(command, *args, &block)
|
|
547
|
+
end
|
|
548
|
+
else
|
|
549
|
+
valkey_client.send(command, *args, &block)
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Avoid modifying the caller's (pass-by-reference) arguments.
|
|
554
|
+
def clone_args(arg)
|
|
555
|
+
if arg.is_a?(Array)
|
|
556
|
+
arg.map {|sub_arg| clone_args(sub_arg)}
|
|
557
|
+
elsif arg.is_a?(Hash)
|
|
558
|
+
Hash[arg.map {|k, v| [clone_args(k), clone_args(v)]}]
|
|
559
|
+
else
|
|
560
|
+
arg # Some objects (e.g. symbol) can't be dup'd.
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def call_site
|
|
565
|
+
caller.reject { |l| l.start_with?(__FILE__) }.first
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def namespaced_block(command, &block)
|
|
569
|
+
if block.arity == 0
|
|
570
|
+
wrapped_send(valkey, command, &block)
|
|
571
|
+
else
|
|
572
|
+
outer_block = proc { |r| copy = dup; copy.valkey = r; yield copy }
|
|
573
|
+
wrapped_send(valkey, command, &outer_block)
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
def add_namespace(key)
|
|
578
|
+
return key unless key && namespace
|
|
579
|
+
|
|
580
|
+
case key
|
|
581
|
+
when Array
|
|
582
|
+
key.map! {|k| add_namespace k}
|
|
583
|
+
when Hash
|
|
584
|
+
key.keys.each {|k| key[add_namespace(k)] = key.delete(k)}
|
|
585
|
+
key
|
|
586
|
+
else
|
|
587
|
+
"#{namespace}:#{key}"
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def rem_namespace(key)
|
|
592
|
+
return key unless key && namespace
|
|
593
|
+
|
|
594
|
+
case key
|
|
595
|
+
when Array
|
|
596
|
+
key.map {|k| rem_namespace k}
|
|
597
|
+
when Hash
|
|
598
|
+
Hash[*key.map {|k, v| [ rem_namespace(k), v ]}.flatten]
|
|
599
|
+
when Enumerator
|
|
600
|
+
create_enumerator do |yielder|
|
|
601
|
+
key.each { |k| yielder.yield rem_namespace(k) }
|
|
602
|
+
end
|
|
603
|
+
else
|
|
604
|
+
key.to_s.sub(/\A#{namespace}:/, '')
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def create_enumerator(&block)
|
|
609
|
+
# Enumerator in 1.8.7 *requires* a single argument, so we need to use
|
|
610
|
+
# its Generator class, which matches the block syntax of 1.9.x's
|
|
611
|
+
# Enumerator class.
|
|
612
|
+
if RUBY_VERSION.start_with?('1.8')
|
|
613
|
+
require 'generator' unless defined?(Generator)
|
|
614
|
+
Generator.new(&block).to_enum
|
|
615
|
+
else
|
|
616
|
+
Enumerator.new(&block)
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def supports_scan?
|
|
621
|
+
# Valkey supports SCAN command (inherited from Redis 2.8.0+)
|
|
622
|
+
true
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'valkey/namespace'
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
require 'valkey-namespace'
|
|
3
|
+
require 'rspec/its'
|
|
4
|
+
|
|
5
|
+
RSpec.configure do |config|
|
|
6
|
+
config.expect_with :rspec do |expectations|
|
|
7
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
config.mock_with :rspec do |mocks|
|
|
11
|
+
mocks.verify_partial_doubles = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
15
|
+
config.filter_run_when_matching :focus
|
|
16
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
|
17
|
+
config.disable_monkey_patching!
|
|
18
|
+
config.warnings = true
|
|
19
|
+
|
|
20
|
+
if config.files_to_run.one?
|
|
21
|
+
config.default_formatter = "doc"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
config.profile_examples = 10
|
|
25
|
+
config.order = :random
|
|
26
|
+
Kernel.srand config.seed
|
|
27
|
+
end
|
data/spec/valkey_spec.rb
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Valkey::Namespace do
|
|
4
|
+
let(:valkey) { double('valkey') }
|
|
5
|
+
let(:namespace) { 'test' }
|
|
6
|
+
let(:namespaced) { Valkey::Namespace.new(namespace, valkey: valkey) }
|
|
7
|
+
|
|
8
|
+
describe '#initialize' do
|
|
9
|
+
it 'accepts a namespace and valkey connection' do
|
|
10
|
+
expect(namespaced.namespace).to eq('test')
|
|
11
|
+
expect(namespaced.valkey).to eq(valkey)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'creates a new Valkey connection if none provided' do
|
|
15
|
+
allow(Valkey).to receive(:new).and_return(valkey)
|
|
16
|
+
ns = Valkey::Namespace.new('test')
|
|
17
|
+
expect(ns.valkey).to eq(valkey)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'accepts a Proc as namespace' do
|
|
21
|
+
proc_namespace = Proc.new { 'dynamic' }
|
|
22
|
+
ns = Valkey::Namespace.new(proc_namespace, valkey: valkey)
|
|
23
|
+
expect(ns.namespace).to eq('dynamic')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#set and #get' do
|
|
28
|
+
it 'namespaces the key for set' do
|
|
29
|
+
expect(valkey).to receive(:set).with('test:foo', 'bar')
|
|
30
|
+
namespaced.set('foo', 'bar')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'namespaces the key for get' do
|
|
34
|
+
expect(valkey).to receive(:get).with('test:foo').and_return('bar')
|
|
35
|
+
expect(namespaced.get('foo')).to eq('bar')
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#keys' do
|
|
40
|
+
it 'namespaces the pattern and removes namespace from results' do
|
|
41
|
+
expect(valkey).to receive(:keys).with('test:*').and_return(['test:foo', 'test:bar'])
|
|
42
|
+
expect(namespaced.keys).to eq(['foo', 'bar'])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'accepts a custom pattern' do
|
|
46
|
+
expect(valkey).to receive(:keys).with('test:foo*').and_return(['test:foo1', 'test:foo2'])
|
|
47
|
+
expect(namespaced.keys('foo*')).to eq(['foo1', 'foo2'])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe '#del' do
|
|
52
|
+
it 'namespaces all keys' do
|
|
53
|
+
expect(valkey).to receive(:del).with('test:foo', 'test:bar').and_return(2)
|
|
54
|
+
expect(namespaced.del('foo', 'bar')).to eq(2)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#mget' do
|
|
59
|
+
it 'namespaces all keys' do
|
|
60
|
+
expect(valkey).to receive(:mget).with('test:foo', 'test:bar').and_return(['val1', 'val2'])
|
|
61
|
+
expect(namespaced.mget('foo', 'bar')).to eq(['val1', 'val2'])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#mset' do
|
|
66
|
+
it 'namespaces alternating keys' do
|
|
67
|
+
expect(valkey).to receive(:mset).with('test:foo', 'val1', 'test:bar', 'val2')
|
|
68
|
+
namespaced.mset('foo', 'val1', 'bar', 'val2')
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#full_namespace' do
|
|
73
|
+
it 'returns the namespace as string' do
|
|
74
|
+
expect(namespaced.full_namespace).to eq('test')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context 'with nested namespaces' do
|
|
78
|
+
it 'combines namespaces with colons' do
|
|
79
|
+
inner_ns = Valkey::Namespace.new('inner', valkey: namespaced)
|
|
80
|
+
expect(inner_ns.full_namespace).to eq('test:inner')
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe '#inspect' do
|
|
86
|
+
it 'includes version and namespace' do
|
|
87
|
+
expect(namespaced.inspect).to include('Valkey::Namespace')
|
|
88
|
+
expect(namespaced.inspect).to include('v1.0.0')
|
|
89
|
+
expect(namespaced.inspect).to include('test')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe 'administrative commands' do
|
|
94
|
+
it 'warns when using flushall' do
|
|
95
|
+
allow(valkey).to receive(:flushall)
|
|
96
|
+
expect { namespaced.flushall }.to output(/administrative commands/).to_stderr
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'raises NoMethodError when deprecations enabled' do
|
|
100
|
+
ns = Valkey::Namespace.new('test', valkey: valkey, deprecations: true)
|
|
101
|
+
expect { ns.flushall }.to raise_error(NoMethodError)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe '#warning?' do
|
|
106
|
+
it 'returns true by default' do
|
|
107
|
+
expect(namespaced.warning?).to be true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'can be disabled via option' do
|
|
111
|
+
ns = Valkey::Namespace.new('test', valkey: valkey, warning: false)
|
|
112
|
+
expect(ns.warning?).to be false
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'can be disabled via environment variable' do
|
|
116
|
+
ENV['VALKEY_NAMESPACE_QUIET'] = '1'
|
|
117
|
+
ns = Valkey::Namespace.new('test', valkey: valkey)
|
|
118
|
+
expect(ns.warning?).to be false
|
|
119
|
+
ENV.delete('VALKEY_NAMESPACE_QUIET')
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: valkey-namespace
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Valkey Community
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-26 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: valkey
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.7'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.7'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec-its
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: connection_pool
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
description: |
|
|
84
|
+
Adds a Valkey::Namespace class which can be used to namespace calls
|
|
85
|
+
to Valkey. This is useful when using a single instance of Valkey with
|
|
86
|
+
multiple, different applications.
|
|
87
|
+
email:
|
|
88
|
+
- maintainer@example.com
|
|
89
|
+
executables: []
|
|
90
|
+
extensions: []
|
|
91
|
+
extra_rdoc_files: []
|
|
92
|
+
files:
|
|
93
|
+
- LICENSE
|
|
94
|
+
- NOTICE
|
|
95
|
+
- README.md
|
|
96
|
+
- Rakefile
|
|
97
|
+
- lib/valkey-namespace.rb
|
|
98
|
+
- lib/valkey/namespace.rb
|
|
99
|
+
- lib/valkey/namespace/version.rb
|
|
100
|
+
- spec/spec_helper.rb
|
|
101
|
+
- spec/valkey_spec.rb
|
|
102
|
+
homepage: https://github.com/valkey-io/valkey-namespace
|
|
103
|
+
licenses:
|
|
104
|
+
- MIT
|
|
105
|
+
metadata:
|
|
106
|
+
bug_tracker_uri: https://github.com/valkey-io/valkey-namespace/issues
|
|
107
|
+
changelog_uri: https://github.com/valkey-io/valkey-namespace/blob/master/CHANGELOG.md
|
|
108
|
+
documentation_uri: https://www.rubydoc.info/gems/valkey-namespace/1.0.0
|
|
109
|
+
source_code_uri: https://github.com/valkey-io/valkey-namespace
|
|
110
|
+
homepage_uri: https://github.com/valkey-io/valkey-namespace
|
|
111
|
+
rubygems_mfa_required: 'true'
|
|
112
|
+
post_install_message:
|
|
113
|
+
rdoc_options: []
|
|
114
|
+
require_paths:
|
|
115
|
+
- lib
|
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - ">="
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '2.7'
|
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - ">="
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: '0'
|
|
126
|
+
requirements: []
|
|
127
|
+
rubygems_version: 3.4.19
|
|
128
|
+
signing_key:
|
|
129
|
+
specification_version: 4
|
|
130
|
+
summary: Namespaces Valkey commands.
|
|
131
|
+
test_files: []
|