nobrainer 0.22.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/autoload.rb +1 -1
  3. data/lib/no_brainer/config.rb +54 -14
  4. data/lib/no_brainer/connection.rb +24 -21
  5. data/lib/no_brainer/connection_manager.rb +29 -9
  6. data/lib/no_brainer/criteria/cache.rb +8 -0
  7. data/lib/no_brainer/criteria/changes.rb +16 -0
  8. data/lib/no_brainer/criteria/core.rb +4 -5
  9. data/lib/no_brainer/criteria/eager_load.rb +2 -2
  10. data/lib/no_brainer/criteria/enumerable.rb +3 -1
  11. data/lib/no_brainer/criteria/find.rb +6 -9
  12. data/lib/no_brainer/criteria/first.rb +3 -3
  13. data/lib/no_brainer/criteria/first_or_create.rb +114 -0
  14. data/lib/no_brainer/criteria/join.rb +62 -0
  15. data/lib/no_brainer/criteria/order_by.rb +19 -16
  16. data/lib/no_brainer/criteria/run.rb +26 -0
  17. data/lib/no_brainer/criteria/scope.rb +8 -8
  18. data/lib/no_brainer/criteria/where.rb +130 -107
  19. data/lib/no_brainer/criteria.rb +4 -4
  20. data/lib/no_brainer/document/association/belongs_to.rb +44 -17
  21. data/lib/no_brainer/document/association/core.rb +11 -0
  22. data/lib/no_brainer/document/association/eager_loader.rb +26 -26
  23. data/lib/no_brainer/document/association/has_many.rb +7 -9
  24. data/lib/no_brainer/document/association/has_many_through.rb +1 -2
  25. data/lib/no_brainer/document/association/has_one.rb +5 -1
  26. data/lib/no_brainer/document/association.rb +5 -5
  27. data/lib/no_brainer/document/atomic_ops.rb +40 -7
  28. data/lib/no_brainer/document/attributes.rb +24 -15
  29. data/lib/no_brainer/document/callbacks.rb +1 -1
  30. data/lib/no_brainer/document/core.rb +18 -18
  31. data/lib/no_brainer/document/criteria.rb +9 -5
  32. data/lib/no_brainer/document/dirty.rb +15 -12
  33. data/lib/no_brainer/document/dynamic_attributes.rb +6 -0
  34. data/lib/no_brainer/document/index/index.rb +5 -9
  35. data/lib/no_brainer/document/index/meta_store.rb +1 -10
  36. data/lib/no_brainer/document/index/synchronizer.rb +16 -20
  37. data/lib/no_brainer/document/index.rb +3 -3
  38. data/lib/no_brainer/document/lazy_fetch.rb +3 -3
  39. data/lib/no_brainer/document/missing_attributes.rb +7 -2
  40. data/lib/no_brainer/document/persistance.rb +14 -30
  41. data/lib/no_brainer/document/polymorphic.rb +8 -4
  42. data/lib/no_brainer/document/primary_key/generator.rb +6 -1
  43. data/lib/no_brainer/document/primary_key.rb +19 -4
  44. data/lib/no_brainer/document/serialization.rb +0 -2
  45. data/lib/no_brainer/document/table_config/synchronizer.rb +21 -0
  46. data/lib/no_brainer/document/table_config.rb +118 -0
  47. data/lib/no_brainer/document/timestamps.rb +8 -0
  48. data/lib/no_brainer/document/validation/core.rb +63 -0
  49. data/lib/no_brainer/document/validation/uniqueness.rb +30 -36
  50. data/lib/no_brainer/document/validation.rb +1 -58
  51. data/lib/no_brainer/document.rb +2 -2
  52. data/lib/no_brainer/error.rb +1 -0
  53. data/lib/no_brainer/geo/base.rb +0 -1
  54. data/lib/no_brainer/locale/en.yml +1 -0
  55. data/lib/no_brainer/lock.rb +12 -8
  56. data/lib/no_brainer/profiler/controller_runtime.rb +76 -0
  57. data/lib/no_brainer/{query_runner → profiler}/logger.rb +11 -27
  58. data/lib/no_brainer/profiler.rb +11 -0
  59. data/lib/no_brainer/query_runner/database_on_demand.rb +8 -8
  60. data/lib/no_brainer/query_runner/driver.rb +3 -1
  61. data/lib/no_brainer/query_runner/missing_index.rb +3 -3
  62. data/lib/no_brainer/query_runner/profiler.rb +43 -0
  63. data/lib/no_brainer/query_runner/reconnect.rb +38 -23
  64. data/lib/no_brainer/query_runner/run_options.rb +35 -15
  65. data/lib/no_brainer/query_runner/table_on_demand.rb +18 -11
  66. data/lib/no_brainer/query_runner.rb +3 -3
  67. data/lib/no_brainer/railtie/database.rake +14 -4
  68. data/lib/no_brainer/railtie.rb +5 -12
  69. data/lib/no_brainer/rql.rb +11 -8
  70. data/lib/no_brainer/symbol_decoration.rb +11 -0
  71. data/lib/no_brainer/system/cluster_config.rb +5 -0
  72. data/lib/no_brainer/system/db_config.rb +5 -0
  73. data/lib/no_brainer/system/document.rb +24 -0
  74. data/lib/no_brainer/system/issue.rb +10 -0
  75. data/lib/no_brainer/system/job.rb +10 -0
  76. data/lib/no_brainer/system/log.rb +11 -0
  77. data/lib/no_brainer/system/server_config.rb +7 -0
  78. data/lib/no_brainer/system/server_status.rb +9 -0
  79. data/lib/no_brainer/system/stat.rb +11 -0
  80. data/lib/no_brainer/system/table_config.rb +10 -0
  81. data/lib/no_brainer/system/table_status.rb +8 -0
  82. data/lib/no_brainer/system.rb +17 -0
  83. data/lib/nobrainer.rb +16 -11
  84. data/lib/rails/generators/nobrainer/install_generator.rb +48 -0
  85. data/lib/rails/generators/nobrainer/{model/model_generator.rb → model_generator.rb} +7 -3
  86. data/lib/rails/generators/nobrainer/namespace_fix.rb +15 -0
  87. data/lib/rails/generators/{nobrainer/model/templates/model.rb.tt → templates/model.rb} +1 -1
  88. data/lib/rails/generators/templates/nobrainer.rb +101 -0
  89. metadata +34 -10
  90. data/lib/no_brainer/document/store_in.rb +0 -35
  91. data/lib/rails/generators/nobrainer.rb +0 -18
@@ -1,55 +1,48 @@
1
1
  module NoBrainer::Document::Validation::Uniqueness
2
2
  extend ActiveSupport::Concern
3
3
 
4
- def _create(options={})
4
+ # XXX we don't use read_attribute_for_validation, which goes through the user
5
+ # getters, but read internal attributes instead. It makes more sense.
6
+
7
+ def save?(options={})
5
8
  lock_unique_fields
6
9
  super
7
10
  ensure
8
11
  unlock_unique_fields
9
12
  end
10
13
 
11
- def _update_only_changed_attrs(options={})
12
- lock_unique_fields
13
- super
14
- ensure
15
- unlock_unique_fields
14
+ def _lock_for_uniqueness_once(key)
15
+ @locked_keys_for_uniqueness ||= {}
16
+ @locked_keys_for_uniqueness[key] ||= NoBrainer::Config.distributed_lock_class.new(key).tap(&:lock)
16
17
  end
17
18
 
18
- def _lock_key_from_field(field)
19
- value = read_attribute(field).to_s
20
- ['nobrainer', self.class.database_name || NoBrainer.connection.parsed_uri[:db],
21
- self.class.table_name, field, value.empty? ? 'nil' : value].join(':')
19
+ def unlock_unique_fields
20
+ (@locked_keys_for_uniqueness || {}).values.each(&:unlock)
21
+ @locked_keys_for_uniqueness = {}
22
22
  end
23
23
 
24
24
  def lock_unique_fields
25
- return unless NoBrainer::Config.distributed_lock_class && !self.class.unique_validators.empty?
26
-
27
25
  self.class.unique_validators
28
- .map { |validator| validator.attributes.map { |attr| [attr, validator] } }
29
- .flatten(1)
26
+ .flat_map { |validator| validator.attributes.map { |attr| [attr, validator] } }
30
27
  .select { |f, validator| validator.should_validate_field?(self, f) }
31
- .map { |f, options| _lock_key_from_field(f) }
32
- .sort
33
- .uniq
34
- .each do |key|
35
- lock = NoBrainer::Config.distributed_lock_class.new(key)
36
- lock.lock
37
- @locked_unique_fields ||= []
38
- @locked_unique_fields << lock
39
- end
40
- end
41
-
42
- def unlock_unique_fields
43
- return unless @locked_unique_fields
44
- @locked_unique_fields.pop.unlock until @locked_unique_fields.empty?
28
+ .map { |f, validator| [f, *validator.scope].map { |k| [k, _read_attribute(k)] } }
29
+ .map { |params| self.class._uniqueness_key_name_from_params(params) }
30
+ .sort.each { |key| _lock_for_uniqueness_once(key) }
45
31
  end
46
32
 
47
33
  included do
48
34
  singleton_class.send(:attr_accessor, :unique_validators)
49
35
  self.unique_validators = []
36
+ attr_accessor :locked_keys_for_uniqueness
50
37
  end
51
38
 
52
39
  module ClassMethods
40
+ def _uniqueness_key_name_from_params(params)
41
+ ['uniq', NoBrainer.current_db, self.table_name,
42
+ *params.map { |k,v| [k.to_s, (v = v.to_s; v.empty? ? 'nil' : v)] }.sort
43
+ ].join(':')
44
+ end
45
+
53
46
  def validates_uniqueness_of(*attr_names)
54
47
  validates_with(UniquenessValidator, _merge_attributes(attr_names))
55
48
  end
@@ -61,14 +54,15 @@ module NoBrainer::Document::Validation::Uniqueness
61
54
  end
62
55
 
63
56
  class UniquenessValidator < ActiveModel::EachValidator
64
- attr_accessor :scope
57
+ attr_accessor :scope, :model
65
58
 
66
59
  def initialize(options={})
67
60
  super
68
- model = options[:class]
69
- self.scope = [*options[:scope]]
70
- ([model] + model.descendants).each do |_model|
71
- _model.unique_validators << self
61
+ self.model = options[:class]
62
+ self.scope = [*options[:scope]].map(&:to_sym)
63
+
64
+ model.subclass_tree.each do |subclass|
65
+ subclass.unique_validators << self
72
66
  end
73
67
  end
74
68
 
@@ -77,7 +71,7 @@ module NoBrainer::Document::Validation::Uniqueness
77
71
  end
78
72
 
79
73
  def validate_each(doc, attr, value)
80
- criteria = doc.root_class.unscoped.where(attr => value)
74
+ criteria = self.model.unscoped.where(attr => value)
81
75
  criteria = apply_scopes(criteria, doc)
82
76
  criteria = exclude_doc(criteria, doc) if doc.persisted?
83
77
  doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless criteria.empty?
@@ -89,11 +83,11 @@ module NoBrainer::Document::Validation::Uniqueness
89
83
  end
90
84
 
91
85
  def apply_scopes(criteria, doc)
92
- criteria.where(scope.map { |k| {k => doc.read_attribute(k)} })
86
+ criteria.where(scope.map { |k| {k => doc._read_attribute(k)} })
93
87
  end
94
88
 
95
89
  def exclude_doc(criteria, doc)
96
- criteria.where(doc.class.pk_name.ne => doc.pk_value)
90
+ criteria.where(doc.class.pk_name.not => doc.pk_value)
97
91
  end
98
92
  end
99
93
  end
@@ -1,63 +1,6 @@
1
1
  module NoBrainer::Document::Validation
2
2
  extend NoBrainer::Autoload
3
3
  extend ActiveSupport::Concern
4
- include ActiveModel::Validations
5
- include ActiveModel::Validations::Callbacks
6
4
 
7
- autoload_and_include :Uniqueness, :NotNull
8
-
9
- included do
10
- # We don't want before_validation returning false to halt the chain.
11
- define_callbacks :validation, :skip_after_callbacks_if_terminated => true,
12
- :scope => [:kind, :name], :terminator => proc { false }
13
- end
14
-
15
- def valid?(context=nil, options={})
16
- context ||= new_record? ? :create : :update
17
-
18
- # XXX Monkey Patching, because we need to have control on errors.clear
19
- current_context, self.validation_context = validation_context, context
20
- errors.clear unless options[:clear_errors] == false
21
- run_validations!
22
- ensure
23
- self.validation_context = current_context
24
- end
25
-
26
- SHORTHANDS = { :format => :format, :length => :length, :required => :presence,
27
- :uniq => :uniqueness, :unique => :uniqueness, :in => :inclusion }
28
-
29
- module ClassMethods
30
- def _field(attr, options={})
31
- super
32
-
33
- shorthands = SHORTHANDS
34
- shorthands = shorthands.merge(:required => :not_null) if options[:type] == NoBrainer::Boolean
35
- shorthands.each { |k,v| validates(attr, v => options[k]) if options.has_key?(k) }
36
-
37
- validates(attr, options[:validates]) if options[:validates]
38
- validates(attr, :length => { :minimum => options[:min_length] }) if options[:min_length]
39
- validates(attr, :length => { :maximum => options[:max_length] }) if options[:max_length]
40
- end
41
- end
42
- end
43
-
44
- class ActiveModel::EachValidator
45
- def should_validate_field?(record, attribute)
46
- return true unless record.is_a?(NoBrainer::Document)
47
- return true if record.new_record?
48
-
49
- attr_changed = "#{attribute}_changed?"
50
- return record.respond_to?(attr_changed) ? record.__send__(attr_changed) : true
51
- end
52
-
53
- # XXX Monkey Patching :(
54
- def validate(record)
55
- attributes.each do |attribute|
56
- next unless should_validate_field?(record, attribute) # <--- Added
57
- value = record.read_attribute_for_validation(attribute)
58
- next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- Added
59
- next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
60
- validate_each(record, attribute, value)
61
- end
62
- end
5
+ autoload_and_include :Core, :Uniqueness, :NotNull
63
6
  end
@@ -4,8 +4,8 @@ module NoBrainer::Document
4
4
  extend ActiveSupport::Concern
5
5
  extend NoBrainer::Autoload
6
6
 
7
- autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
8
- :Persistance, :Validation, :Types, :Callbacks, :Dirty, :PrimaryKey,
7
+ autoload_and_include :Core, :TableConfig, :InjectionLayer, :Attributes, :Readonly,
8
+ :Persistance, :Callbacks, :Validation, :Types, :Dirty, :PrimaryKey,
9
9
  :Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
10
10
  :MissingAttributes, :LazyFetch, :AtomicOps
11
11
 
@@ -12,6 +12,7 @@ module NoBrainer::Error
12
12
  class AtomicBlock < RuntimeError; end
13
13
  class LostLock < RuntimeError; end
14
14
  class LockUnavailable < RuntimeError; end
15
+ class InvalidPolymorphicType < RuntimeError; end
15
16
 
16
17
  class DocumentInvalid < RuntimeError
17
18
  attr_accessor :instance
@@ -9,7 +9,6 @@ module NoBrainer::Geo::Base
9
9
 
10
10
  options[:unit] = unit if unit && unit.to_s != 'm'
11
11
  options[:geo_system] = geo_system if geo_system && geo_system.to_s != 'WGS84'
12
- options[:max_dist] = options.delete(:max_distance) if options[:max_distance]
13
12
 
14
13
  options
15
14
  end
@@ -4,3 +4,4 @@ en:
4
4
  taken: "is already taken"
5
5
  invalid_type: "should be a %{type}"
6
6
  undefined: "must be defined"
7
+ invalid_foreign_key: "is an invalid foreign key: %{target_model}(%{primary_key}: %{value}) is not found"
@@ -3,7 +3,7 @@ require 'digest/sha1'
3
3
  class NoBrainer::Lock
4
4
  include NoBrainer::Document
5
5
 
6
- store_in :table => 'nobrainer_locks'
6
+ table_config :name => 'nobrainer_locks'
7
7
 
8
8
  # Since PKs are limited to 127 characters, we can't use the user's key as a PK
9
9
  # as it could be arbitrarily long.
@@ -30,12 +30,16 @@ class NoBrainer::Lock
30
30
  super(options.merge(:key => key))
31
31
  end
32
32
 
33
- def lock(options={}, &block)
34
- if block
35
- lock(options)
36
- return block.call.tap { unlock }
33
+ def synchronize(options={}, &block)
34
+ lock(options)
35
+ begin
36
+ block.call
37
+ ensure
38
+ unlock if @locked
37
39
  end
40
+ end
38
41
 
42
+ def lock(options={})
39
43
  options.assert_valid_keys(:expire, :timeout)
40
44
  timeout = NoBrainer::Config.lock_options.merge(options)[:timeout]
41
45
  sleep_amount = 0.1
@@ -102,13 +106,13 @@ class NoBrainer::Lock
102
106
  end
103
107
  end
104
108
 
109
+ def save?(*); raise NotImplementedError; end
110
+ def delete(*); raise NotImplementedError; end
111
+
105
112
  private
106
113
 
107
114
  def set_expiration(options)
108
115
  expire = NoBrainer::Config.lock_options.merge(options)[:expire]
109
116
  self.expires_at = RethinkDB::RQL.new.now + expire
110
117
  end
111
-
112
- def save?; raise; end
113
- def delete; raise; end
114
118
  end
@@ -0,0 +1,76 @@
1
+ # Rails specific. TODO Test
2
+ module NoBrainer::Profiler::ControllerRuntime
3
+ extend ActiveSupport::Concern
4
+
5
+ class Profiler
6
+ attr_accessor :write_duration, :read_duration, :other_duration
7
+ def initialize
8
+ @write_duration = @read_duration = @other_duration = 0.0
9
+ end
10
+
11
+ def total_duration
12
+ read_duration + write_duration + other_duration
13
+ end
14
+
15
+ def add_query(env)
16
+ case env[:query_type]
17
+ when :write then @write_duration += env[:duration]
18
+ when :read then @read_duration += env[:duration]
19
+ else @other_duration += env[:duration]
20
+ end
21
+ end
22
+
23
+ def self.spawn_controller_profiler
24
+ Thread.current[:nobrainer_controller_profiler] = new
25
+ end
26
+
27
+ def self.cleanup_controller_profiler
28
+ Thread.current[:nobrainer_controller_profiler] = nil
29
+ end
30
+
31
+ def self.current
32
+ Thread.current[:nobrainer_controller_profiler]
33
+ end
34
+
35
+ def self.on_query(env)
36
+ current.try(:add_query, env)
37
+ end
38
+
39
+ NoBrainer::Profiler.register(self)
40
+ end
41
+
42
+ def process_action(action, *args)
43
+ Profiler.spawn_controller_profiler
44
+ super
45
+ ensure
46
+ Profiler.cleanup_controller_profiler
47
+ end
48
+
49
+ def cleanup_view_runtime
50
+ time_spent_in_db_before_views = Profiler.current.total_duration
51
+ runtime = super
52
+ time_spent_in_db_after_views = Profiler.current.total_duration
53
+
54
+ time_spent_in_db_during_views = (time_spent_in_db_after_views - time_spent_in_db_before_views) * 1000
55
+ runtime - time_spent_in_db_during_views
56
+ end
57
+
58
+ def append_info_to_payload(payload)
59
+ super
60
+ payload[:nobrainer_profiler] = Profiler.current
61
+ end
62
+
63
+ module ClassMethods # :nodoc:
64
+ def log_process_action(payload)
65
+ messages, profiler = super, payload[:nobrainer_profiler]
66
+ if profiler && !profiler.total_duration.zero?
67
+ msg = []
68
+ msg << "%.1fms (write)" % (profiler.write_duration * 1000) unless profiler.write_duration.zero?
69
+ msg << "%.1fms (read)" % (profiler.read_duration * 1000) unless profiler.read_duration.zero?
70
+ msg << "%.1fms (other)" % (profiler.other_duration * 1000) unless profiler.other_duration.zero?
71
+ messages << "NoBrainer: #{msg.join(", ")}"
72
+ end
73
+ messages
74
+ end
75
+ end
76
+ end
@@ -1,41 +1,29 @@
1
- class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
2
- def call(env)
3
- start_time = Time.now
4
- @runner.call(env).tap { log_query(env, start_time) }
5
- rescue Exception => e
6
- log_query(env, start_time, e)
7
- raise e
8
- end
9
-
10
- private
11
-
12
- def log_query(env, start_time, exception=nil)
13
- return if handle_on_demand_exception?(env, exception)
14
-
1
+ class NoBrainer::Profiler::Logger
2
+ def on_query(env)
15
3
  not_indexed = env[:criteria] && env[:criteria].where_present? &&
16
4
  !env[:criteria].where_indexed? &&
17
5
  !env[:criteria].model.try(:perf_warnings_disabled)
18
6
 
19
- level = exception ? Logger::ERROR :
7
+ level = env[:exception] ? Logger::ERROR :
20
8
  not_indexed ? Logger::INFO : Logger::DEBUG
21
- return if NoBrainer.logger.nil? || NoBrainer.logger.level > level
9
+ return if NoBrainer.logger.level > level
22
10
 
23
- duration = Time.now - start_time
24
-
25
- msg_duration = (duration * 1000.0).round(1).to_s
11
+ msg_duration = (env[:duration] * 1000.0).round(1).to_s
26
12
  msg_duration = " " * [0, 6 - msg_duration.size].max + msg_duration
27
13
  msg_duration = "[#{msg_duration}ms] "
28
14
 
29
- msg_db = "[#{env[:db_name]}] " if env[:db_name] && env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
15
+ env[:query_type] = NoBrainer::RQL.type_of(env[:query])
16
+
17
+ msg_db = "[#{env[:options][:db]}] " if env[:options][:db]
30
18
  msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
31
19
 
32
- msg_exception = "#{exception.class} #{exception.message.split("\n").first}" if exception
20
+ msg_exception = "#{env[:exception].class} #{env[:exception].message.split("\n").first}" if env[:exception]
33
21
  msg_exception ||= "perf: filtering without using an index" if not_indexed
34
22
 
35
23
  msg_last = nil
36
24
 
37
25
  if NoBrainer::Config.colorize_logger
38
- query_color = case NoBrainer::RQL.type_of(env[:query])
26
+ query_color = case env[:query_type]
39
27
  when :write then "\e[1;31m" # red
40
28
  when :read then "\e[1;32m" # green
41
29
  when :management then "\e[1;33m" # yellow
@@ -53,9 +41,5 @@ class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
53
41
  NoBrainer.logger.add(level, msg)
54
42
  end
55
43
 
56
- def handle_on_demand_exception?(env, e)
57
- # pretty gross I must say.
58
- e && (NoBrainer::QueryRunner::DatabaseOnDemand.new(nil).handle_database_on_demand_exception?(env, e) ||
59
- NoBrainer::QueryRunner::TableOnDemand.new(nil).handle_table_on_demand_exception?(env, e))
60
- end
44
+ NoBrainer::Profiler.register(self.new)
61
45
  end
@@ -0,0 +1,11 @@
1
+ module NoBrainer::Profiler
2
+ class << self
3
+ attr_accessor :registered_profilers
4
+
5
+ def register(profiler)
6
+ self.registered_profilers << profiler
7
+ end
8
+ end
9
+
10
+ self.registered_profilers = []
11
+ end
@@ -2,8 +2,8 @@ class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middlew
2
2
  def call(env)
3
3
  @runner.call(env)
4
4
  rescue RuntimeError => e
5
- if database_name = handle_database_on_demand_exception?(env, e)
6
- auto_create_database(env, database_name)
5
+ if db_name = handle_database_on_demand_exception?(env, e)
6
+ auto_create_database(env, db_name)
7
7
  retry
8
8
  end
9
9
  raise
@@ -15,15 +15,15 @@ class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middlew
15
15
 
16
16
  private
17
17
 
18
- def auto_create_database(env, database_name)
19
- if env[:last_auto_create_database] == database_name
20
- raise "Auto database creation is not working with #{database_name}"
18
+ def auto_create_database(env, db_name)
19
+ if env[:last_auto_create_database] == db_name
20
+ raise "Auto database creation is not working with #{db_name}"
21
21
  end
22
- env[:last_auto_create_database] = database_name
22
+ env[:last_auto_create_database] = db_name
23
23
 
24
- NoBrainer.db_create(database_name)
24
+ NoBrainer.run { |r| r.db_create(db_name) }
25
25
  rescue RuntimeError => e
26
26
  # We might have raced with another db_create
27
- raise unless e.message =~ /Database `#{database_name}` already exists/
27
+ raise unless e.message =~ /Database `#{db_name}` already exists/
28
28
  end
29
29
  end
@@ -1,5 +1,7 @@
1
1
  class NoBrainer::QueryRunner::Driver < NoBrainer::QueryRunner::Middleware
2
2
  def call(env)
3
- env[:query].run(NoBrainer.connection.raw, env[:options])
3
+ options = env[:options]
4
+ options = options.merge(:db => RethinkDB::RQL.new.db(options[:db])) if options[:db]
5
+ env[:query].run(NoBrainer.connection.raw, options)
4
6
  end
5
7
  end
@@ -4,7 +4,7 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
4
4
  rescue RethinkDB::RqlRuntimeError => e
5
5
  if e.message =~ /^Index `(.+)` was not found on table `(.+)\.(.+)`\.$/
6
6
  index_name = $1
7
- database_name = $2
7
+ db_name = $2
8
8
  table_name = $3
9
9
 
10
10
  model = NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
@@ -12,10 +12,10 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
12
12
  index_name = index.name if index
13
13
 
14
14
  if model.try(:pk_name).try(:to_s) == index_name.to_s
15
- err_msg = "Please update the primary key `#{index_name}` in the table `#{database_name}.#{table_name}`."
15
+ err_msg = "Please update the primary key `#{index_name}` in the table `#{db_name}.#{table_name}`."
16
16
  else
17
17
  err_msg = "Please run `NoBrainer.sync_indexes' or `rake nobrainer:sync_indexes' to create the index `#{index_name}`"
18
- err_msg += " in the table `#{database_name}.#{table_name}`."
18
+ err_msg += " in the table `#{db_name}.#{table_name}`."
19
19
  err_msg += " Read http://nobrainer.io/docs/indexes for more information."
20
20
  end
21
21
 
@@ -0,0 +1,43 @@
1
+ class NoBrainer::QueryRunner::Profiler < NoBrainer::QueryRunner::Middleware
2
+ def call(env)
3
+ profiler_start(env)
4
+ @runner.call(env).tap { profiler_end(env) }
5
+ rescue Exception => e
6
+ profiler_end(env, e)
7
+ raise e
8
+ end
9
+
10
+ private
11
+
12
+ require 'no_brainer/profiler/logger'
13
+
14
+ def profiler_start(env)
15
+ env[:start_time] = Time.now
16
+ end
17
+
18
+ def profiler_end(env, exception=nil)
19
+ return if handle_on_demand_exception?(env, exception)
20
+
21
+ env[:end_time] = Time.now
22
+ env[:duration] = env[:end_time] - env[:start_time]
23
+ env[:exception] = exception
24
+
25
+ env[:model] = env[:criteria] && env[:criteria].model
26
+ env[:query_type] = NoBrainer::RQL.type_of(env[:query])
27
+
28
+ NoBrainer::Profiler.registered_profilers.each do |profiler|
29
+ begin
30
+ profiler.on_query(env)
31
+ rescue Exception => e
32
+ STDERR.puts "[NoBrainer] Profiling error: #{e.class} #{e.message}"
33
+ end
34
+ end
35
+ end
36
+
37
+ def handle_on_demand_exception?(env, e)
38
+ # pretty gross I must say.
39
+ e && (NoBrainer::QueryRunner::DatabaseOnDemand.new(nil).handle_database_on_demand_exception?(env, e) ||
40
+ NoBrainer::QueryRunner::TableOnDemand.new(nil).handle_table_on_demand_exception?(env, e))
41
+ end
42
+ end
43
+
@@ -2,28 +2,44 @@ class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
2
2
  def call(env)
3
3
  @runner.call(env)
4
4
  rescue StandardError => e
5
- context ||= { :retries => NoBrainer::Config.max_retries_on_connection_failure }
6
5
  if is_connection_error_exception?(e)
7
- if NoBrainer::Config.max_retries_on_connection_failure == 0
8
- NoBrainer.disconnect
9
- else
10
- # XXX Possibly dangerous, as we could reexecute a non idempotent operation
11
- # Check the semantics of the db
12
- retry if reconnect(e, context)
13
- end
6
+ context ||= {}
7
+ # XXX Possibly dangerous, as we could reexecute a non idempotent operation
8
+ retry if reconnect(e, context)
14
9
  end
15
10
  raise
16
11
  end
17
12
 
18
13
  private
19
14
 
15
+ def max_tries
16
+ NoBrainer::Config.max_retries_on_connection_failure
17
+ end
18
+
20
19
  def reconnect(e, context)
21
- return false if context[:retries].zero?
22
- context[:retries] -= 1
20
+ context[:connection_retries] ||= max_tries
21
+ context[:previous_connection] ||= NoBrainer.connection
22
+ NoBrainer.disconnect
23
+
24
+ unless context[:lost_connection_logged]
25
+ context[:lost_connection_logged] = true
26
+
27
+ msg = server_not_ready?(e) ? "Server %s not ready: %s" : "Connection issue with %s: %s"
28
+ NoBrainer.logger.warn(msg % [context[:previous_connection].try(:uri), exception_msg(e)])
29
+ end
30
+
31
+ if context[:connection_retries].zero?
32
+ NoBrainer.logger.info("Retry limit exceeded (#{max_tries}). Giving up.")
33
+ return false
34
+ end
35
+ context[:connection_retries] -= 1
23
36
 
24
- warn_reconnect(e)
25
37
  sleep 1
26
- NoBrainer.connection.reconnect(:noreply_wait => false)
38
+
39
+ c = NoBrainer.connection
40
+ NoBrainer.logger.info("Connecting to #{c.uri}... (last error: #{exception_msg(e)})")
41
+ c.connect
42
+
27
43
  true
28
44
  rescue StandardError => e
29
45
  retry if is_connection_error_exception?(e)
@@ -36,21 +52,20 @@ class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
36
52
  Errno::ECONNRESET, Errno::ETIMEDOUT, IOError
37
53
  true
38
54
  when RethinkDB::RqlRuntimeError
39
- e.message =~ /No master available/ ||
40
- e.message =~ /Master .* not available/ ||
41
- e.message =~ /Error: Connection Closed/
55
+ e.message =~ /lost contact/ ||
56
+ e.message =~ /(P|p)rimary .* not available/||
57
+ e.message =~ /Connection.*closed/
42
58
  else
43
59
  false
44
60
  end
45
61
  end
46
62
 
47
- def warn_reconnect(e)
48
- if e.is_a?(RethinkDB::RqlRuntimeError)
49
- e_msg = e.message.split("\n").first
50
- msg = "Server #{NoBrainer::Config.rethinkdb_url} not ready - #{e_msg}, retrying..."
51
- else
52
- msg = "Connection issue with #{NoBrainer::Config.rethinkdb_url} - #{e}, retrying..."
53
- end
54
- NoBrainer.logger.try(:warn, msg)
63
+ def exception_msg(e)
64
+ e.is_a?(RethinkDB::RqlRuntimeError) ? e.message.split("\n").first : e.to_s
65
+ end
66
+
67
+ def server_not_ready?(e)
68
+ e.message =~ /lost contact/ ||
69
+ e.message =~ /(P|p)rimary .* not available/
55
70
  end
56
71
  end