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 the server coerces to boolean by name, but which arrive as raw 0/1 inside an EXEC array (glide-core keys coercion on the executed command - EXEC - not the queued ones). Listed here so #reconvert_queued_replies can restore it.

This is glide-core's boolean table (value_conversion.rs::expected_type_for_cmd) minus commands whose Ruby method passes its own conversion block - those are handled by the if block branch below. Only block-less RequestTypes belong here.

SETNX is absent by design: glide-core doesn't treat it as boolean (name-keyed coercion can't distinguish it from a raw customCommand(["SETNX", ...])), so setnx passes &Utils::Boolify itself (string_commands.rb) - covered by if block.

[
  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