Class: Familia::SortedSet
- Includes:
- DataType::CollectionBase
- Defined in:
- lib/familia/data_type/types/sorted_set.rb
Instance Attribute Summary collapse
-
#features_enabled ⇒ Object
included
from Features
readonly
Returns the value of attribute features_enabled.
- #logical_database(val = nil) ⇒ Object included from DataType::ClassMethods
-
#parent ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute parent.
-
#prefix ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute prefix.
-
#suffix ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute suffix.
-
#uri(val = nil) ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute uri.
Attributes included from Settings
#current_key_version, #default_expiration, #delim, #encryption_hkdf_salt, #encryption_hkdf_salt_history, #encryption_keys, #encryption_personalization, #encryption_personalization_history, #logical_database, #prefix, #raise_on_unsaved_parent_write, #schema_path, #schema_validator, #schemas, #strict_write_order, #suffix, #transaction_mode
Class Method Summary collapse
-
.supports_max_length? ⇒ Boolean
SortedSet implements :max_length (capped collection, issue #351): a cap of N retains the N HIGHEST-scoring members — trim is ZREMRANGEBYRANK key 0 -(N+1), removing from the low-score end.
Instance Method Summary collapse
-
#<<(val) ⇒ Integer
Adds a new element to the sorted set with the current timestamp as the score.
-
#[]=(val, score) ⇒ Object
NOTE: The argument order is the reverse of #add.
-
#add(val, score = nil, nx: false, xx: false, gt: false, lt: false, ch: false) ⇒ Boolean
(also: #add_element)
Adds an element to the sorted set with an optional score and ZADD options.
- #at(idx) ⇒ Object
- #collectraw ⇒ Object
- #decrement(val, by = 1) ⇒ Object (also: #decr, #decrby)
-
#diff(*other_sets, withscores: false) ⇒ Array
Returns the difference between this sorted set and other sorted sets.
-
#diffstore(destination, *other_sets) ⇒ Integer
Stores the difference of sorted sets into a destination key.
-
#each(since: nil, batch_size: 100, **kwargs) {|member| ... } ⇒ Enumerator, self
Iterates over members of the sorted set.
- #eachraw ⇒ Object
- #eachraw_with_index ⇒ Object
-
#element_count ⇒ Integer
(also: #size, #length, #count)
Returns the number of elements in the sorted set.
- #empty? ⇒ Boolean
-
#enforce_max_length! ⇒ Integer, Redis::Future
Trims an already-oversized sorted set down to its :max_length cap, keeping the max_length highest-scoring members — the same trim every capped write applies (ZREMRANGEBYRANK 0 -(max_length+1)).
-
#first ⇒ Object
Return the first element in the list.
- #increment(val, by = 1) ⇒ Object (also: #incr, #incrby)
-
#inter(*other_sets, weights: nil, aggregate: nil, withscores: false) ⇒ Array
Returns the intersection of this sorted set with other sorted sets.
-
#interstore(destination, *other_sets, weights: nil, aggregate: nil) ⇒ Integer
Stores the intersection of sorted sets into a destination key.
-
#last ⇒ Object
Return the last element in the list.
-
#lexcount(min, max) ⇒ Integer
Counts members in a lexicographical range.
- #member?(val) ⇒ Boolean (also: #include?)
- #members(count = -1,, opts = {}) ⇒ Object (also: #to_a, #all)
- #membersraw(count = -1,, opts = {}) ⇒ Object
-
#mscore(*members) ⇒ Array<Float, nil>
Gets scores for multiple members at once.
-
#popmax(count = 1) ⇒ Array?
Removes and returns the member(s) with the highest score(s).
-
#popmin(count = 1) ⇒ Array?
Removes and returns the member(s) with the lowest score(s).
-
#randmember(count = nil, withscores: false) ⇒ Object, ...
Returns random member(s) from the sorted set.
- #range(sidx, eidx, opts = {}) ⇒ Object
-
#rangebylex(min, max, limit: nil) ⇒ Array
Returns members in a lexicographical range (requires all members have same score).
-
#rangebyscore(sscore, escore, opts = {}) ⇒ Object
e.g.
- #rangebyscoreraw(sscore, escore, opts = {}) ⇒ Object
- #rangeraw(sidx, eidx, opts = {}) ⇒ Object
-
#rank(v) ⇒ Object
rank of member +v+ when ordered lowest to highest (starts at 0).
-
#remove_element(value) ⇒ Integer
(also: #remove)
Removes a member from the sorted set.
-
#remrangebylex(min, max) ⇒ Integer
Removes members in a lexicographical range.
- #remrangebyrank(srank, erank) ⇒ Object
- #remrangebyscore(sscore, escore) ⇒ Object
- #revmembers(count = -1,, opts = {}) ⇒ Object
- #revmembersraw(count = -1,, opts = {}) ⇒ Object
- #revrange(sidx, eidx, opts = {}) ⇒ Object
-
#revrangebylex(max, min, limit: nil) ⇒ Array
Returns members in reverse lexicographical range.
-
#revrangebyscore(sscore, escore, opts = {}) ⇒ Object
e.g.
- #revrangebyscoreraw(sscore, escore, opts = {}) ⇒ Object
- #revrangeraw(sidx, eidx, opts = {}) ⇒ Object
-
#revrank(v) ⇒ Object
rank of member +v+ when ordered highest to lowest (starts at 0).
-
#scan(cursor = 0, match: nil, count: nil) ⇒ Array
Iterates over members using cursor-based scanning.
- #score(val) ⇒ Object (also: #[])
-
#score_count(min, max) ⇒ Integer
(also: #zcount)
Counts members within a score range.
- #selectraw ⇒ Object
-
#union(*other_sets, weights: nil, aggregate: nil, withscores: false) ⇒ Array
Returns the union of this sorted set with other sorted sets.
-
#unionstore(destination, *other_sets, weights: nil, aggregate: nil) ⇒ Integer
Stores the union of sorted sets into a destination key.
-
#update(hsh = {}) ⇒ Integer
(also: #merge!)
Bulk-adds or updates multiple members in a single ZADD.
Methods included from DataType::CollectionBase
#collection_type?, #each_record
Methods included from Features::Autoloader
autoload_files, included, normalize_to_config_name
Methods included from DataType::Serialization
#deserialize_value, #deserialize_values, #deserialize_values_with_nil, #serialize_value, #strip_legacy_json_encoding
Methods included from DataType::DatabaseCommands
#current_expiration, #delete!, #echo, #exists?, #expire, #expireat, #move, #persist, #rename, #renamenx, #type
Methods included from DataType::Connection
Methods included from Connection::Behavior
#connect, #create_dbclient, #multi, #normalize_uri, #pipeline, #pipelined, #transaction, #uri=, #url, #url=
Methods included from Settings
#configure, #default_suffix, #dirty_write_warnings, #dirty_write_warnings=, #pipelined_mode, #pipelined_mode=
Methods included from Base
add_feature, #as_json, #expired?, #expires?, find_feature, #generate_id, #to_json, #to_s, #ttl, #update_expiration, #uuid
Constructor Details
This class inherits a constructor from Familia::DataType
Instance Attribute Details
#features_enabled ⇒ Object (readonly) Originally defined in module Features
Returns the value of attribute features_enabled.
#logical_database(val = nil) ⇒ Object Originally defined in module DataType::ClassMethods
#parent ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute parent.
#prefix ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute prefix.
#suffix ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute suffix.
#uri(val = nil) ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute uri.
Class Method Details
.supports_max_length? ⇒ Boolean
SortedSet implements :max_length (capped collection, issue #351): a cap of N retains the N HIGHEST-scoring members — trim is ZREMRANGEBYRANK key 0 -(N+1), removing from the low-score end. When scores are timestamps (the #add default) this keeps the newest N; with arbitrary scores it keeps the top-N by score, not by recency.
All member-creating paths trim: #add (covers << and []=), #update /
merge! (bulk ZADD), and #increment / #decrement (ZINCRBY creates the
member if absent). The trim is issued unconditionally after the write — an under-cap trim is an O(log N) no-op, and it is harmless when an NX/XX/GT/LT constraint skipped the write. NOT capped (external writers beware): unionstore/interstore/diffstore destination keys and restore.
21 22 23 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 21 def self.supports_max_length? true end |
Instance Method Details
#<<(val) ⇒ Integer
This is a non-standard operation for sorted sets as it doesn't allow
specifying a custom score. Use add or []= for more control.
Adds a new element to the sorted set with the current timestamp as the score.
This method provides a convenient way to add elements to the sorted set without explicitly specifying a score. It uses the current Unix timestamp as the score, which effectively sorts elements by their insertion time.
54 55 56 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 54 def <<(val) add(val) end |
#[]=(val, score) ⇒ Object
NOTE: The argument order is the reverse of #add. We do this to more naturally align with how the [] and []= methods are used.
e.g. obj.metrics[VALUE] = SCORE obj.metrics[VALUE] # => SCORE
65 66 67 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 65 def []=(val, score) add val, score end |
#add(val, score = nil, nx: false, xx: false, gt: false, lt: false, ch: false) ⇒ Boolean Also known as: add_element
GT and LT options do NOT prevent adding new elements, they only affect update behavior for existing elements.
Default behavior (no options) adds new elements and updates existing ones unconditionally, matching standard Redis ZADD semantics.
INCR option is not supported. Use the increment method for ZINCRBY operations.
This method executes a Redis ZADD immediately, unlike scalar field setters which are deferred until save. If the parent object has unsaved scalar field changes, consider calling save first to avoid split-brain state.
Adds an element to the sorted set with an optional score and ZADD options.
This method supports Redis ZADD options for conditional adds and updates:
- NX: Only add new elements (don't update existing)
- XX: Only update existing elements (don't add new)
- GT: Only update if new score > current score
- LT: Only update if new score < current score
- CH: Return changed count (new + updated) instead of just new count
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 127 def add(val, score = nil, nx: false, xx: false, gt: false, lt: false, ch: false) warn_if_dirty! score ||= Familia.now # Validate mutual exclusivity (nx: nx, xx: xx, gt: gt, lt: lt) # Build options hash for redis gem opts = {} opts[:nx] = true if nx opts[:xx] = true if xx opts[:gt] = true if gt opts[:lt] = true if lt opts[:ch] = true if ch # Pass options to ZADD ret = capped_zadd_write do |conn| if opts.empty? conn.zadd(dbkey, score, serialize_value(val)) else conn.zadd(dbkey, score, serialize_value(val), **opts) end end update_expiration ret end |
#at(idx) ⇒ Object
455 456 457 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 455 def at(idx) range(idx, idx).first end |
#collectraw ⇒ Object
334 335 336 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 334 def collectraw(&) membersraw.collect(&) end |
#decrement(val, by = 1) ⇒ Object Also known as: decr, decrby
437 438 439 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 437 def decrement(val, by = 1) increment val, -by end |
#diff(*other_sets, withscores: false) ⇒ Array
Returns the difference between this sorted set and other sorted sets.
779 780 781 782 783 784 785 786 787 788 789 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 779 def diff(*other_sets, withscores: false) keys = [dbkey] + resolve_set_keys(other_sets) result = if withscores dbclient.zdiff(*keys, withscores: true) else dbclient.zdiff(*keys) end process_set_operation_result(result, withscores: withscores) end |
#diffstore(destination, *other_sets) ⇒ Integer
Stores the difference of sorted sets into a destination key.
797 798 799 800 801 802 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 797 def diffstore(destination, *other_sets) keys = [dbkey] + resolve_set_keys(other_sets) result = dbclient.zdiffstore(destination, keys) update_expiration result end |
#each(since: nil, batch_size: 100, **kwargs) {|member| ... } ⇒ Enumerator, self
The until: parameter uses Ruby keyword syntax. Since until is
a reserved word, it's accessed via **kwargs internally.
Score-cursor pagination: bounded queries paginate using the last seen score as the next exclusive minimum. This is O(n) total work, unlike offset-based LIMIT which is O(n²). However, members with identical scores may be skipped between pages. Use high-precision floats (e.g., Familia.now) for scores to avoid collisions.
Iterates over members of the sorted set.
When called with score bounds (since/until), uses ZRANGEBYSCORE for efficient range queries. Otherwise uses ZSCAN for memory-efficient iteration over large sets.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 283 def each(since: nil, batch_size: 100, **kwargs, &block) until_score = kwargs.delete(:until) raise ArgumentError, "unknown keyword(s): #{kwargs.keys.join(', ')}" if kwargs.any? return to_enum(:each, since: since, until: until_score, batch_size: batch_size) unless block # Convert Time objects to numeric scores since_score = since.is_a?(Time) ? since.to_f : since until_score_val = until_score.is_a?(Time) ? until_score.to_f : until_score if since_score || until_score_val # Score-cursor pagination: track last score, use exclusive bound for next page min = since_score || '-inf' max = until_score_val || '+inf' loop do # with_scores returns nested pairs: [["a", 1.0], ["b", 2.0]] pairs = rangebyscoreraw(min, max, limit: [0, batch_size], with_scores: true) break if pairs.empty? pairs.each do |raw_member, score| yield deserialize_value(raw_member) min = "(#{score}" # exclusive bound for next iteration end break if pairs.size < batch_size end else # Use ZSCAN for unbounded iteration (memory-efficient) cursor = 0 loop do new_cursor, pairs = scan(cursor, count: batch_size) # ZSCAN yields an Array of [member, score] pairs (not a Hash), so # Hash#each_key would raise NoMethodError here. The cop cannot see # that and its autocorrect breaks this loop — keep it disabled. pairs.each { |member, _score| block.call(member) } # rubocop:disable Style/HashEachMethods cursor = new_cursor break if cursor.zero? end end self end |
#eachraw ⇒ Object
326 327 328 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 326 def eachraw(&) membersraw.each(&) end |
#eachraw_with_index ⇒ Object
330 331 332 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 330 def eachraw_with_index(&) membersraw.each_with_index(&) end |
#element_count ⇒ Integer Also known as: size, length, count
Returns the number of elements in the sorted set
27 28 29 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 27 def element_count dbclient.zcard dbkey end |
#empty? ⇒ Boolean
34 35 36 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 34 def empty? element_count.zero? end |
#enforce_max_length! ⇒ Integer, Redis::Future
Trims an already-oversized sorted set down to its :max_length cap, keeping the max_length highest-scoring members — the same trim every capped write applies (ZREMRANGEBYRANK 0 -(max_length+1)).
Capped writes enforce the cap going forward, but nothing runs at definition time: adding max_length: to a live collection leaves the existing overage in place until the next write. Call this once, e.g. as a migration step, to enforce the cap immediately.
408 409 410 411 412 413 414 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 408 def enforce_max_length! max = require_max_length! warn_if_dirty! removed = dbclient.zremrangebyrank(dbkey, 0, -(max + 1)) update_expiration removed end |
#first ⇒ Object
Return the first element in the list. Redis: ZRANGE(0)
460 461 462 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 460 def first at(0) end |
#increment(val, by = 1) ⇒ Object Also known as: incr, incrby
423 424 425 426 427 428 429 430 431 432 433 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 423 def increment(val, by = 1) warn_if_dirty! # ZINCRBY creates the member if absent, so it is a capped path too. ret = capped_zadd_write { |conn| conn.zincrby(dbkey, by, serialize_value(val)) } # Inside a transaction or pipeline the write is a Redis::Future, which # cannot be coerced until the block commits — pass it through untouched # (same contract as #add). See Familia.positive? for the same idiom. ret = ret.to_f unless ret.is_a?(Redis::Future) update_expiration ret end |
#inter(*other_sets, weights: nil, aggregate: nil, withscores: false) ⇒ Array
Returns the intersection of this sorted set with other sorted sets.
608 609 610 611 612 613 614 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 608 def inter(*other_sets, weights: nil, aggregate: nil, withscores: false) keys = [dbkey] + resolve_set_keys(other_sets) opts = build_set_operation_opts(weights: weights, aggregate: aggregate, withscores: withscores) result = dbclient.zinter(*keys, **opts) process_set_operation_result(result, withscores: withscores) end |
#interstore(destination, *other_sets, weights: nil, aggregate: nil) ⇒ Integer
Stores the intersection of sorted sets into a destination key.
761 762 763 764 765 766 767 768 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 761 def interstore(destination, *other_sets, weights: nil, aggregate: nil) keys = [dbkey] + resolve_set_keys(other_sets) opts = build_set_operation_opts(weights: weights, aggregate: aggregate) result = dbclient.zinterstore(destination, keys, **opts) update_expiration result end |
#last ⇒ Object
Return the last element in the list. Redis: ZRANGE(-1)
465 466 467 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 465 def last at(-1) end |
#lexcount(min, max) ⇒ Integer
Counts members in a lexicographical range.
665 666 667 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 665 def lexcount(min, max) dbclient.zlexcount(dbkey, min, max) end |
#member?(val) ⇒ Boolean Also known as: include?
209 210 211 212 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 209 def member?(val) Familia.trace :MEMBER, nil, "#{val}<#{val.class}>" if Familia.debug? !rank(val).nil? end |
#members(count = -1,, opts = {}) ⇒ Object Also known as: to_a, all
227 228 229 230 231 232 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 227 def members(count = -1, opts = {}) # NOTE: count math (positive count -> end index) is handled once by # membersraw. Do not decrement here too, or members(n) returns n-1. elements = membersraw count, opts deserialize_values(*elements) end |
#membersraw(count = -1,, opts = {}) ⇒ Object
236 237 238 239 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 236 def membersraw(count = -1, opts = {}) count -= 1 if count.positive? rangeraw 0, count, opts end |
#mscore(*members) ⇒ Array<Float, nil>
Gets scores for multiple members at once.
566 567 568 569 570 571 572 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 566 def mscore(*members) return [] if members.empty? serialized = members.map { |m| serialize_value(m) } result = dbclient.zmscore(dbkey, *serialized) result.map { |s| s&.to_f } end |
#popmax(count = 1) ⇒ Array?
Removes and returns the member(s) with the highest score(s).
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 517 def popmax(count = 1) warn_if_dirty! # Normalize explicit nil to the default so the structural dispatch below # behaves identically whether the arg is omitted or passed as nil. # redis-rb treats nil as count <= 1 and returns a flat pair. count = 1 if count.nil? result = dbclient.zpopmax(dbkey, count) return nil if result.nil? || result.empty? update_expiration # redis-rb returns a flat [member, score] pair when count <= 1, and a # nested [[member, score], ...] array when count > 1. Normalize by # inspecting the result's structure rather than relying on count alone, # so that a redis-rb version change or a member that serializes to an # array-like string cannot mislead the dispatch. if count == 1 pair = result.first.is_a?(Array) ? result.first : result [deserialize_value(pair[0]), pair[1].to_f] else result.map { |member, score| [deserialize_value(member), score.to_f] } end end |
#popmin(count = 1) ⇒ Array?
Removes and returns the member(s) with the lowest score(s).
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 481 def popmin(count = 1) warn_if_dirty! # Normalize explicit nil to the default so the structural dispatch below # behaves identically whether the arg is omitted or passed as nil. # redis-rb treats nil as count <= 1 and returns a flat pair. count = 1 if count.nil? result = dbclient.zpopmin(dbkey, count) return nil if result.nil? || result.empty? update_expiration # redis-rb returns a flat [member, score] pair when count <= 1, and a # nested [[member, score], ...] array when count > 1. Normalize by # inspecting the result's structure rather than relying on count alone, # so that a redis-rb version change or a member that serializes to an # array-like string cannot mislead the dispatch. if count == 1 pair = result.first.is_a?(Array) ? result.first : result [deserialize_value(pair[0]), pair[1].to_f] else result.map { |member, score| [deserialize_value(member), score.to_f] } end end |
#randmember(count = nil, withscores: false) ⇒ Object, ...
Returns random member(s) from the sorted set.
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 684 def randmember(count = nil, withscores: false) if count.nil? result = dbclient.zrandmember(dbkey) return nil if result.nil? deserialize_value(result) else result = if withscores dbclient.zrandmember(dbkey, count, withscores: true) else dbclient.zrandmember(dbkey, count) end return [] if result.nil? || result.empty? if withscores result.map { |member, score| [deserialize_value(member), score.to_f] } else deserialize_values(*result) end end end |
#range(sidx, eidx, opts = {}) ⇒ Object
342 343 344 345 346 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 342 def range(sidx, eidx, opts = {}) echo :range, Familia.pretty_stack(limit: 1) if Familia.debug elements = rangeraw(sidx, eidx, opts) deserialize_values(*elements) end |
#rangebylex(min, max, limit: nil) ⇒ Array
Returns members in a lexicographical range (requires all members have same score).
629 630 631 632 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 629 def rangebylex(min, max, limit: nil) result = dbclient.zrangebylex(dbkey, min, max, limit: limit) deserialize_values(*result) end |
#rangebyscore(sscore, escore, opts = {}) ⇒ Object
e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
364 365 366 367 368 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 364 def rangebyscore(sscore, escore, opts = {}) echo :rangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug elements = rangebyscoreraw(sscore, escore, opts) deserialize_values(*elements) end |
#rangebyscoreraw(sscore, escore, opts = {}) ⇒ Object
370 371 372 373 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 370 def rangebyscoreraw(sscore, escore, opts = {}) echo :rangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug dbclient.zrangebyscore(dbkey, sscore, escore, **opts) end |
#rangeraw(sidx, eidx, opts = {}) ⇒ Object
348 349 350 351 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 348 def rangeraw(sidx, eidx, opts = {}) # NOTE: Redis 5.x gem uses :with_scores (with underscore) dbclient.zrange(dbkey, sidx, eidx, **opts) end |
#rank(v) ⇒ Object
rank of member +v+ when ordered lowest to highest (starts at 0)
216 217 218 219 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 216 def rank(v) ret = dbclient.zrank dbkey, serialize_value(v) ret&.to_i end |
#remove_element(value) ⇒ Integer Also known as: remove
Removes a member from the sorted set
446 447 448 449 450 451 452 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 446 def remove_element(value) warn_if_dirty! Familia.trace :REMOVE_ELEMENT, nil, "#{value}<#{value.class}>" if Familia.debug? ret = dbclient.zrem dbkey, serialize_value(value) update_expiration ret end |
#remrangebylex(min, max) ⇒ Integer
Removes members in a lexicographical range.
652 653 654 655 656 657 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 652 def remrangebylex(min, max) warn_if_dirty! result = dbclient.zremrangebylex(dbkey, min, max) update_expiration result end |
#remrangebyrank(srank, erank) ⇒ Object
388 389 390 391 392 393 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 388 def remrangebyrank(srank, erank) warn_if_dirty! ret = dbclient.zremrangebyrank dbkey, srank, erank update_expiration ret end |
#remrangebyscore(sscore, escore) ⇒ Object
416 417 418 419 420 421 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 416 def remrangebyscore(sscore, escore) warn_if_dirty! ret = dbclient.zremrangebyscore dbkey, sscore, escore update_expiration ret end |
#revmembers(count = -1,, opts = {}) ⇒ Object
241 242 243 244 245 246 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 241 def revmembers(count = -1, opts = {}) # See #members: revmembersraw already converts a positive count to the # correct end index; decrementing here as well would drop one element. elements = revmembersraw count, opts deserialize_values(*elements) end |
#revmembersraw(count = -1,, opts = {}) ⇒ Object
248 249 250 251 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 248 def revmembersraw(count = -1, opts = {}) count -= 1 if count.positive? revrangeraw 0, count, opts end |
#revrange(sidx, eidx, opts = {}) ⇒ Object
353 354 355 356 357 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 353 def revrange(sidx, eidx, opts = {}) echo :revrange, Familia.pretty_stack(limit: 1) if Familia.debug elements = revrangeraw(sidx, eidx, opts) deserialize_values(*elements) end |
#revrangebylex(max, min, limit: nil) ⇒ Array
Returns members in reverse lexicographical range.
641 642 643 644 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 641 def revrangebylex(max, min, limit: nil) result = dbclient.zrevrangebylex(dbkey, max, min, limit: limit) deserialize_values(*result) end |
#revrangebyscore(sscore, escore, opts = {}) ⇒ Object
e.g. obj.metrics.revrangebyscore (now-12.hours), now, :limit => [0, 10]
376 377 378 379 380 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 376 def revrangebyscore(sscore, escore, opts = {}) echo :revrangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug elements = revrangebyscoreraw(sscore, escore, opts) deserialize_values(*elements) end |
#revrangebyscoreraw(sscore, escore, opts = {}) ⇒ Object
382 383 384 385 386 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 382 def revrangebyscoreraw(sscore, escore, opts = {}) echo :revrangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug opts[:with_scores] = true if opts[:withscores] dbclient.zrevrangebyscore(dbkey, sscore, escore, **opts) end |
#revrangeraw(sidx, eidx, opts = {}) ⇒ Object
359 360 361 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 359 def revrangeraw(sidx, eidx, opts = {}) dbclient.zrevrange(dbkey, sidx, eidx, **opts) end |
#revrank(v) ⇒ Object
rank of member +v+ when ordered highest to lowest (starts at 0)
222 223 224 225 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 222 def revrank(v) ret = dbclient.zrevrank dbkey, serialize_value(v) ret&.to_i end |
#scan(cursor = 0, match: nil, count: nil) ⇒ Array
Iterates over members using cursor-based scanning.
725 726 727 728 729 730 731 732 733 734 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 725 def scan(cursor = 0, match: nil, count: nil) opts = {} opts[:match] = match if match opts[:count] = count if count new_cursor, result = dbclient.zscan(dbkey, cursor, **opts) members = result.map { |member, score| [deserialize_value(member), score.to_f] } [new_cursor.to_i, members] end |
#score(val) ⇒ Object Also known as: []
203 204 205 206 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 203 def score(val) ret = dbclient.zscore dbkey, serialize_value(val) ret&.to_f end |
#score_count(min, max) ⇒ Integer Also known as: zcount
Counts members within a score range.
553 554 555 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 553 def score_count(min, max) dbclient.zcount(dbkey, min, max) end |
#selectraw ⇒ Object
338 339 340 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 338 def selectraw(&) membersraw.select(&) end |
#union(*other_sets, weights: nil, aggregate: nil, withscores: false) ⇒ Array
Returns the union of this sorted set with other sorted sets.
590 591 592 593 594 595 596 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 590 def union(*other_sets, weights: nil, aggregate: nil, withscores: false) keys = [dbkey] + resolve_set_keys(other_sets) opts = build_set_operation_opts(weights: weights, aggregate: aggregate, withscores: withscores) result = dbclient.zunion(*keys, **opts) process_set_operation_result(result, withscores: withscores) end |
#unionstore(destination, *other_sets, weights: nil, aggregate: nil) ⇒ Integer
Stores the union of sorted sets into a destination key.
744 745 746 747 748 749 750 751 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 744 def unionstore(destination, *other_sets, weights: nil, aggregate: nil) keys = [dbkey] + resolve_set_keys(other_sets) opts = build_set_operation_opts(weights: weights, aggregate: aggregate) result = dbclient.zunionstore(destination, keys, **opts) update_expiration result end |
#update(hsh = {}) ⇒ Integer Also known as: merge!
Unlike single-value #add, scores are required: this bulk path does not default a missing score to Familia.now. A non-Numeric score (e.g. nil) raises ArgumentError rather than surfacing a low-level client error.
Like #add, this executes immediately (not deferred) and cascades expiration. Empty input is a no-op returning 0.
Bulk-adds or updates multiple members in a single ZADD.
Mirrors HashKey#update/merge! -- the established Familia pattern for bulk-setting keyed collections. A sorted set is member => score, the same pair shape as HashKey's field => value, so it takes a Hash rather than the variadic splat used by the value-only UnsortedSet/ListKey.
Issues exactly one ZADD instead of one round-trip per member, which is what makes populating a large sorted set fast.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/familia/data_type/types/sorted_set.rb', line 183 def update(hsh = {}) warn_if_dirty! raise ArgumentError, 'Argument to bulk add must be a hash' unless hsh.is_a?(Hash) return 0 if hsh.empty? pairs = hsh.map do |member, score| unless score.is_a?(Numeric) raise ArgumentError, "SortedSet#update score for #{member.inspect} must be Numeric, got #{score.class}" end [score, serialize_value(member)] end # Trim is mandatory here: a bulk populate can blow past the cap in a # single ZADD, not just by one member. ret = capped_zadd_write { |conn| conn.zadd(dbkey, pairs) } update_expiration ret end |