promiscuous 0.100.5 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/promiscuous.rb +5 -1
  3. data/lib/promiscuous/config.rb +6 -5
  4. data/lib/promiscuous/dsl.rb +0 -4
  5. data/lib/promiscuous/loader.rb +0 -5
  6. data/lib/promiscuous/mongoid.rb +15 -5
  7. data/lib/promiscuous/publisher.rb +1 -1
  8. data/lib/promiscuous/publisher/model/active_record.rb +6 -1
  9. data/lib/promiscuous/publisher/model/base.rb +8 -11
  10. data/lib/promiscuous/publisher/model/mock.rb +2 -2
  11. data/lib/promiscuous/publisher/model/mongoid.rb +3 -4
  12. data/lib/promiscuous/publisher/operation/active_record.rb +13 -69
  13. data/lib/promiscuous/publisher/operation/atomic.rb +15 -158
  14. data/lib/promiscuous/publisher/operation/base.rb +13 -381
  15. data/lib/promiscuous/publisher/operation/ephemeral.rb +12 -8
  16. data/lib/promiscuous/publisher/operation/mongoid.rb +22 -92
  17. data/lib/promiscuous/publisher/operation/non_persistent.rb +0 -9
  18. data/lib/promiscuous/publisher/operation/proxy_for_query.rb +8 -6
  19. data/lib/promiscuous/publisher/operation/transaction.rb +4 -56
  20. data/lib/promiscuous/publisher/transport.rb +14 -0
  21. data/lib/promiscuous/publisher/transport/batch.rb +138 -0
  22. data/lib/promiscuous/publisher/transport/persistence.rb +14 -0
  23. data/lib/promiscuous/publisher/transport/persistence/active_record.rb +33 -0
  24. data/lib/promiscuous/publisher/transport/persistence/mongoid.rb +22 -0
  25. data/lib/promiscuous/publisher/transport/worker.rb +36 -0
  26. data/lib/promiscuous/publisher/worker.rb +3 -12
  27. data/lib/promiscuous/redis.rb +5 -0
  28. data/lib/promiscuous/subscriber/message.rb +1 -29
  29. data/lib/promiscuous/subscriber/model/base.rb +3 -2
  30. data/lib/promiscuous/subscriber/model/mongoid.rb +16 -1
  31. data/lib/promiscuous/subscriber/model/observer.rb +0 -1
  32. data/lib/promiscuous/subscriber/operation.rb +9 -3
  33. data/lib/promiscuous/subscriber/unit_of_work.rb +7 -7
  34. data/lib/promiscuous/subscriber/worker/eventual_destroyer.rb +1 -1
  35. data/lib/promiscuous/version.rb +1 -1
  36. metadata +39 -35
  37. data/lib/promiscuous/dependency.rb +0 -78
  38. data/lib/promiscuous/error/dependency.rb +0 -116
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36eb13f57a962d958bcd78894b1a1991e1ee5a63
4
- data.tar.gz: ed7e75b9a906ca60da6d9e28d48c93ded541308c
3
+ metadata.gz: 9049ba8a421c1866cee586689ed5927acebc1663
4
+ data.tar.gz: 7dcb69b1cc9e47e3878fd6cd65c55481183069df
5
5
  SHA512:
6
- metadata.gz: 592f65e706d0ef1cfbf3a4bbc36a7494847003efa7e1fae07ec6bf2d1bd33e6db454e0e982843b77e1e660397187f25c25f4e4b4e5b03e81c5e78394e6db78bf
7
- data.tar.gz: 1842b7b3e6627868ee1312f123a1007342b8ac241a05368f77ab2c9bf67dcc4373daaef2b5f7ec1f9ea0ef56dabcf78ff4bff42415c1dfb25f8672b03b11d35e
6
+ metadata.gz: f0f7e2f082d0684b9c0e8ce7bb6b58a38eb1ec40d2108ab839c295384707e60e704a681ac015702c7ad26ffe6ff40879d38877c86e15512a9b872dc7aae5646e
7
+ data.tar.gz: a3f0fe71f03c4acca822c6a71164b4b88fd1381b0cd83309ff94a703e6cf60aece9e022e8e6b58083296359a4d81c26ba7da7a5a69eda92ca4fa0fb99330d364
data/lib/promiscuous.rb CHANGED
@@ -4,8 +4,12 @@ require 'multi_json'
4
4
 
5
5
  module Promiscuous
6
6
  def self.require_for(gem, file)
7
+ only_for(gem) { require file }
8
+ end
9
+
10
+ def self.only_for(gem, &block)
7
11
  require gem
8
- require file
12
+ block.call
9
13
  rescue LoadError
10
14
  end
11
15
 
@@ -4,8 +4,8 @@ module Promiscuous::Config
4
4
  :subscriber_exchanges, :queue_name, :queue_options, :redis_url,
5
5
  :redis_urls, :redis_stats_url, :stats_interval,
6
6
  :socket_timeout, :heartbeat, :hash_size,
7
- :prefetch, :recovery_timeout, :logger, :subscriber_threads,
8
- :version_field, :error_notifier, :recovery_on_boot,
7
+ :prefetch, :recovery_timeout, :recovery_interval, :logger, :subscriber_threads,
8
+ :version_field, :error_notifier, :transport_collection,
9
9
  :on_stats, :max_retries, :generation, :destroy_timeout, :destroy_check_interval
10
10
 
11
11
  def self.backend=(value)
@@ -52,15 +52,16 @@ module Promiscuous::Config
52
52
  self.heartbeat ||= 60
53
53
  self.hash_size ||= 2**20 # one million keys ~ 200Mb.
54
54
  self.prefetch ||= 1000
55
- self.recovery_timeout ||= 10
55
+ self.recovery_timeout ||= 10.seconds
56
+ self.recovery_interval ||= 5.seconds
56
57
  self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
57
58
  self.subscriber_threads ||= 10
58
59
  self.error_notifier ||= proc {}
59
60
  self.version_field ||= '_v'
60
- self.recovery_on_boot = true if self.recovery_on_boot.nil?
61
+ self.transport_collection ||= '_promiscuous'
61
62
  self.on_stats ||= proc { |rate, latency| }
62
63
  self.max_retries ||= defined?(Rails) ? Rails.env.production? ? 10 : 0 : 10
63
- self.generation ||= 1
64
+ self.generation ||= 0
64
65
  self.destroy_timeout ||= 1.hour
65
66
  self.destroy_check_interval ||= 10.minutes
66
67
  end
@@ -27,10 +27,6 @@ module Promiscuous::DSL
27
27
  @model_class.__send__(@mode, *fields, @options.merge(options))
28
28
  end
29
29
 
30
- def track_dependencies_of(field)
31
- @model_class.track_dependencies_of(field)
32
- end
33
-
34
30
  alias attribute attributes
35
31
  end
36
32
  end
@@ -6,11 +6,6 @@ module Promiscuous::Loader
6
6
  file = defined?(Rails) ? Rails.root.join(file_name) : File.join('.', file_name)
7
7
  load file if File.exists?(file)
8
8
  end
9
-
10
- # A one shot recovery on boot
11
- if Promiscuous::Config.recovery_on_boot
12
- Promiscuous::Publisher::Worker.new.try_recover
13
- end
14
9
  end
15
10
 
16
11
  def self.cleanup
@@ -1,7 +1,17 @@
1
- class Moped::BSON::ObjectId
2
- # No {"$oid": "123"}, it's horrible.
3
- # TODO Document this shit.
4
- def to_json(*args)
5
- "\"#{to_s}\""
1
+ if defined? Moped::BSON
2
+ class Moped::BSON::ObjectId
3
+ # No {"$oid": "123"}, it's horrible.
4
+ # TODO Document this shit.
5
+ def to_json(*args)
6
+ "\"#{to_s}\""
7
+ end
8
+ end
9
+ else
10
+ module BSON
11
+ class ObjectId
12
+ def as_json(options = {})
13
+ to_s
14
+ end
15
+ end
6
16
  end
7
17
  end
@@ -1,6 +1,6 @@
1
1
  module Promiscuous::Publisher
2
2
  extend Promiscuous::Autoload
3
- autoload :Model, :Operation, :MockGenerator, :Context, :Worker, :Bootstrap
3
+ autoload :Model, :Operation, :MockGenerator, :Context, :Worker, :Bootstrap, :Transport
4
4
 
5
5
  extend ActiveSupport::Concern
6
6
 
@@ -11,7 +11,12 @@ module Promiscuous::Publisher::Model::ActiveRecord
11
11
 
12
12
  def belongs_to(*args, &block)
13
13
  super.tap do |association|
14
- publish(association.foreign_key) if self.in_publish_block?
14
+ fk = if association.is_a?(Hash)
15
+ association.values.first.foreign_key # ActiveRecord 4x
16
+ else
17
+ association.foreign_key # ActiveRecord 3x
18
+ end
19
+ publish(fk) if self.in_publish_block?
15
20
  end
16
21
  end
17
22
  end
@@ -36,14 +36,17 @@ module Promiscuous::Publisher::Model::Base
36
36
  value
37
37
  end
38
38
 
39
- def get_dependency
40
- @collection ||= @instance.class.promiscuous_collection_name
41
- Promiscuous::Dependency.new(@collection, :id, id)
42
- end
43
-
44
39
  def id
45
40
  @instance.id
46
41
  end
42
+
43
+ def sync(options={}, &block)
44
+ raise "Model cannot be dirty (have changes) when syncing" if @instance.changed?
45
+
46
+ # We can use the ephemeral because both are mongoid and ephemerals are atomic operations.
47
+ Promiscuous::Publisher::Operation::Ephemeral.new(:instance => @instance, :operation => :update).execute
48
+ end
49
+
47
50
  end
48
51
 
49
52
  class PromiscuousMethods
@@ -55,12 +58,6 @@ module Promiscuous::Publisher::Model::Base
55
58
  @promiscuous ||= self.class.const_get(:PromiscuousMethods).new(self)
56
59
  end
57
60
 
58
- def valid?(*args)
59
- # Validations are not dependencies
60
- # TODO we should yell if the user is trying to write
61
- without_promiscuous { super }
62
- end
63
-
64
61
  module ClassMethods
65
62
  # all methods are virtual
66
63
 
@@ -28,9 +28,9 @@ module Promiscuous::Publisher::Model::Mock
28
28
  op = Promiscuous::Publisher::Operation::Ephemeral.new(:instance => self, :operation => operation)
29
29
  # TODO FIX the mocks to populate app name, also we need to hook before the
30
30
  # json dump.
31
- payload = op.generate_payload
31
+ batch = op.create_transport_batch([op])
32
32
 
33
- Promiscuous::Subscriber::Message.new(payload).process
33
+ Promiscuous::Subscriber::Message.new(batch.payload).process
34
34
  end
35
35
 
36
36
  module ClassMethods
@@ -15,6 +15,8 @@ module Promiscuous::Publisher::Model::Mongoid
15
15
  raise "Please include Promiscuous::Publisher in the root class of #{self}"
16
16
  end
17
17
 
18
+ field Promiscuous::Config.version_field
19
+
18
20
  Promiscuous::Publisher::Model::Mongoid.collection_mapping[self.collection.name] = self
19
21
  end
20
22
 
@@ -23,9 +25,7 @@ module Promiscuous::Publisher::Model::Mongoid
23
25
 
24
26
  def sync(options={}, &block)
25
27
  raise "Use promiscuous.sync on the parent instance" if @instance.embedded?
26
-
27
- # We can use the ephemeral because both are mongoid and ephemerals are atomic operations.
28
- Promiscuous::Publisher::Operation::Ephemeral.new(:instance => @instance, :operation => :update).execute
28
+ super
29
29
  end
30
30
 
31
31
  def attribute(attr)
@@ -41,7 +41,6 @@ module Promiscuous::Publisher::Model::Mongoid
41
41
  end
42
42
 
43
43
  module ClassMethods
44
- # TODO DRY this up with the publisher side
45
44
  def self.publish_on(method, options={})
46
45
  define_method(method) do |name, *args, &block|
47
46
  super(name, *args, &block)
@@ -1,46 +1,4 @@
1
1
  class ActiveRecord::Base
2
- module PostgresSQL2PCExtensions
3
- extend ActiveSupport::Concern
4
-
5
- def prepare_db_transaction
6
- execute("PREPARE TRANSACTION '#{quote_string(@current_transaction_id)}'")
7
- end
8
-
9
- def commit_prepared_db_transaction(xid)
10
- # We might always be racing with another instance, these sort of errors
11
- # are spurious.
12
- execute("COMMIT PREPARED '#{quote_string(xid)}'")
13
- rescue Exception => e
14
- raise unless e.message =~ /^PG::UndefinedObject/
15
- end
16
-
17
- def rollback_prepared_db_transaction(xid, options={})
18
- execute("ROLLBACK PREPARED '#{quote_string(xid)}'")
19
- rescue Exception => e
20
- raise unless e.message =~ /^PG::UndefinedObject/
21
- end
22
-
23
- included do
24
- # We want to make sure that we never block the database by having
25
- # uncommitted transactions.
26
- Promiscuous::Publisher::Operation::Base.register_recovery_mechanism do
27
- connection = ActiveRecord::Base.connection
28
- db_name = connection.current_database
29
-
30
- # We wait twice the time of expiration, to allow a better recovery scenario.
31
- expire_duration = 2 * Promiscuous::Publisher::Operation::Base.lock_options[:expire]
32
-
33
- q = "SELECT gid FROM pg_prepared_xacts " +
34
- "WHERE database = '#{db_name}' " +
35
- "AND prepared < current_timestamp + #{expire_duration} * interval '1 second'"
36
-
37
- connection.exec_query(q, "Promiscuous Recovery").each do |tx|
38
- ActiveRecord::Base::PromiscuousTransaction.recover_transaction(connection, tx['gid'])
39
- end
40
- end
41
- end
42
- end
43
-
44
2
  class << self
45
3
  alias_method :connection_without_promiscuous, :connection
46
4
 
@@ -50,10 +8,6 @@ class ActiveRecord::Base
50
8
  connection.class.class_eval do
51
9
  attr_accessor :current_transaction_id
52
10
 
53
- if self.name == "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
54
- include ActiveRecord::Base::PostgresSQL2PCExtensions
55
- end
56
-
57
11
  def promiscuous_hook; end
58
12
 
59
13
  alias_method :begin_db_transaction_without_promiscuous, :begin_db_transaction
@@ -165,7 +119,7 @@ class ActiveRecord::Base
165
119
  query.non_instrumented { db_operation.call }
166
120
  query.instrumented do
167
121
  db_operation_and_select.tap do
168
- transaction_context.add_write_operation(self) if !@instances.empty?
122
+ transaction_context.add_write_operation(self) if @instances.present?
169
123
  end
170
124
  end
171
125
  end
@@ -178,7 +132,8 @@ class ActiveRecord::Base
178
132
  def operation_payloads
179
133
  @instances.map do |instance|
180
134
  instance.promiscuous.payload(:with_attributes => self.operation.in?([:create, :update])).tap do |payload|
181
- payload[:operation] = self.operation
135
+ payload[:operation] = self.operation
136
+ payload[:version] = instance.__send__(Promiscuous::Config.version_field)
182
137
  end
183
138
  end
184
139
  end
@@ -196,9 +151,13 @@ class ActiveRecord::Base
196
151
 
197
152
  def db_operation_and_select
198
153
  # XXX This is only supported by Postgres and should be in the postgres driver
199
-
200
- @connection.exec_insert("#{@connection.to_sql(@arel, @binds)} RETURNING *", @operation_name, @binds).tap do |result|
201
- @instances = result.map { |row| model.instantiate(row) }
154
+ @connection.transaction do
155
+ @connection.exec_insert("#{@connection.to_sql(@arel, @binds)} RETURNING *", @operation_name, @binds).tap do |result|
156
+ @instances = result.map do |row|
157
+ instance = model.instantiate(row)
158
+ instance
159
+ end
160
+ end
202
161
  end
203
162
  # TODO Use correct primary key
204
163
  @instances.first.id
@@ -234,6 +193,8 @@ class ActiveRecord::Base
234
193
 
235
194
  def db_operation_and_select
236
195
  # TODO this should be in the postgres driver (to also leverage the cache)
196
+ @arel.ast.values << Arel::Nodes::SqlLiteral.new("\"#{Promiscuous::Config.version_field}\" = COALESCE(\"#{Promiscuous::Config.version_field}\", 0) + 1")
197
+
237
198
  @connection.exec_query("#{@connection.to_sql(@arel, @binds)} RETURNING *", @operation_name, @binds).tap do |result|
238
199
  @instances = result.map { |row| model.instantiate(row) }
239
200
  end.rows.size
@@ -254,7 +215,6 @@ class ActiveRecord::Base
254
215
  end
255
216
 
256
217
  def db_operation_and_select
257
- # TODO We only need the tracked attributes really (most likely, we just need ID)
258
218
  # XXX This is only supported by Postgres.
259
219
  @connection.exec_query("#{@connection.to_sql(@arel, @binds)} RETURNING *", @operation_name, @binds).tap do |result|
260
220
  @instances = result.map { |row| model.instantiate(row) }
@@ -272,24 +232,8 @@ class ActiveRecord::Base
272
232
  end
273
233
 
274
234
  def execute_instrumented(query)
275
- query.prepare { @connection.prepare_db_transaction }
276
- query.instrumented { @connection.commit_prepared_db_transaction(@transaction_id) }
235
+ query.instrumented { @connection.commit_db_transaction_without_promiscuous }
277
236
  super
278
237
  end
279
-
280
- def self.recover_transaction(connection, transaction_id)
281
- op = new(:connection => connection, :transaction_id => transaction_id)
282
- # Getting the lock will trigger the real recovery mechanism
283
- if op.acquire_op_lock
284
- op.release_op_lock
285
- end
286
-
287
- # In the event where the recovery payload wasn't found, we must roll back.
288
- # If the operation was recoverable, but couldn't be recovered, an
289
- # exception would be thrown, so we won't roll it back by mistake.
290
- # If the operation was recovered, the roll back will result in an error,
291
- # which is fine.
292
- connection.rollback_prepared_db_transaction(transaction_id)
293
- end
294
238
  end
295
239
  end
@@ -2,176 +2,33 @@ class Promiscuous::Publisher::Operation::Atomic < Promiscuous::Publisher::Operat
2
2
  # XXX instance can be a selector representation.
3
3
  attr_accessor :instance
4
4
 
5
- def initialize(options={})
6
- super
7
- @instance = options[:instance]
8
- end
9
-
10
- def acquire_op_lock
11
- unless dependency_for_op_lock
12
- return unless reload_instance
13
- end
14
-
15
- loop do
16
- instance_dep = dependency_for_op_lock
17
-
18
- super
19
-
20
- return if operation == :create
21
-
22
- # We need to make sure that the lock we acquired matches our selector.
23
- # There is a bit of room for optimization if we know that we don't have
24
- # any tracked attributes on the model and our selector is already an id.
25
- return unless reload_instance
26
-
27
- # If reload_instance changed the current instance because the selector,
28
- # we need to unlock the old instance, lock this new instance, and
29
- # retry.
30
- return if instance_dep == dependency_for_op_lock
31
-
32
- # XXX What should we do if we are going in a live lock?
33
- # Sleep with some jitter?
34
- release_op_lock
35
- end
36
- end
37
-
38
- def do_database_query(query)
39
- case operation
40
- when :create
41
- # We don't stash the version in the document as we can't have races
42
- # on the same document.
43
- when :update
44
- increment_version_in_document
45
- # We are now in the possession of an instance that matches the original
46
- # selector. We need to make sure the db query will operate on it,
47
- # instead of the original selector.
48
- use_id_selector(:use_atomic_version_selector => true)
49
- # We need to use an atomic versioned selector to make sure that
50
- # if we lose the lock for a long period of time, we don't mess up
51
- # the record. Perhaps the operation has been recovered a while ago.
52
- when :destroy
53
- use_id_selector
54
- end
55
-
56
- # The driver is responsible to set instance to the appropriate value.
57
- query.call_and_remember_result(:instrumented)
58
-
59
- if query.failed?
60
- # If we get an network failure, we should retry later.
61
- return if recoverable_failure?(query.exception)
62
- @instance = nil
63
- end
64
- end
65
-
66
- def yell_about_missing_instance
67
- err = "Cannot find document. Database had a dataloss?. Proceeding anyways. #{@recovery_data}"
68
- e = Promiscuous::Error::Recovery.new(err)
69
- Promiscuous.warn "[recovery] #{e}"
70
- Promiscuous::Config.error_notifier.call(e)
5
+ def instances
6
+ [@instance].compact
71
7
  end
72
8
 
73
9
  def execute_instrumented(query)
74
- if recovering?
75
- # The DB died or something. We cannot find our instance any more :(
76
- # this is a problem, but we need to publish.
77
- yell_about_missing_instance if @instance.nil?
10
+ if operation == :destroy
11
+ fetch_instance
78
12
  else
79
- acquire_op_lock
80
-
81
- if @instance.nil?
82
- # The selector missed the instance, bailing out.
83
- query.call_and_remember_result(:non_instrumented)
84
- return
85
- end
13
+ increment_version_in_document
86
14
  end
87
15
 
88
- # All the versions are updated and a marked as pending for publish in Redis
89
- # atomically in case we die before we could write the versions in the
90
- # database. Once incremented, concurrent queries that are reading our
91
- # instance will be serialized after our write, even through it may read our
92
- # old instance. This is a race that we tolerate.
93
- # XXX We also stash the document for create operations, so the recovery can
94
- # redo the create to avoid races when instances are getting partitioned.
95
- increment_dependencies
96
-
97
- # From this point, if we die, the one expiring our write locks must finish
98
- # the publish, either by sending a dummy, or by sending the real instance.
99
- # We could have die before or after the database query.
100
-
101
- # We save the versions in the database, as it is our source of truth.
102
- # This allow a reconstruction of redis in the face of failures.
103
- # We would also need to send a special message to the subscribers to reset
104
- # their read counters to the last write version since we would not be able
105
- # to restore the read counters (and we don't want to store them because
106
- # this would dramatically augment our footprint on the db).
107
- #
108
- # If we are doing a destroy operation, and redis dies right after, and
109
- # we happen to lost contact with rabbitmq, recovery is going to be complex:
110
- # we would need to do a diff from the dummy subscriber to see what
111
- # documents are missing on our side to be able to resend the destroy
112
- # message.
113
-
114
- do_database_query(query) unless @instance.nil?
115
- # We take a timestamp right after the write is performed because latency
116
- # measurements are performed on the subscriber.
117
- record_timestamp
118
-
119
- # This make sure that if the db operation failed because of a network issue
120
- # and we got recovered, we don't send anything as we could send a different
121
- # message than the recovery mechanism.
122
- ensure_op_still_locked
123
-
124
- generate_payload
125
-
126
- # As soon as we unlock the locks, the rescuer will not be able to assume
127
- # that the database instance is still pristine, and so we need to stash the
128
- # payload in redis. If redis dies, we don't care because it can be
129
- # reconstructed. Subscribers can see "compressed" updates.
130
- publish_payload_in_redis
131
-
132
- # TODO Performance: merge these 3 redis operations to speed things up.
133
- release_op_lock
134
-
135
- # If we die from this point on, a recovery worker can republish our payload
136
- # since we queued it in Redis.
137
-
138
- # We don't care if we lost the lock and got recovered, subscribers are
139
- # immune to duplicate messages.
140
- publish_payload_in_rabbitmq_async
141
- end
16
+ transport_batch = create_transport_batch([self])
17
+ transport_batch.prepare
142
18
 
143
- def operation_payloads
144
- @instance.nil? ? [] : [payload_for(@instance)]
145
- end
146
-
147
- def query_dependencies
148
- dependencies_for(@instance)
149
- end
19
+ query.call_and_remember_result(:instrumented)
150
20
 
151
- def fetch_instance
152
- # This method is overridden to use the original query selector.
153
- # Should return nil if the instance is not found.
154
- @instance.reload if @instance.respond_to?(:reload)
155
- @instance
156
- end
21
+ unless operation == :destroy
22
+ # Refresh the operation on the batch to include the updated instance
23
+ # reflecting the executed operation so that we publish the correct data.
24
+ transport_batch.clear
25
+ transport_batch.add query.operation.operation, query.operation.instances
26
+ end
157
27
 
158
- def reload_instance
159
- @instance = fetch_instance
28
+ transport_batch.publish
160
29
  end
161
30
 
162
31
  def increment_version_in_document
163
- # Overridden to increment version field in the query
164
- end
165
-
166
- def use_id_selector(options={})
167
- # Overridden to use the {:id => @instance.id} selector.
168
- # if the option use_atomic_version_selector is passed, the driver must add
169
- # the version_field selector.
170
- end
171
-
172
- def recoverable_failure?(exception)
173
- # Overridden to tell if the db exception is spurious, like a network
174
- # failure.
175
32
  raise
176
33
  end
177
34
  end