Module: Valkey::Commands::TransactionCommands

Included in:
Valkey::Commands
Defined in:
lib/valkey/commands/transaction_commands.rb

Overview

This module contains commands related to transactions.

Constant Summary collapse

BOOLEAN_REQUEST_TYPES =

Commands whose reply is boolean when run standalone (via native return-type coercion server-side), but arrives as a raw 0/1 integer inside an EXEC array, since that coercion is keyed by the single command actually being run - which, for a queued command, is EXEC itself, not the original command.

This list mirrors glide-core's own Boolean-coercion table in value_conversion.rs::expected_type_for_cmd (HEXISTS, HSETNX, EXPIRE, EXPIREAT, PEXPIRE, PEXPIREAT, SISMEMBER, PERSIST, SMOVE, PFADD, RENAMENX, MOVE, COPY, MSETNX, XGROUP DESTROY, XGROUP CREATECONSUMER) MINUS the commands whose Ruby method already passes its own explicit conversion block (hexists/hsetnx use &Utils::Boolify; xgroup_destroy/xgroup_createconsumer use a custom bool->int block). Those are already handled correctly by the if block branch below, before this list is even consulted - listing them here too would be redundant, not wrong. Every RequestType below calls send_command with NO block at all, so this static list is the only place their boolean-ness is recorded.

NOTE: SETNX is deliberately NOT in glide-core's coercion table (and so isn't here as a "no block" entry either) - it's redis-rb-style boolean-ness only, not something the server/glide-core treats as boolean, since coercion there is keyed by command name alone and can't distinguish this dedicated RequestType from a raw customCommand(["SETNX", ...]) call, which other bindings' existing contracts expect to keep returning a plain integer. setnx's own Ruby method passes &Utils::Boolify directly instead - see string_commands.rb - so it's already covered by the if block branch, same as hexists/hsetnx.

[
  RequestType::EXPIRE, RequestType::EXPIRE_AT, RequestType::PEXPIRE, RequestType::PEXPIRE_AT,
  RequestType::PERSIST, RequestType::SISMEMBER, RequestType::S_MOVE, RequestType::PFADD,
  RequestType::RENAME_NX, RequestType::MOVE, RequestType::COPY, RequestType::MSET_NX
].freeze

Instance Method Summary collapse

Instance Method Details

#discardString

Discard all commands issued after MULTI.

Returns:

  • (String)

    "OK"

See Also:



167
168
169
170
171
172
173
174
175
176
# File 'lib/valkey/commands/transaction_commands.rb', line 167

def discard
  send_command(RequestType::DISCARD)
rescue CommandError
  # DISCARD without MULTI is treated similarly to EXEC without MULTI:
  # ignore the server error and return nil.
  nil
ensure
  @in_multi = false
  @queued_commands = []
end

#execnil, Array<...>

Execute all commands issued after MULTI.

Only call this method when #multi was called without a block.

Returns:

  • (nil, Array<...>)
    • when commands were not executed, nil
    • when commands were executed, an array with their replies

See Also:



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/valkey/commands/transaction_commands.rb', line 132

def exec
  if @in_multi
    queued_commands = @queued_commands
    begin
      begin
        result = send_command(RequestType::EXEC)
        # If EXEC returns an error object (from array), it's already handled
        result.is_a?(Array) ? reconvert_queued_replies(result, queued_commands) : result
      rescue CommandError => e
        # If EXEC itself raises an error (like when transaction is aborted),
        # return an array with the error to match expected behavior in tests
        [e]
      end
    ensure
      @in_multi = false
      @queued_commands = []
    end
  else
    # When EXEC is called without a preceding MULTI the server returns an
    # error. The lint tests allow clients to either raise or return nil;
    # we normalize this to simply return nil.
    begin
      send_command(RequestType::EXEC)
    rescue CommandError
      nil
    end
  end
end

#multi {|multi| ... } ⇒ Array<...>

Mark the start of a transaction block.

Examples:

With a block

valkey.multi do |multi|
  multi.set("key", "value")
  multi.incr("counter")
end # => ["OK", 6]

Yields:

  • (multi)

    the commands that are called inside this block are cached locally (no server round-trip per command) and sent to the server as a single atomic batch once the block returns - GLIDE wraps them in a real MULTI/EXEC transaction internally. If the block raises, nothing has been sent to the server yet, so the exception simply propagates - there is no transaction to discard. Each in-block command call returns a Future immediately; capture it and call #value on it once the block has returned to read that command's own reply:

    @example Capturing a per-command reply future = nil valkey.multi do |multi| future = multi.incr("counter") multi.expire("counter", 60) end future.value # => 6

Yield Parameters:

Returns:

  • (Array<...>)
    • an array with replies

See Also:



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/valkey/commands/transaction_commands.rb', line 41

def multi
  if block_given?
    pipeline = Pipeline.new

    begin
      yield pipeline

      return [] if pipeline.commands.empty?

      results = send_batch_commands(pipeline.commands, exception: true, is_atomic: true)
      pipeline.resolve_futures!(results)
      results
    rescue StandardError
      pipeline.abort_futures!
      raise
    end
  else
    start_multi
    self
  end
end

#unwatchString

Forget about all watched keys.

Returns:

  • (String)

    "OK"

See Also:



118
119
120
# File 'lib/valkey/commands/transaction_commands.rb', line 118

def unwatch
  send_command(RequestType::UNWATCH)
end

#watch(*keys) ⇒ Object, String

Watch the given keys to determine execution of the MULTI/EXEC block.

Using a block is optional, but is recommended for automatic cleanup.

An #unwatch is automatically issued if an exception is raised within the block that is a subclass of StandardError and is not a ConnectionError.

Examples:

With a block

valkey.watch("key") do
  if valkey.get("key") == "some value"
    valkey.multi do |multi|
      multi.set("key", "other value")
      multi.incr("counter")
    end
  else
    valkey.unwatch
  end
end
  # => ["OK", 6]

Without a block

valkey.watch("key")
  # => "OK"

Parameters:

  • keys (String, Array<String>)

    one or more keys to watch

Returns:

  • (Object)

    if using a block, returns the return value of the block

  • (String)

    if not using a block, returns "OK"

See Also:



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/valkey/commands/transaction_commands.rb', line 94

def watch(*keys)
  keys.flatten!(1)
  res = send_command(RequestType::WATCH, keys)

  if block_given?
    begin
      yield(self)
    rescue ConnectionError
      raise
    rescue StandardError
      unwatch
      raise
    end
  else
    res
  end
end