redis-time-series 0.5.2 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 461a6f3842867d1ba51440e7576aad3e9b92ecd02700cd59e5508c0e8bbfcaa7
4
- data.tar.gz: 0c03af91af0eaaa37917db2cad21417c38709a53b223ed2864bad0f9857fa1f7
3
+ metadata.gz: 4648678522434687a6e278490c7a308c64f6460394b8ebde16b2ce2daa9e92ad
4
+ data.tar.gz: e2f48fb1c8efebb3b4d6e4c47d1d86137a10ac8a54735c94b3bdb35ef6396651
5
5
  SHA512:
6
- metadata.gz: 56dcd4439b2f4f06469237d2925d84b2ffabc40e2dccb608d5489b78f0e2a5b299881487b55e67fb48f1c9d0f7f56951ee98254ae84229b1827fb4cf3683ca44
7
- data.tar.gz: 860e638f906b9dc19358e6156ad6a0488452be0be499853926742073cf6e85d00f4e95755f1659feae9763334eebbab044147fa3d94512dbb2a91360ec08728b
6
+ metadata.gz: 269a787d8d2a2a467a52e915ab717bc5b20eb28cacc55eb5dfc9ce3909a7d83cdd4e15b19afc92612d19fa551c6ba793df76ceef03d8bb8bdb16b6dc0253d2cc
7
+ data.tar.gz: c5d845052ca8f2a518fb3a20a82a8d8aa35128cb824f54d2531a949767e90f8c7979ede07ba1bd56ae30a825edb9f6be90e52ca404a14199bfd13484026d8d40
@@ -11,9 +11,13 @@ on:
11
11
  jobs:
12
12
  spec:
13
13
  runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ image_version: ['latest', 'edge']
17
+ ruby_version: ['2.6', '2.7']
14
18
  services:
15
19
  redis:
16
- image: redislabs/redistimeseries:latest
20
+ image: redislabs/redistimeseries:${{ matrix.image_version }}
17
21
  ports:
18
22
  - 6379:6379/tcp
19
23
  env:
@@ -25,16 +29,18 @@ jobs:
25
29
  - name: Set up Ruby
26
30
  uses: ruby/setup-ruby@v1
27
31
  with:
28
- ruby-version: 2.6
32
+ ruby-version: ${{ matrix.ruby_version }}
29
33
  - name: Set up CodeClimate
30
34
  run: |
31
35
  curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
32
36
  chmod +x ./cc-test-reporter
33
37
  ./cc-test-reporter before-build
34
38
  - name: Install dependencies
35
- run: bundle install
39
+ run: |
40
+ bundle install
41
+ bundle exec appraisal install
36
42
  - name: Run specs
37
- run: bundle exec rake spec
43
+ run: bundle exec appraisal rake spec
38
44
  - name: Upload coverage report
39
45
  run: ./cc-test-reporter after-build -t simplecov coverage/.resultset.json
40
46
  - uses: actions/upload-artifact@v2
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
@@ -0,0 +1,7 @@
1
+ appraise 'redis 3' do
2
+ gem 'redis', '~> 3.3'
3
+ end
4
+
5
+ appraise 'redis 4' do
6
+ gem 'redis', '~> 4.0'
7
+ end
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.6.0
6
+ * Add CHUNK_SIZE param to CREATE, ADD, INCRBY, DECRBY commands (#53)
7
+ * Add duplication policy to TS.CREATE and TS.ADD commands (#51)
8
+ * Add support for endless ranges to TS.RANGE (#50)
9
+ * Cast label values to integers in Info struct (#49)
10
+ * Build against edge upstream in addition to latest stable (#48)
11
+
5
12
  ## 0.5.2
6
13
  * Add chunk_type to info struct (#47)
7
14
 
@@ -1,9 +1,11 @@
1
1
  require 'bigdecimal'
2
2
  require 'forwardable'
3
3
  require 'ext/time_msec'
4
+
4
5
  require 'redis/time_series/client'
5
6
  require 'redis/time_series/errors'
6
7
  require 'redis/time_series/aggregation'
8
+ require 'redis/time_series/duplicate_policy'
7
9
  require 'redis/time_series/filters'
8
10
  require 'redis/time_series/rule'
9
11
  require 'redis/time_series/info'
@@ -37,6 +37,11 @@ class Redis
37
37
  # With no value, the series will not be trimmed.
38
38
  # @option options [Boolean] :uncompressed
39
39
  # When true, series data will be stored in an uncompressed format.
40
+ # @option options [String, Symbol] :duplicate_policy
41
+ # A duplication policy to resolve conflicts when adding values to the series.
42
+ # Valid values are in Redis::TimeSeries::DuplicatePolicy::VALID_POLICIES
43
+ # @option options [Integer] :chunk_size
44
+ # Amount of memory, in bytes, to allocate for each chunk of data
40
45
  #
41
46
  # @return [Redis::TimeSeries] the created time series
42
47
  # @see https://oss.redislabs.com/redistimeseries/commands/#tscreate
@@ -154,21 +159,33 @@ class Redis
154
159
  # @param value [Numeric] the value to add
155
160
  # @param timestamp [Time, Numeric] the +Time+, or integer timestamp in milliseconds, to add the value
156
161
  # @param uncompressed [Boolean] if true, stores data in an uncompressed format
162
+ # @param on_duplicate [String, Symbol] a duplication policy for conflict resolution
163
+ # @param chunk_size [Integer] set default chunk size, in bytes, for the time series
157
164
  #
158
165
  # @return [Sample] the value that was added
159
166
  # @raise [Redis::CommandError] if the value being added is older than the latest timestamp in the series
160
- def add(value, timestamp = '*', uncompressed: nil)
161
- ts = cmd 'TS.ADD', key, timestamp, value, ('UNCOMPRESSED' if uncompressed)
167
+ #
168
+ # @see TimeSeries::DuplicatePolicy
169
+ def add(value, timestamp = '*', uncompressed: nil, on_duplicate: nil, chunk_size: nil)
170
+ ts = cmd 'TS.ADD',
171
+ key,
172
+ timestamp,
173
+ value,
174
+ ('UNCOMPRESSED' if uncompressed),
175
+ (['CHUNK_SIZE', chunk_size] if chunk_size),
176
+ (DuplicatePolicy.new(on_duplicate).to_a('ON_DUPLICATE') if on_duplicate)
162
177
  Sample.new(ts, value)
163
178
  end
164
179
 
165
180
  # Issues a TS.CREATE command for the current series.
166
181
  # You should use class method {Redis::TimeSeries.create} instead.
167
182
  # @api private
168
- def create(retention: nil, uncompressed: nil, labels: nil)
183
+ def create(retention: nil, uncompressed: nil, labels: nil, duplicate_policy: nil, chunk_size: nil)
169
184
  cmd 'TS.CREATE', key,
170
185
  (['RETENTION', retention] if retention),
171
186
  ('UNCOMPRESSED' if uncompressed),
187
+ (['CHUNK_SIZE', chunk_size] if chunk_size),
188
+ (DuplicatePolicy.new(duplicate_policy).to_a if duplicate_policy),
172
189
  (['LABELS', labels.to_a] if labels&.any?)
173
190
  self
174
191
  end
@@ -206,11 +223,17 @@ class Redis
206
223
  # @param value [Integer] the amount to decrement by
207
224
  # @param timestamp [Time, Integer] the Time or integer millisecond timestamp to save the new value at
208
225
  # @param uncompressed [Boolean] if true, stores data in an uncompressed format
226
+ # @param chunk_size [Integer] set default chunk size, in bytes, for the time series
209
227
  #
210
228
  # @return [Integer] the timestamp the value was stored at
211
229
  # @see https://oss.redislabs.com/redistimeseries/commands/#tsincrbytsdecrby
212
- def decrby(value = 1, timestamp = nil, uncompressed: nil)
213
- cmd 'TS.DECRBY', key, value, (timestamp if timestamp), ('UNCOMPRESSED' if uncompressed)
230
+ def decrby(value = 1, timestamp = nil, uncompressed: nil, chunk_size: nil)
231
+ cmd 'TS.DECRBY',
232
+ key,
233
+ value,
234
+ (timestamp if timestamp),
235
+ ('UNCOMPRESSED' if uncompressed),
236
+ (['CHUNK_SIZE', chunk_size] if chunk_size)
214
237
  end
215
238
  alias decrement decrby
216
239
 
@@ -241,11 +264,17 @@ class Redis
241
264
  # @param value [Integer] the amount to increment by
242
265
  # @param timestamp [Time, Integer] the Time or integer millisecond timestamp to save the new value at
243
266
  # @param uncompressed [Boolean] if true, stores data in an uncompressed format
267
+ # @param chunk_size [Integer] set default chunk size, in bytes, for the time series
244
268
  #
245
269
  # @return [Integer] the timestamp the value was stored at
246
270
  # @see https://oss.redislabs.com/redistimeseries/commands/#tsincrbytsdecrby
247
- def incrby(value = 1, timestamp = nil, uncompressed: nil)
248
- cmd 'TS.INCRBY', key, value, (timestamp if timestamp), ('UNCOMPRESSED' if uncompressed)
271
+ def incrby(value = 1, timestamp = nil, uncompressed: nil, chunk_size: nil)
272
+ cmd 'TS.INCRBY',
273
+ key,
274
+ value,
275
+ (timestamp if timestamp),
276
+ ('UNCOMPRESSED' if uncompressed),
277
+ (['CHUNK_SIZE', chunk_size] if chunk_size)
249
278
  end
250
279
  alias increment incrby
251
280
 
@@ -312,12 +341,12 @@ class Redis
312
341
  # `range` will swallow all parameters if they're all hash syntax
313
342
  count = range.delete(:count)
314
343
  aggregation = range.delete(:aggregation)
315
- range = range.fetch(:from)..range.fetch(:to)
344
+ range = range.fetch(:from)..range[:to]
316
345
  end
317
346
  cmd('TS.RANGE',
318
347
  key,
319
- range.min,
320
- range.max,
348
+ (range.begin || '-'),
349
+ (range.end || '+'),
321
350
  (['COUNT', count] if count),
322
351
  Aggregation.parse(aggregation)&.to_a
323
352
  ).map { |ts, val| Sample.new(ts, val) }
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ class Redis
3
+ class TimeSeries
4
+ # Duplication policies can be applied to a time series in order to resolve conflicts
5
+ # when adding data that already exists in the series.
6
+ #
7
+ # @see https://oss.redislabs.com/redistimeseries/master/configuration/#duplicate_policy
8
+ class DuplicatePolicy
9
+ VALID_POLICIES = %i[
10
+ block
11
+ first
12
+ last
13
+ min
14
+ max
15
+ sum
16
+ ].freeze
17
+
18
+ attr_reader :policy
19
+
20
+ def initialize(policy)
21
+ policy = policy.to_s.downcase.to_sym
22
+ if VALID_POLICIES.include?(policy)
23
+ @policy = policy
24
+ else
25
+ raise UnknownPolicyError, "#{policy} is not a valid duplicate policy"
26
+ end
27
+ end
28
+
29
+ def to_a(cmd = 'DUPLICATE_POLICY')
30
+ [cmd, policy]
31
+ end
32
+
33
+ def to_s(cmd = 'DUPLICATE_POLICY')
34
+ to_a(cmd).join(' ')
35
+ end
36
+
37
+ def ==(other)
38
+ return policy == other.policy if other.is_a?(self.class)
39
+ policy == self.class.new(other).policy
40
+ end
41
+
42
+ VALID_POLICIES.each do |policy|
43
+ define_method("#{policy}?") do
44
+ @policy == policy
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -15,5 +15,10 @@ class Redis
15
15
  # an unknown type, or when calling a command with an invalid aggregation value.
16
16
  # @see Redis::TimeSeries::Aggregation
17
17
  class AggregationError < Error; end
18
+
19
+ # +UnknownPolicyError+ is raised when attempting to apply an unkown type of
20
+ # duplicate policy when creating or adding to a series.
21
+ # @see Redis::TimeSeries::DuplicatePolicy
22
+ class UnknownPolicyError < Error; end
18
23
  end
19
24
  end
@@ -10,6 +10,10 @@ class Redis
10
10
  #
11
11
  # @!attribute [r] chunk_count
12
12
  # @return [Integer] number of memory chunks used for the time-series
13
+ # @!attribute [r] chunk_size
14
+ # @return [Integer] amount of allocated memory in bytes
15
+ # @!attribute [r] chunk_type
16
+ # @return [String] whether the chunk is "compressed" or "uncompressed"
13
17
  # @!attribute [r] first_timestamp
14
18
  # @return [Integer] first timestamp present in the time-series (milliseconds since epoch)
15
19
  # @!attribute [r] labels
@@ -52,20 +56,43 @@ class Redis
52
56
  :total_samples,
53
57
  keyword_init: true
54
58
  ) do
55
- # @api private
56
- # @return [Info]
57
- def self.parse(series:, data:)
58
- data.each_slice(2).reduce({}) do |h, (key, value)|
59
- # Convert camelCase info keys to snake_case
60
- key = key.gsub(/(.)([A-Z])/,'\1_\2').downcase.to_sym
61
- next h unless members.include?(key)
62
- h[key] = value
63
- h
64
- end.then do |parsed_hash|
65
- parsed_hash[:series] = series
66
- parsed_hash[:labels] = parsed_hash[:labels].to_h
67
- parsed_hash[:rules] = parsed_hash[:rules].map { |d| Rule.new(source: series, data: d) }
68
- new(parsed_hash)
59
+ class << self
60
+ # @api private
61
+ # @return [Info]
62
+ def parse(series:, data:)
63
+ build_hash(data)
64
+ .merge(series: series)
65
+ .then(&method(:parse_labels))
66
+ .then(&method(:parse_policies))
67
+ .then(&method(:parse_rules))
68
+ .then(&method(:new))
69
+ end
70
+
71
+ private
72
+
73
+ def build_hash(data)
74
+ data.each_slice(2).reduce({}) do |h, (key, value)|
75
+ # Convert camelCase info keys to snake_case
76
+ key = key.gsub(/(.)([A-Z])/,'\1_\2').downcase.to_sym
77
+ # Skip unknown properties
78
+ next h unless members.include?(key)
79
+ h.merge(key => value)
80
+ end
81
+ end
82
+
83
+ def parse_labels(hash)
84
+ hash[:labels] = hash[:labels].to_h.transform_values { |v| v.to_i.to_s == v ? v.to_i : v }
85
+ hash
86
+ end
87
+
88
+ def parse_policies(hash)
89
+ hash[:duplicate_policy] = DuplicatePolicy.new(hash[:duplicate_policy]) if hash[:duplicate_policy]
90
+ hash
91
+ end
92
+
93
+ def parse_rules(hash)
94
+ hash[:rules] = hash[:rules].map { |d| Rule.new(source: hash[:series], data: d) }
95
+ hash
69
96
  end
70
97
  end
71
98
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  class Redis
3
3
  class TimeSeries
4
- VERSION = '0.5.2'
4
+ VERSION = '0.6.0'
5
5
  end
6
6
  end
@@ -31,10 +31,11 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ['lib']
33
33
 
34
- spec.add_dependency 'redis', '~> 4.0'
34
+ spec.add_dependency 'redis', '>= 3.3', '< 5'
35
35
 
36
36
  spec.add_development_dependency 'activesupport', '~> 6.0'
37
- spec.add_development_dependency 'bundler', '~> 1.17'
37
+ spec.add_development_dependency 'appraisal'
38
+ spec.add_development_dependency 'bundler', '~> 2.0'
38
39
  spec.add_development_dependency 'pry', '~> 0.13'
39
40
  spec.add_development_dependency 'rake', '~> 13.0'
40
41
  spec.add_development_dependency 'rspec', '~> 3.0'
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-time-series
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Duszynski
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-10 00:00:00.000000000 Z
11
+ date: 2020-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.3'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '4.0'
22
+ version: '5'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '4.0'
29
+ version: '3.3'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: activesupport
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -38,20 +44,34 @@ dependencies:
38
44
  - - "~>"
39
45
  - !ruby/object:Gem::Version
40
46
  version: '6.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: appraisal
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: bundler
43
63
  requirement: !ruby/object:Gem::Requirement
44
64
  requirements:
45
65
  - - "~>"
46
66
  - !ruby/object:Gem::Version
47
- version: '1.17'
67
+ version: '2.0'
48
68
  type: :development
49
69
  prerelease: false
50
70
  version_requirements: !ruby/object:Gem::Requirement
51
71
  requirements:
52
72
  - - "~>"
53
73
  - !ruby/object:Gem::Version
54
- version: '1.17'
74
+ version: '2.0'
55
75
  - !ruby/object:Gem::Dependency
56
76
  name: pry
57
77
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +128,7 @@ dependencies:
108
128
  - - "<"
109
129
  - !ruby/object:Gem::Version
110
130
  version: '0.18'
111
- description:
131
+ description:
112
132
  email:
113
133
  - dzunk@hey.com
114
134
  executables: []
@@ -118,9 +138,9 @@ files:
118
138
  - ".github/workflows/rspec.yml"
119
139
  - ".gitignore"
120
140
  - ".rspec"
141
+ - Appraisals
121
142
  - CHANGELOG.md
122
143
  - Gemfile
123
- - Gemfile.lock
124
144
  - LICENSE.txt
125
145
  - README.md
126
146
  - Rakefile
@@ -131,6 +151,7 @@ files:
131
151
  - lib/redis/time_series.rb
132
152
  - lib/redis/time_series/aggregation.rb
133
153
  - lib/redis/time_series/client.rb
154
+ - lib/redis/time_series/duplicate_policy.rb
134
155
  - lib/redis/time_series/errors.rb
135
156
  - lib/redis/time_series/filters.rb
136
157
  - lib/redis/time_series/info.rb
@@ -145,7 +166,7 @@ metadata:
145
166
  homepage_uri: https://github.com/dzunk/redis-time-series
146
167
  source_code_uri: https://github.com/dzunk/redis-time-series
147
168
  changelog_uri: https://github.com/dzunk/redis-time-series/blob/master/CHANGELOG.md
148
- post_install_message:
169
+ post_install_message:
149
170
  rdoc_options: []
150
171
  require_paths:
151
172
  - lib
@@ -160,8 +181,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
181
  - !ruby/object:Gem::Version
161
182
  version: '0'
162
183
  requirements: []
163
- rubygems_version: 3.0.3
164
- signing_key:
184
+ rubygems_version: 3.2.2
185
+ signing_key:
165
186
  specification_version: 4
166
187
  summary: A Ruby adapter for the RedisTimeSeries module.
167
188
  test_files: []
@@ -1,66 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- redis-time-series (0.5.2)
5
- redis (~> 4.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (6.0.3.1)
11
- concurrent-ruby (~> 1.0, >= 1.0.2)
12
- i18n (>= 0.7, < 2)
13
- minitest (~> 5.1)
14
- tzinfo (~> 1.1)
15
- zeitwerk (~> 2.2, >= 2.2.2)
16
- coderay (1.1.3)
17
- concurrent-ruby (1.1.6)
18
- diff-lcs (1.3)
19
- docile (1.3.2)
20
- i18n (1.8.3)
21
- concurrent-ruby (~> 1.0)
22
- json (2.3.1)
23
- method_source (1.0.0)
24
- minitest (5.14.1)
25
- pry (0.13.1)
26
- coderay (~> 1.1)
27
- method_source (~> 1.0)
28
- rake (13.0.1)
29
- redis (4.2.2)
30
- rspec (3.9.0)
31
- rspec-core (~> 3.9.0)
32
- rspec-expectations (~> 3.9.0)
33
- rspec-mocks (~> 3.9.0)
34
- rspec-core (3.9.2)
35
- rspec-support (~> 3.9.3)
36
- rspec-expectations (3.9.2)
37
- diff-lcs (>= 1.2.0, < 2.0)
38
- rspec-support (~> 3.9.0)
39
- rspec-mocks (3.9.1)
40
- diff-lcs (>= 1.2.0, < 2.0)
41
- rspec-support (~> 3.9.0)
42
- rspec-support (3.9.3)
43
- simplecov (0.17.1)
44
- docile (~> 1.1)
45
- json (>= 1.8, < 3)
46
- simplecov-html (~> 0.10.0)
47
- simplecov-html (0.10.2)
48
- thread_safe (0.3.6)
49
- tzinfo (1.2.7)
50
- thread_safe (~> 0.1)
51
- zeitwerk (2.3.0)
52
-
53
- PLATFORMS
54
- ruby
55
-
56
- DEPENDENCIES
57
- activesupport (~> 6.0)
58
- bundler (~> 1.17)
59
- pry (~> 0.13)
60
- rake (~> 13.0)
61
- redis-time-series!
62
- rspec (~> 3.0)
63
- simplecov (< 0.18)
64
-
65
- BUNDLED WITH
66
- 1.17.2