Class: ActiveRecordSpannerAdapter::Transaction

Inherits:
Object
  • Object
show all
Defined in:
lib/activerecord_spanner_adapter/transaction.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, isolation) ⇒ Transaction

Returns a new instance of Transaction.



11
12
13
14
15
16
17
18
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 11

def initialize connection, isolation
  @connection = connection
  @isolation = isolation
  @committable = ![:read_only, :pdml].include?(isolation) && !isolation.is_a?(Hash)
  @state = :INITIALIZED
  @sequence_number = 0
  @mutations = []
end

Instance Attribute Details

#stateObject (readonly)

Returns the value of attribute state.



9
10
11
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 9

def state
  @state
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


20
21
22
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 20

def active?
  @state == :STARTED
end

#beginObject

Begins the transaction.

Read-only and PDML transactions are started by executing a BeginTransaction RPC. Read/write transactions are not really started by this method, and instead a transaction selector is prepared that will be included with the first statement on the transaction.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 39

def begin
  raise "Nested transactions are not allowed" if @state != :INITIALIZED
  begin
    case @isolation
    when Hash
      if @isolation[:timestamp]
        @grpc_transaction = @connection.session.create_snapshot timestamp: @isolation[:timestamp]
      elsif @isolation[:staleness]
        @grpc_transaction = @connection.session.create_snapshot staleness: @isolation[:staleness]
      elsif @isolation[:strong]
        @grpc_transaction = @connection.session.create_snapshot strong: true
      else
        raise "Invalid snapshot argument: #{@isolation}"
      end
    when :read_only
      @grpc_transaction = @connection.session.create_snapshot strong: true
    when :pdml
      @grpc_transaction = @connection.session.create_pdml
    else
      @begin_transaction_selector = Google::Cloud::Spanner::V1::TransactionSelector.new \
        begin: Google::Cloud::Spanner::V1::TransactionOptions.new(
          read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new
        )

    end
    @state = :STARTED
  rescue Google::Cloud::NotFoundError => e
    if @connection.session_not_found? e
      @connection.reset!
      retry
    end
    @state = :FAILED
    raise
  rescue StandardError
    @state = :FAILED
    raise
  end
end

#buffer(mutation) ⇒ Object



29
30
31
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 29

def buffer mutation
  @mutations << mutation
end

#commitObject



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 88

def commit
  raise "This transaction is not active" unless active?

  begin
    # Start a transaction with an explicit BeginTransaction RPC if the transaction only contains mutations.
    force_begin_read_write if @committable && !@mutations.empty? && !@grpc_transaction

    @connection.session.commit_transaction @grpc_transaction, @mutations if @committable && @grpc_transaction
    @state = :COMMITTED
  rescue Google::Cloud::NotFoundError => e
    if @connection.session_not_found? e
      shoot_and_forget_rollback
      @connection.reset!
      @connection.raise_aborted_err
    end
    @state = :FAILED
    raise
  rescue StandardError
    @state = :FAILED
    raise
  end
end

#force_begin_read_writeObject

Forces a BeginTransaction RPC for a read/write transaction. This is used by a connection if the first statement of a transaction failed.



80
81
82
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 80

def force_begin_read_write
  @grpc_transaction = @connection.session.create_transaction
end

#grpc_transaction=(grpc) ⇒ Object

Sets the underlying gRPC transaction to use for this Transaction. This is used for queries/DML statements that inlined the BeginTransaction option and returned a transaction in the metadata.



137
138
139
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 137

def grpc_transaction= grpc
  @grpc_transaction = Google::Cloud::Spanner::Transaction.from_grpc grpc, @connection.session
end

#isolationObject



24
25
26
27
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 24

def isolation
  return nil unless active?
  @isolation
end

#mark_abortedObject



130
131
132
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 130

def mark_aborted
  @state = :ABORTED
end

#next_sequence_numberObject



84
85
86
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 84

def next_sequence_number
  @sequence_number += 1 if @committable
end

#rollbackObject



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 111

def rollback
  # Allow rollback after abort and/or a failed commit.
  raise "This transaction is not active" unless active? || @state == :FAILED || @state == :ABORTED
  if active? && @grpc_transaction
    # We do a shoot-and-forget rollback here, as the error that caused the transaction to be rolled back could
    # also have invalidated the transaction (e.g. `Session not found`). If the rollback fails for any other
    # reason, we also do not need to retry it or propagate the error to the application, as the transaction will
    # automatically be aborted by Cloud Spanner after 10 seconds anyways.
    shoot_and_forget_rollback
  end
  @state = :ROLLED_BACK
end

#shoot_and_forget_rollbackObject



124
125
126
127
128
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 124

def shoot_and_forget_rollback
  @connection.session.rollback @grpc_transaction.transaction_id if @committable
rescue StandardError # rubocop:disable Lint/HandleExceptions
  # Ignored
end

#transaction_selectorObject



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/activerecord_spanner_adapter/transaction.rb', line 141

def transaction_selector
  return unless active?

  # Use the transaction that has been started by a BeginTransaction RPC or returned by a
  # statement, if present.
  return Google::Cloud::Spanner::V1::TransactionSelector.new id: @grpc_transaction.transaction_id \
      if @grpc_transaction

  # Return a transaction selector that will instruct the statement to also start a transaction
  # and return its id as a side effect.
  @begin_transaction_selector
end