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.
- checksums.yaml +4 -4
- data/lib/no_brainer/autoload.rb +1 -1
- data/lib/no_brainer/config.rb +54 -14
- data/lib/no_brainer/connection.rb +24 -21
- data/lib/no_brainer/connection_manager.rb +29 -9
- data/lib/no_brainer/criteria/cache.rb +8 -0
- data/lib/no_brainer/criteria/changes.rb +16 -0
- data/lib/no_brainer/criteria/core.rb +4 -5
- data/lib/no_brainer/criteria/eager_load.rb +2 -2
- data/lib/no_brainer/criteria/enumerable.rb +3 -1
- data/lib/no_brainer/criteria/find.rb +6 -9
- data/lib/no_brainer/criteria/first.rb +3 -3
- data/lib/no_brainer/criteria/first_or_create.rb +114 -0
- data/lib/no_brainer/criteria/join.rb +62 -0
- data/lib/no_brainer/criteria/order_by.rb +19 -16
- data/lib/no_brainer/criteria/run.rb +26 -0
- data/lib/no_brainer/criteria/scope.rb +8 -8
- data/lib/no_brainer/criteria/where.rb +130 -107
- data/lib/no_brainer/criteria.rb +4 -4
- data/lib/no_brainer/document/association/belongs_to.rb +44 -17
- data/lib/no_brainer/document/association/core.rb +11 -0
- data/lib/no_brainer/document/association/eager_loader.rb +26 -26
- data/lib/no_brainer/document/association/has_many.rb +7 -9
- data/lib/no_brainer/document/association/has_many_through.rb +1 -2
- data/lib/no_brainer/document/association/has_one.rb +5 -1
- data/lib/no_brainer/document/association.rb +5 -5
- data/lib/no_brainer/document/atomic_ops.rb +40 -7
- data/lib/no_brainer/document/attributes.rb +24 -15
- data/lib/no_brainer/document/callbacks.rb +1 -1
- data/lib/no_brainer/document/core.rb +18 -18
- data/lib/no_brainer/document/criteria.rb +9 -5
- data/lib/no_brainer/document/dirty.rb +15 -12
- data/lib/no_brainer/document/dynamic_attributes.rb +6 -0
- data/lib/no_brainer/document/index/index.rb +5 -9
- data/lib/no_brainer/document/index/meta_store.rb +1 -10
- data/lib/no_brainer/document/index/synchronizer.rb +16 -20
- data/lib/no_brainer/document/index.rb +3 -3
- data/lib/no_brainer/document/lazy_fetch.rb +3 -3
- data/lib/no_brainer/document/missing_attributes.rb +7 -2
- data/lib/no_brainer/document/persistance.rb +14 -30
- data/lib/no_brainer/document/polymorphic.rb +8 -4
- data/lib/no_brainer/document/primary_key/generator.rb +6 -1
- data/lib/no_brainer/document/primary_key.rb +19 -4
- data/lib/no_brainer/document/serialization.rb +0 -2
- data/lib/no_brainer/document/table_config/synchronizer.rb +21 -0
- data/lib/no_brainer/document/table_config.rb +118 -0
- data/lib/no_brainer/document/timestamps.rb +8 -0
- data/lib/no_brainer/document/validation/core.rb +63 -0
- data/lib/no_brainer/document/validation/uniqueness.rb +30 -36
- data/lib/no_brainer/document/validation.rb +1 -58
- data/lib/no_brainer/document.rb +2 -2
- data/lib/no_brainer/error.rb +1 -0
- data/lib/no_brainer/geo/base.rb +0 -1
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/lock.rb +12 -8
- data/lib/no_brainer/profiler/controller_runtime.rb +76 -0
- data/lib/no_brainer/{query_runner → profiler}/logger.rb +11 -27
- data/lib/no_brainer/profiler.rb +11 -0
- data/lib/no_brainer/query_runner/database_on_demand.rb +8 -8
- data/lib/no_brainer/query_runner/driver.rb +3 -1
- data/lib/no_brainer/query_runner/missing_index.rb +3 -3
- data/lib/no_brainer/query_runner/profiler.rb +43 -0
- data/lib/no_brainer/query_runner/reconnect.rb +38 -23
- data/lib/no_brainer/query_runner/run_options.rb +35 -15
- data/lib/no_brainer/query_runner/table_on_demand.rb +18 -11
- data/lib/no_brainer/query_runner.rb +3 -3
- data/lib/no_brainer/railtie/database.rake +14 -4
- data/lib/no_brainer/railtie.rb +5 -12
- data/lib/no_brainer/rql.rb +11 -8
- data/lib/no_brainer/symbol_decoration.rb +11 -0
- data/lib/no_brainer/system/cluster_config.rb +5 -0
- data/lib/no_brainer/system/db_config.rb +5 -0
- data/lib/no_brainer/system/document.rb +24 -0
- data/lib/no_brainer/system/issue.rb +10 -0
- data/lib/no_brainer/system/job.rb +10 -0
- data/lib/no_brainer/system/log.rb +11 -0
- data/lib/no_brainer/system/server_config.rb +7 -0
- data/lib/no_brainer/system/server_status.rb +9 -0
- data/lib/no_brainer/system/stat.rb +11 -0
- data/lib/no_brainer/system/table_config.rb +10 -0
- data/lib/no_brainer/system/table_status.rb +8 -0
- data/lib/no_brainer/system.rb +17 -0
- data/lib/nobrainer.rb +16 -11
- data/lib/rails/generators/nobrainer/install_generator.rb +48 -0
- data/lib/rails/generators/nobrainer/{model/model_generator.rb → model_generator.rb} +7 -3
- data/lib/rails/generators/nobrainer/namespace_fix.rb +15 -0
- data/lib/rails/generators/{nobrainer/model/templates/model.rb.tt → templates/model.rb} +1 -1
- data/lib/rails/generators/templates/nobrainer.rb +101 -0
- metadata +34 -10
- data/lib/no_brainer/document/store_in.rb +0 -35
- 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
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
.
|
|
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,
|
|
32
|
-
.
|
|
33
|
-
.
|
|
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
|
-
|
|
71
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
data/lib/no_brainer/document.rb
CHANGED
|
@@ -4,8 +4,8 @@ module NoBrainer::Document
|
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
extend NoBrainer::Autoload
|
|
6
6
|
|
|
7
|
-
autoload_and_include :Core, :
|
|
8
|
-
:Persistance, :
|
|
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
|
|
data/lib/no_brainer/error.rb
CHANGED
|
@@ -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
|
data/lib/no_brainer/geo/base.rb
CHANGED
data/lib/no_brainer/lock.rb
CHANGED
|
@@ -3,7 +3,7 @@ require 'digest/sha1'
|
|
|
3
3
|
class NoBrainer::Lock
|
|
4
4
|
include NoBrainer::Document
|
|
5
5
|
|
|
6
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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::
|
|
2
|
-
def
|
|
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.
|
|
9
|
+
return if NoBrainer.logger.level > level
|
|
22
10
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
6
|
-
auto_create_database(env,
|
|
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,
|
|
19
|
-
if env[:last_auto_create_database] ==
|
|
20
|
-
raise "Auto database creation is not working with #{
|
|
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] =
|
|
22
|
+
env[:last_auto_create_database] = db_name
|
|
23
23
|
|
|
24
|
-
NoBrainer.db_create(
|
|
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 `#{
|
|
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
|
-
|
|
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
|
-
|
|
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 `#{
|
|
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 `#{
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
22
|
-
context[:
|
|
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
|
-
|
|
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 =~ /
|
|
40
|
-
e.message =~ /
|
|
41
|
-
e.message =~ /
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|