nobrainer 0.24.0 → 0.25.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/connection.rb +12 -6
- data/lib/no_brainer/criteria/cache.rb +8 -0
- data/lib/no_brainer/criteria/core.rb +4 -5
- data/lib/no_brainer/criteria/eager_load.rb +1 -1
- data/lib/no_brainer/criteria/find.rb +6 -9
- data/lib/no_brainer/criteria/first_or_create.rb +101 -0
- 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 +73 -62
- data/lib/no_brainer/criteria.rb +4 -4
- data/lib/no_brainer/document/criteria.rb +4 -2
- data/lib/no_brainer/document/index/index.rb +3 -7
- data/lib/no_brainer/document/index/meta_store.rb +1 -10
- data/lib/no_brainer/document/index/synchronizer.rb +4 -6
- data/lib/no_brainer/document/polymorphic.rb +5 -1
- data/lib/no_brainer/document/store_in.rb +7 -9
- data/lib/no_brainer/document/validation/uniqueness.rb +25 -28
- data/lib/no_brainer/error.rb +1 -0
- data/lib/no_brainer/lock.rb +8 -4
- data/lib/no_brainer/profiler/logger.rb +1 -2
- 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 +0 -1
- data/lib/no_brainer/query_runner/run_options.rb +35 -15
- data/lib/no_brainer/query_runner/table_on_demand.rb +7 -7
- data/lib/nobrainer.rb +7 -5
- metadata +4 -3
- data/lib/no_brainer/logger.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 950e98b70408c2d1fc05a6ee2e80f1f3e942999b
|
4
|
+
data.tar.gz: 5b4ec8da15e3d9f48e1729f07cd336af12c27433
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3719c006243290324956ec397d700cae2766598460423199bfc9ed3f731761b2d6fceec50365f2a839099b2edc3fb751aa88984a349aab4ef2132b6547444f09
|
7
|
+
data.tar.gz: 02bc9ef3d13a887305d6fdd81a7f1eebd296a63c206f9347bce5e8078f3af71c36e625d9ebe5ac0ce55a93da24b5760842ef3e2b6e55738d38df189d6384c731
|
@@ -42,17 +42,23 @@ class NoBrainer::Connection
|
|
42
42
|
RUBY
|
43
43
|
end
|
44
44
|
|
45
|
+
def default_db
|
46
|
+
parsed_uri[:db]
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_db
|
50
|
+
NoBrainer.current_run_options.try(:[], :db) || default_db
|
51
|
+
end
|
52
|
+
|
45
53
|
def drop!
|
46
|
-
|
47
|
-
db = (Thread.current[:nobrainer_options] || parsed_uri)[:db]
|
48
|
-
db_drop(db)['dropped'] == 1
|
54
|
+
db_drop(current_db)['dropped'] == 1
|
49
55
|
end
|
50
56
|
|
51
|
-
# Note that truncating each table (purge) is much faster than dropping the
|
52
|
-
# database (drop)
|
57
|
+
# Note that truncating each table (purge!) is much faster than dropping the database (drop!)
|
53
58
|
def purge!
|
54
59
|
table_list.each do |table_name|
|
55
|
-
|
60
|
+
# keeping the index meta store because indexes are not going away
|
61
|
+
next if table_name == NoBrainer::Document::Index::MetaStore.table_name
|
56
62
|
NoBrainer.run { |r| r.table(table_name).delete }
|
57
63
|
end
|
58
64
|
true
|
@@ -77,4 +77,12 @@ module NoBrainer::Criteria::Cache
|
|
77
77
|
|
78
78
|
use_cache_for :first, :last, :count, :empty?, :any?
|
79
79
|
reload_on :update_all, :destroy_all, :delete_all
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def apply_named_scope(name, args, block)
|
84
|
+
return super unless with_cache?
|
85
|
+
@scope_cache ||= {}
|
86
|
+
@scope_cache[[name, args, block]] ||= super
|
87
|
+
end
|
80
88
|
end
|
@@ -56,11 +56,6 @@ module NoBrainer::Criteria::Core
|
|
56
56
|
merge(self.class.new(options), merge_options)
|
57
57
|
end
|
58
58
|
|
59
|
-
def run(&block)
|
60
|
-
block ||= proc { to_rql }
|
61
|
-
NoBrainer.run(:criteria => self, &block)
|
62
|
-
end
|
63
|
-
|
64
59
|
def compile_rql_pass1
|
65
60
|
# This method is overriden by other modules.
|
66
61
|
raise "Criteria not bound to a model" unless model
|
@@ -105,5 +100,9 @@ module NoBrainer::Criteria::Core
|
|
105
100
|
def append_array(a, b)
|
106
101
|
a ? a+b : b
|
107
102
|
end
|
103
|
+
|
104
|
+
def merge_hash(a, b)
|
105
|
+
a ? a.merge(b) : b
|
106
|
+
end
|
108
107
|
end
|
109
108
|
end
|
@@ -15,7 +15,7 @@ module NoBrainer::Criteria::EagerLoad
|
|
15
15
|
def merge!(criteria, options={})
|
16
16
|
super.tap do
|
17
17
|
# If we already have some cached documents, and we need to so some eager
|
18
|
-
# loading, then we it now. It's easier than doing it lazily.
|
18
|
+
# loading, then we do it now. It's easier than doing it lazily.
|
19
19
|
if self.cached? && criteria.options[:eager_load].present?
|
20
20
|
perform_eager_load(@cache)
|
21
21
|
end
|
@@ -1,27 +1,24 @@
|
|
1
1
|
module NoBrainer::Criteria::Find
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
def find_by?(*args, &block)
|
5
|
-
where(*args, &block).first
|
6
|
-
end
|
7
|
-
|
8
4
|
def find_by(*args, &block)
|
9
|
-
find_by
|
5
|
+
raise "find_by() has unclear semantics. Please use where().first instead"
|
10
6
|
end
|
11
7
|
alias_method :find_by!, :find_by
|
8
|
+
alias_method :find_by?, :find_by
|
12
9
|
|
13
10
|
def find?(pk)
|
14
|
-
without_ordering.
|
11
|
+
without_ordering.where(model.pk_name => pk).first
|
15
12
|
end
|
16
13
|
|
17
14
|
def find(pk)
|
18
|
-
|
15
|
+
find?(pk).tap { |doc| raise_not_found(pk) unless doc }
|
19
16
|
end
|
20
17
|
alias_method :find!, :find
|
21
18
|
|
22
19
|
private
|
23
20
|
|
24
|
-
def raise_not_found(
|
25
|
-
raise NoBrainer::Error::DocumentNotFound, "#{model}
|
21
|
+
def raise_not_found(pk)
|
22
|
+
raise NoBrainer::Error::DocumentNotFound, "#{model} :#{model.pk_name}=>#{pk.inspect} not found"
|
26
23
|
end
|
27
24
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module NoBrainer::Criteria::FirstOrCreate
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def first_or_create(create_params={}, &block)
|
5
|
+
_first_or_create(create_params, :save_method => :save?, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def first_or_create!(create_params={}, &block)
|
9
|
+
_first_or_create(create_params, :save_method => :save!, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def _first_or_create(create_params={}, options={}, &block)
|
15
|
+
raise "Cannot use .raw() with .first_or_create()" if raw?
|
16
|
+
raise "Use first_or_create() on the root class `#{model.root_class}'" unless model.is_root_class?
|
17
|
+
|
18
|
+
where_params = extract_where_params()
|
19
|
+
keys = where_params.keys
|
20
|
+
|
21
|
+
# When matching on the primary key, we'll just pretend that we have a
|
22
|
+
# uniqueness validator on it. We will be racy against other create(),
|
23
|
+
# but not on first_or_create().
|
24
|
+
# And if we get caught in a race with another create(), we'll just have a
|
25
|
+
# duplicate primary key exception.
|
26
|
+
matched_validator = true if keys == [model.pk_name]
|
27
|
+
matched_validator ||= !!get_uniqueness_validators_map[keys.sort]
|
28
|
+
unless matched_validator
|
29
|
+
# We could do without a uniqueness validator, but it's much preferable to
|
30
|
+
# have it, so that we don't conflict with others create(), not just others
|
31
|
+
# first_or_create().
|
32
|
+
raise "Please add the following uniqueness validator for first_or_create():\n" +
|
33
|
+
"class #{model}\n" +
|
34
|
+
case keys.size
|
35
|
+
when 1 then " field :#{keys.first}, :uniq => true"
|
36
|
+
when 2 then " field :#{keys.first}, :uniq => {:scope => :#{keys.last}}"
|
37
|
+
else " field :#{keys.first}, :uniq => {:scope => #{keys[1..-1].inspect}}"
|
38
|
+
end +
|
39
|
+
"\nend"
|
40
|
+
end
|
41
|
+
|
42
|
+
# We don't want to access create_params yet, because invoking the block
|
43
|
+
# might be costly (the user might be doing some API call or w/e), and
|
44
|
+
# so we want to invoke the block only if necessary.
|
45
|
+
new_instance = model.new(where_params)
|
46
|
+
lock_key_name = model._uniqueness_key_name_from_params(where_params)
|
47
|
+
new_instance._lock_for_uniqueness_once(lock_key_name)
|
48
|
+
|
49
|
+
old_instance = self.first
|
50
|
+
return old_instance if old_instance
|
51
|
+
|
52
|
+
create_params = block.call if block
|
53
|
+
create_params = create_params.symbolize_keys
|
54
|
+
|
55
|
+
keys_in_conflict = create_params.keys & where_params.keys
|
56
|
+
keys_in_conflict = keys_in_conflict.select { |k| create_params[k] == where_params[k] }
|
57
|
+
unless keys_in_conflict.empty?
|
58
|
+
raise "where() and first_or_create() were given conflicting values " +
|
59
|
+
"on the following keys: #{keys_in_conflict.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
if create_params[:_type]
|
63
|
+
# We have to recreate the instance because we are given a _type in
|
64
|
+
# create_params specifying a subclass. We'll have to transfert the lock
|
65
|
+
# ownership to that new instance.
|
66
|
+
new_instance = model.model_from_attrs(create_params).new(where_params).tap do |i|
|
67
|
+
i.locked_keys_for_uniqueness = new_instance.locked_keys_for_uniqueness
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
new_instance.assign_attributes(create_params)
|
72
|
+
new_instance.__send__(options[:save_method])
|
73
|
+
return new_instance
|
74
|
+
ensure
|
75
|
+
new_instance.try(:unlock_unique_fields)
|
76
|
+
end
|
77
|
+
|
78
|
+
def extract_where_params()
|
79
|
+
where_clauses = finalized_criteria.options[:where_ast]
|
80
|
+
|
81
|
+
unless where_clauses.is_a?(NoBrainer::Criteria::Where::MultiOperator) &&
|
82
|
+
where_clauses.op == :and && where_clauses.clauses.size > 0 &&
|
83
|
+
where_clauses.clauses.all? do |c|
|
84
|
+
c.is_a?(NoBrainer::Criteria::Where::BinaryOperator) &&
|
85
|
+
c.op == :eq && c.key_modifier == :scalar
|
86
|
+
end
|
87
|
+
raise "Please use a query of the form `.where(...).first_or_create(...)'"
|
88
|
+
end
|
89
|
+
|
90
|
+
Hash[where_clauses.clauses.map do |c|
|
91
|
+
raise "You may not use nested hash queries with first_or.create()" if c.key_path.size > 1
|
92
|
+
[c.key_path.first.to_sym, c.value]
|
93
|
+
end]
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_uniqueness_validators_map
|
97
|
+
Hash[model.unique_validators
|
98
|
+
.flat_map { |validator| validator.attributes.map { |attr| [attr, validator] } }
|
99
|
+
.map { |f, validator| [[f, *validator.scope].map(&:to_sym).sort, validator] }]
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module NoBrainer::Criteria::Run
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
criteria_option :initial_run_options, :merge_with => :set_scalar
|
6
|
+
criteria_option :run_with, :merge_with => :merge_hash
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def run(&block)
|
12
|
+
return finalized_criteria.__send__(:run, &block) unless finalized?
|
13
|
+
ensure_same_run_option_context!
|
14
|
+
|
15
|
+
block ||= proc { to_rql }
|
16
|
+
NoBrainer.run(:criteria => self, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ensure_same_run_option_context!
|
20
|
+
return if @options[:initial_run_options].nil?
|
21
|
+
return if @options[:initial_run_options] == NoBrainer.current_run_options
|
22
|
+
|
23
|
+
raise "The current criteria cannot be executed as it was constructed in a different `run_with()' context\n" +
|
24
|
+
"Note: you may use `run_with()' directly in your query (e.g. Model.run_with(...).first)."
|
25
|
+
end
|
26
|
+
end
|
@@ -3,10 +3,6 @@ module NoBrainer::Criteria::Scope
|
|
3
3
|
|
4
4
|
included { criteria_option :use_default_scope, :merge_with => :set_scalar }
|
5
5
|
|
6
|
-
def scoped
|
7
|
-
chain(:use_default_scope => true)
|
8
|
-
end
|
9
|
-
|
10
6
|
def unscoped
|
11
7
|
chain(:use_default_scope => false)
|
12
8
|
end
|
@@ -17,21 +13,25 @@ module NoBrainer::Criteria::Scope
|
|
17
13
|
|
18
14
|
def method_missing(name, *args, &block)
|
19
15
|
return super unless self.model.respond_to?(name)
|
16
|
+
apply_named_scope(name, args, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def apply_named_scope(name, args, block)
|
20
22
|
criteria = self.model.method(name).call(*args, &block)
|
21
23
|
raise "#{name} did not return a criteria" unless criteria.is_a?(NoBrainer::Criteria)
|
22
24
|
merge(criteria)
|
23
25
|
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
def _apply_default_scope
|
27
|
+
def apply_default_scope
|
28
28
|
return self if @options[:use_default_scope] == false
|
29
29
|
(model.default_scopes.map(&:call).compact + [self]).reduce(:merge)
|
30
30
|
end
|
31
31
|
|
32
32
|
module ClassMethods
|
33
33
|
def _finalize_criteria(base)
|
34
|
-
super.__send__(:
|
34
|
+
super.__send__(:apply_default_scope)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -57,7 +57,7 @@ module NoBrainer::Criteria::Where
|
|
57
57
|
same_op_clauses, other_clauses = clauses.partition do |v|
|
58
58
|
v.is_a?(self.class) && (v.clauses.size == 1 || v.op == self.op)
|
59
59
|
end
|
60
|
-
simplified_clauses = other_clauses + same_op_clauses.
|
60
|
+
simplified_clauses = other_clauses + same_op_clauses.flat_map(&:clauses)
|
61
61
|
simplified_clauses = BinaryOperator.simplify_clauses(op, simplified_clauses.uniq)
|
62
62
|
self.class.new(op, simplified_clauses)
|
63
63
|
end
|
@@ -70,7 +70,7 @@ module NoBrainer::Criteria::Where
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
class BinaryOperator < Struct.new(:
|
73
|
+
class BinaryOperator < Struct.new(:key_path, :key_modifier, :op, :value, :model, :casted_values)
|
74
74
|
def self.get_candidate_clauses(clauses, *types)
|
75
75
|
clauses.select { |c| c.is_a?(self) && types.include?(c.op) }
|
76
76
|
end
|
@@ -79,10 +79,10 @@ module NoBrainer::Criteria::Where
|
|
79
79
|
# This code assumes that simplfy() has already been called on all clauses.
|
80
80
|
if op == :or
|
81
81
|
eq_clauses = get_candidate_clauses(ast_clauses, :in, :eq)
|
82
|
-
new_clauses = eq_clauses.group_by { |c| [c.
|
82
|
+
new_clauses = eq_clauses.group_by { |c| [c.key_path, c.key_modifier] }.map do |(key_path, key_modifier), clauses|
|
83
83
|
if key_modifier.in?([:scalar, :any]) && clauses.size > 1
|
84
|
-
values = clauses.
|
85
|
-
[BinaryOperator.new(
|
84
|
+
values = clauses.flat_map { |c| c.op == :in ? c.value : [c.value] }.uniq
|
85
|
+
[BinaryOperator.new(key_path, key_modifier, :in, values, clauses.first.model, true)]
|
86
86
|
else
|
87
87
|
clauses
|
88
88
|
end
|
@@ -97,7 +97,7 @@ module NoBrainer::Criteria::Where
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def simplify
|
100
|
-
|
100
|
+
new_key_path = cast_key_path(key_path)
|
101
101
|
new_op, new_value = case op
|
102
102
|
when :in then
|
103
103
|
case value
|
@@ -111,11 +111,14 @@ module NoBrainer::Criteria::Where
|
|
111
111
|
[op, cast_value(value)]
|
112
112
|
else [op, cast_value(value)]
|
113
113
|
end
|
114
|
-
BinaryOperator.new(
|
114
|
+
BinaryOperator.new(new_key_path, key_modifier, new_op, new_value, model, true)
|
115
115
|
end
|
116
116
|
|
117
117
|
def to_rql(doc)
|
118
|
-
|
118
|
+
key_path = [model.lookup_field_alias(self.key_path.first), *self.key_path[1..-1]]
|
119
|
+
|
120
|
+
doc = key_path[0..-2].reduce(doc) { |d,k| d[k] }
|
121
|
+
key = key_path.last
|
119
122
|
|
120
123
|
case key_modifier
|
121
124
|
when :scalar then
|
@@ -148,10 +151,8 @@ module NoBrainer::Criteria::Where
|
|
148
151
|
private
|
149
152
|
|
150
153
|
def association
|
151
|
-
|
152
|
-
|
153
|
-
# collect symbols.
|
154
|
-
@association ||= [model.association_metadata[key.to_sym]]
|
154
|
+
return nil if key_path.size > 1
|
155
|
+
@association ||= [model.association_metadata[key_path.first]]
|
155
156
|
@association.first
|
156
157
|
end
|
157
158
|
|
@@ -162,7 +163,7 @@ module NoBrainer::Criteria::Where
|
|
162
163
|
when NoBrainer::Document::Association::BelongsTo::Metadata
|
163
164
|
target_model = association.target_model
|
164
165
|
unless value.is_a?(target_model)
|
165
|
-
opts = { :model => model, :attr_name =>
|
166
|
+
opts = { :model => model, :attr_name => key_path.first, :type => target_model, :value => value }
|
166
167
|
raise NoBrainer::Error::InvalidType.new(opts)
|
167
168
|
end
|
168
169
|
value.pk_value
|
@@ -173,38 +174,43 @@ module NoBrainer::Criteria::Where
|
|
173
174
|
raise "Use a geo object with `intersects`" unless value.is_a?(NoBrainer::Geo::Base)
|
174
175
|
value
|
175
176
|
when :near
|
177
|
+
# TODO enforce key is a geo type
|
176
178
|
case value
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
end
|
188
|
-
{ :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] }
|
179
|
+
when Hash
|
180
|
+
options = NoBrainer::Geo::Base.normalize_geo_options(value)
|
181
|
+
|
182
|
+
options[:radius] = options.delete(:max_distance) if options[:max_distance]
|
183
|
+
options[:radius] = options.delete(:max_dist) if options[:max_dist]
|
184
|
+
options[:center] = options.delete(:point) if options[:point]
|
185
|
+
|
186
|
+
unless options[:circle]
|
187
|
+
unless options[:center] && options[:radius]
|
188
|
+
raise "`near' takes something like {:center => P, :radius => d}"
|
189
189
|
end
|
190
|
-
|
191
|
-
|
190
|
+
{ :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] }
|
191
|
+
end
|
192
|
+
when NoBrainer::Geo::Circle then { :circle => value }
|
193
|
+
else raise "Incorrect use of `near': rvalue must be a hash or a circle"
|
192
194
|
end
|
193
195
|
else
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
196
|
+
# 1) Box value in array if we have an any/all modifier
|
197
|
+
# 2) Box value in hash if we have a nested query.
|
198
|
+
value = [value] if key_modifier.in?([:any, :all])
|
199
|
+
value_hash = key_path.reverse.reduce(value) { |v,k| {k => v} }
|
200
|
+
value = model.cast_user_to_db_for(*value_hash.first)
|
201
|
+
value = key_path[1..-1].reduce(value) { |h,k| h[k] }
|
202
|
+
value = value.first if key_modifier.in?([:any, :all])
|
203
|
+
value
|
198
204
|
end
|
199
205
|
end
|
200
206
|
end
|
201
207
|
|
202
|
-
def
|
208
|
+
def cast_key_path(key)
|
203
209
|
return key if casted_values
|
204
210
|
|
205
211
|
case association
|
206
|
-
when NoBrainer::Document::Association::BelongsTo::Metadata then association.foreign_key
|
207
|
-
else model.ensure_valid_key!(
|
212
|
+
when NoBrainer::Document::Association::BelongsTo::Metadata then [association.foreign_key]
|
213
|
+
else model.ensure_valid_key!(key_path.first); key_path
|
208
214
|
end
|
209
215
|
end
|
210
216
|
end
|
@@ -240,49 +246,53 @@ module NoBrainer::Criteria::Where
|
|
240
246
|
end
|
241
247
|
end
|
242
248
|
|
243
|
-
def parse_clause(clause)
|
249
|
+
def parse_clause(clause, options={})
|
244
250
|
clause = sanitize_for_mass_assignment(clause)
|
245
251
|
case clause
|
246
|
-
when Array then MultiOperator.new(:and, clause.map { |c| parse_clause(c) })
|
247
|
-
when Hash then MultiOperator.new(:and, clause.map { |k,v| parse_clause_stub(k,v) })
|
252
|
+
when Array then MultiOperator.new(:and, clause.map { |c| parse_clause(c, options) })
|
253
|
+
when Hash then MultiOperator.new(:and, clause.map { |k,v| parse_clause_stub(k, v, options) })
|
248
254
|
when Proc then Lambda.new(clause)
|
249
255
|
when Symbol::Decoration
|
250
256
|
case clause.args.size
|
251
|
-
when 1 then parse_clause_stub(clause, clause.args.first)
|
257
|
+
when 1 then parse_clause_stub(clause, clause.args.first, options)
|
252
258
|
else raise "Invalid argument: #{clause}"
|
253
259
|
end
|
254
260
|
else raise "Invalid clause: #{clause}"
|
255
261
|
end
|
256
262
|
end
|
257
263
|
|
258
|
-
def parse_clause_stub(key, value)
|
264
|
+
def parse_clause_stub(key, value, options={})
|
259
265
|
case key
|
260
|
-
when :and then parse_multi_value(:and, value)
|
261
|
-
when :or then parse_multi_value(:or, value)
|
262
|
-
when :_and then parse_multi_value(:and, value,
|
263
|
-
when :_or then parse_multi_value(:or, value,
|
264
|
-
when :not then UnaryOperator.new(:not, parse_clause(value))
|
265
|
-
when String, Symbol then
|
266
|
+
when :and then parse_multi_value(:and, value, false, options)
|
267
|
+
when :or then parse_multi_value(:or, value, false, options)
|
268
|
+
when :_and then parse_multi_value(:and, value, true, options)
|
269
|
+
when :_or then parse_multi_value(:or, value, true, options)
|
270
|
+
when :not then UnaryOperator.new(:not, parse_clause(value, options))
|
271
|
+
when String, Symbol then
|
272
|
+
case value
|
273
|
+
when Hash then parse_clause(value, options.merge(:nested_prefix => (options[:nested_prefix] || []) + [key.to_sym]))
|
274
|
+
else instantiate_binary_op(key.to_sym, :eq, value, options)
|
275
|
+
end
|
266
276
|
when Symbol::Decoration then
|
267
277
|
case key.decorator
|
268
|
-
when :any, :all, :not then instantiate_binary_op(key, :eq, value)
|
269
|
-
when :gte then instantiate_binary_op(key.symbol, :ge, value)
|
270
|
-
when :lte then instantiate_binary_op(key.symbol, :le, value)
|
271
|
-
else instantiate_binary_op(key.symbol, key.decorator, value)
|
278
|
+
when :any, :all, :not then instantiate_binary_op(key, :eq, value, options)
|
279
|
+
when :gte then instantiate_binary_op(key.symbol, :ge, value, options)
|
280
|
+
when :lte then instantiate_binary_op(key.symbol, :le, value, options)
|
281
|
+
else instantiate_binary_op(key.symbol, key.decorator, value, options)
|
272
282
|
end
|
273
283
|
else raise "Invalid key: #{key}"
|
274
284
|
end
|
275
285
|
end
|
276
286
|
|
277
|
-
def parse_multi_value(op, value, options={})
|
287
|
+
def parse_multi_value(op, value, multi_safe, options={})
|
278
288
|
raise "The `#{op}' operator takes an array as argument" unless value.is_a?(Array)
|
279
|
-
if value.size == 1 && value.first.is_a?(Hash) && !
|
289
|
+
if value.size == 1 && value.first.is_a?(Hash) && !multi_safe
|
280
290
|
raise "The `#{op}' operator was provided an array with a single hash element.\n" +
|
281
291
|
"In Ruby, [:a => :b, :c => :d] means [{:a => :b, :c => :d}] which is not the same as [{:a => :b}, {:c => :d}].\n" +
|
282
292
|
"To prevent mistakes, the former construct is prohibited as you probably mean the latter.\n" +
|
283
293
|
"However, if you know what you are doing, you can use the `_#{op}' operator instead."
|
284
294
|
end
|
285
|
-
MultiOperator.new(op, value.map { |v| parse_clause(v) })
|
295
|
+
MultiOperator.new(op, value.map { |v| parse_clause(v, options) })
|
286
296
|
end
|
287
297
|
|
288
298
|
def translate_regexp_to_re2_syntax(value)
|
@@ -298,21 +308,22 @@ module NoBrainer::Criteria::Where
|
|
298
308
|
"(?#{flags})#{value.source}"
|
299
309
|
end
|
300
310
|
|
301
|
-
def instantiate_binary_op(key, op, value)
|
311
|
+
def instantiate_binary_op(key, op, value, options={})
|
302
312
|
op, value = case value
|
303
313
|
when Range then [:between, value]
|
304
314
|
when Regexp then [:match, translate_regexp_to_re2_syntax(value)]
|
305
315
|
else [:eq, value]
|
306
316
|
end if op == :eq
|
307
317
|
|
318
|
+
nested_prefix = options[:nested_prefix] || []
|
308
319
|
case key
|
309
320
|
when Symbol::Decoration
|
310
321
|
raise "Use only one .not, .all or .any modifiers in the query" if key.symbol.is_a?(Symbol::Decoration)
|
311
322
|
case key.decorator
|
312
|
-
when :any, :all then BinaryOperator.new(key.symbol, key.decorator, op, value, self.model)
|
313
|
-
when :not then UnaryOperator.new(:not, BinaryOperator.new(key.symbol, :scalar, op, value, self.model))
|
323
|
+
when :any, :all then BinaryOperator.new(nested_prefix + [key.symbol], key.decorator, op, value, self.model)
|
324
|
+
when :not then UnaryOperator.new(:not, BinaryOperator.new(nested_prefix + [key.symbol], :scalar, op, value, self.model))
|
314
325
|
end
|
315
|
-
else BinaryOperator.new(key, :scalar, op, value, self.model)
|
326
|
+
else BinaryOperator.new(nested_prefix + [key], :scalar, op, value, self.model)
|
316
327
|
end
|
317
328
|
end
|
318
329
|
|
@@ -353,9 +364,9 @@ module NoBrainer::Criteria::Where
|
|
353
364
|
clauses = get_candidate_clauses(:eq, :in, :between, :near, :intersects)
|
354
365
|
return nil unless clauses.present?
|
355
366
|
|
356
|
-
usable_indexes = Hash[get_usable_indexes.map { |i| [i.name, i] }]
|
367
|
+
usable_indexes = Hash[get_usable_indexes.map { |i| [[i.name], i] }]
|
357
368
|
clauses.map do |clause|
|
358
|
-
index = usable_indexes[clause.
|
369
|
+
index = usable_indexes[clause.key_path]
|
359
370
|
next unless index && clause.compatible_with_index?(index)
|
360
371
|
next unless index.geo == [:near, :intersects].include?(clause.op)
|
361
372
|
|
@@ -377,11 +388,11 @@ module NoBrainer::Criteria::Where
|
|
377
388
|
end
|
378
389
|
|
379
390
|
def find_strategy_compound
|
380
|
-
clauses = Hash[get_candidate_clauses(:eq).map { |c| [c.
|
391
|
+
clauses = Hash[get_candidate_clauses(:eq).map { |c| [c.key_path, c] }]
|
381
392
|
return nil unless clauses.present?
|
382
393
|
|
383
394
|
get_usable_indexes(:kind => :compound, :geo => false, :multi => false).each do |index|
|
384
|
-
indexed_clauses = index.what.map { |field| clauses[field] }
|
395
|
+
indexed_clauses = index.what.map { |field| clauses[[field]] }
|
385
396
|
next unless indexed_clauses.all? { |c| c.try(:compatible_with_index?, index) }
|
386
397
|
|
387
398
|
return IndexStrategy.new(self, ast, indexed_clauses, index, :get_all, [indexed_clauses.map(&:value)])
|
@@ -390,11 +401,11 @@ module NoBrainer::Criteria::Where
|
|
390
401
|
end
|
391
402
|
|
392
403
|
def find_strategy_hidden_between
|
393
|
-
clauses = get_candidate_clauses(:gt, :ge, :lt, :le).group_by(&:
|
404
|
+
clauses = get_candidate_clauses(:gt, :ge, :lt, :le).group_by(&:key_path)
|
394
405
|
return nil unless clauses.present?
|
395
406
|
|
396
407
|
get_usable_indexes(:geo => false).each do |index|
|
397
|
-
matched_clauses = clauses[index.name].try(:select) { |c| c.compatible_with_index?(index) }
|
408
|
+
matched_clauses = clauses[[index.name]].try(:select) { |c| c.compatible_with_index?(index) }
|
398
409
|
next unless matched_clauses.present?
|
399
410
|
|
400
411
|
op_clauses = Hash[matched_clauses.map { |c| [c.op, c] }]
|
data/lib/no_brainer/criteria.rb
CHANGED
@@ -2,8 +2,8 @@ require 'rethinkdb'
|
|
2
2
|
|
3
3
|
class NoBrainer::Criteria
|
4
4
|
extend NoBrainer::Autoload
|
5
|
-
autoload_and_include :Core, :Raw, :AfterFind, :Where, :OrderBy,
|
6
|
-
:Pluck, :Count, :Delete, :Enumerable, :
|
7
|
-
:
|
8
|
-
:
|
5
|
+
autoload_and_include :Core, :Run, :Raw, :Scope, :AfterFind, :Where, :OrderBy,
|
6
|
+
:Limit, :Pluck, :Count, :Delete, :Enumerable, :Find,
|
7
|
+
:First, :FirstOrCreate, :Aggregate, :EagerLoad, :Update,
|
8
|
+
:Cache, :Index, :Extend
|
9
9
|
end
|
@@ -17,7 +17,7 @@ module NoBrainer::Document::Criteria
|
|
17
17
|
:raw, # Raw
|
18
18
|
:limit, :offset, :skip, # Limit
|
19
19
|
:order_by, :reverse_order, :without_ordering, :order_by_indexed?, :order_by_index_name, # OrderBy
|
20
|
-
:
|
20
|
+
:unscoped, # Scope
|
21
21
|
:where, :where_indexed?, :where_index_name, :where_index_type, # Where
|
22
22
|
:with_index, :without_index, :used_index, # Index
|
23
23
|
:with_cache, :without_cache, # Cache
|
@@ -26,6 +26,7 @@ module NoBrainer::Document::Criteria
|
|
26
26
|
:preload, :eager_load, # EagerLoad
|
27
27
|
:each, :to_a, # Enumerable
|
28
28
|
:first, :last, :first!, :last!, :sample, # First
|
29
|
+
:first_or_create, :first_or_create!, # FirstOrCreate
|
29
30
|
:min, :max, :sum, :avg, # Aggregate
|
30
31
|
:update_all, :replace_all, # Update
|
31
32
|
:pluck, :without, :lazy_fetch, :without_plucking, # Pluck
|
@@ -33,7 +34,8 @@ module NoBrainer::Document::Criteria
|
|
33
34
|
:to => :all
|
34
35
|
|
35
36
|
def all
|
36
|
-
NoBrainer::Criteria.new(:
|
37
|
+
NoBrainer::Criteria.new(:initial_run_options => NoBrainer.current_run_options,
|
38
|
+
:model => self)
|
37
39
|
end
|
38
40
|
|
39
41
|
def scope(name, criteria=nil, &block)
|
@@ -59,10 +59,8 @@ class NoBrainer::Document::Index::Index < Struct.new(
|
|
59
59
|
NoBrainer::RQL.reset_lambda_var_counter
|
60
60
|
NoBrainer.run(model.rql_table.index_create(aliased_name, opt, &rql_proc))
|
61
61
|
|
62
|
-
MetaStore.
|
63
|
-
|
64
|
-
:rql_function => serialized_rql_proc)
|
65
|
-
end
|
62
|
+
MetaStore.create(:table_name => model.table_name, :index_name => aliased_name,
|
63
|
+
:rql_function => serialized_rql_proc)
|
66
64
|
end
|
67
65
|
|
68
66
|
def delete(options={})
|
@@ -70,9 +68,7 @@ class NoBrainer::Document::Index::Index < Struct.new(
|
|
70
68
|
|
71
69
|
NoBrainer.run(model.rql_table.index_drop(aliased_name))
|
72
70
|
|
73
|
-
MetaStore.
|
74
|
-
MetaStore.where(:table_name => model.table_name, :index_name => aliased_name).delete_all
|
75
|
-
end
|
71
|
+
MetaStore.where(:table_name => model.table_name, :index_name => aliased_name).delete_all
|
76
72
|
end
|
77
73
|
|
78
74
|
def update(wanted_index, options={})
|
@@ -6,8 +6,7 @@ class NoBrainer::Document::Index::MetaStore
|
|
6
6
|
|
7
7
|
default_scope ->{ order_by(:created_at) }
|
8
8
|
|
9
|
-
store_in :
|
10
|
-
:table => 'nobrainer_index_meta'
|
9
|
+
store_in :table => 'nobrainer_index_meta'
|
11
10
|
|
12
11
|
field :table_name, :type => String, :required => true
|
13
12
|
field :index_name, :type => String, :required => true
|
@@ -20,12 +19,4 @@ class NoBrainer::Document::Index::MetaStore
|
|
20
19
|
def rql_function
|
21
20
|
JSON.load(super)
|
22
21
|
end
|
23
|
-
|
24
|
-
def self.on(db_name, &block)
|
25
|
-
old_db_name = Thread.current[:nobrainer_meta_store_db]
|
26
|
-
Thread.current[:nobrainer_meta_store_db] = db_name
|
27
|
-
block.call
|
28
|
-
ensure
|
29
|
-
Thread.current[:nobrainer_meta_store_db] = old_db_name
|
30
|
-
end
|
31
22
|
end
|
@@ -8,9 +8,8 @@ class NoBrainer::Document::Index::Synchronizer
|
|
8
8
|
end]
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
@meta_store ||=
|
13
|
-
@meta_store[db_name] ||= MetaStore.on(db_name) { MetaStore.all.to_a }
|
11
|
+
def meta_store
|
12
|
+
@meta_store ||= MetaStore.to_a
|
14
13
|
end
|
15
14
|
|
16
15
|
class Op < Struct.new(:index, :op, :args)
|
@@ -21,8 +20,7 @@ class NoBrainer::Document::Index::Synchronizer
|
|
21
20
|
|
22
21
|
def _generate_plan_for(model, wanted_indexes)
|
23
22
|
current_indexes = NoBrainer.run(model.rql_table.index_status).map do |s|
|
24
|
-
meta =
|
25
|
-
.select { |i| i.table_name == model.table_name && i.index_name == s['index'] }.last
|
23
|
+
meta = meta_store.select { |i| i.table_name == model.table_name && i.index_name == s['index'] }.last
|
26
24
|
Index.new(model, s['index'], s['index'], nil, nil, nil, s['geo'], s['multi'], meta)
|
27
25
|
end
|
28
26
|
|
@@ -46,7 +44,7 @@ class NoBrainer::Document::Index::Synchronizer
|
|
46
44
|
end
|
47
45
|
|
48
46
|
def generate_plan
|
49
|
-
@models_indexes_map.
|
47
|
+
@models_indexes_map.flat_map { |model, indexes| _generate_plan_for(model, indexes) }
|
50
48
|
end
|
51
49
|
|
52
50
|
def sync_indexes(options={})
|
@@ -37,7 +37,11 @@ module NoBrainer::Document::Polymorphic
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def model_from_attrs(attrs)
|
40
|
-
attrs['_type']
|
40
|
+
class_name = attrs['_type'] || attrs[:_type]
|
41
|
+
return root_class unless class_name
|
42
|
+
class_name.to_s.constantize.tap { |cls| raise NameError unless cls <= self }
|
43
|
+
rescue NameError
|
44
|
+
raise NoBrainer::Error::InvalidPolymorphicType, "Invalid polymorphic class: `#{class_name}' is not a `#{self}'"
|
41
45
|
end
|
42
46
|
|
43
47
|
def all
|
@@ -11,12 +11,13 @@ module NoBrainer::Document::StoreIn
|
|
11
11
|
module ClassMethods
|
12
12
|
def store_in(options)
|
13
13
|
raise "store_in() must be called on the parent class" unless is_root_class?
|
14
|
-
self.store_in_options.merge!(options)
|
15
|
-
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
if options[:database] || options[:db]
|
16
|
+
STDERR.puts "[NoBrainer] `store_in(db: ...)' has been removed. Use `run_with(db: ...)' instead. Sorry."
|
17
|
+
end
|
18
|
+
|
19
|
+
options.assert_valid_keys(:table)
|
20
|
+
self.store_in_options.merge!(options)
|
20
21
|
end
|
21
22
|
|
22
23
|
def table_name
|
@@ -26,10 +27,7 @@ module NoBrainer::Document::StoreIn
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def rql_table
|
29
|
-
|
30
|
-
rql = RethinkDB::RQL.new
|
31
|
-
rql = rql.db(db) if db
|
32
|
-
rql.table(table_name)
|
30
|
+
RethinkDB::RQL.new.table(table_name)
|
33
31
|
end
|
34
32
|
end
|
35
33
|
end
|
@@ -8,41 +8,38 @@ module NoBrainer::Document::Validation::Uniqueness
|
|
8
8
|
unlock_unique_fields
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
13
|
-
[
|
14
|
-
self.class.table_name, field, value.empty? ? 'nil' : value].join(':')
|
11
|
+
def _lock_for_uniqueness_once(key)
|
12
|
+
@locked_keys_for_uniqueness ||= {}
|
13
|
+
@locked_keys_for_uniqueness[key] ||= NoBrainer::Config.distributed_lock_class.new(key).tap(&:lock)
|
15
14
|
end
|
16
15
|
|
17
|
-
def
|
18
|
-
|
16
|
+
def unlock_unique_fields
|
17
|
+
@locked_keys_for_uniqueness.to_h.values.each(&:unlock)
|
18
|
+
@locked_keys_for_uniqueness = {}
|
19
|
+
end
|
19
20
|
|
21
|
+
def lock_unique_fields
|
20
22
|
self.class.unique_validators
|
21
|
-
.
|
22
|
-
.flatten(1)
|
23
|
+
.flat_map { |validator| validator.attributes.map { |attr| [attr, validator] } }
|
23
24
|
.select { |f, validator| validator.should_validate_field?(self, f) }
|
24
|
-
.map { |f,
|
25
|
-
.
|
26
|
-
.
|
27
|
-
.each do |key|
|
28
|
-
lock = NoBrainer::Config.distributed_lock_class.new(key)
|
29
|
-
lock.lock
|
30
|
-
@locked_unique_fields ||= []
|
31
|
-
@locked_unique_fields << lock
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def unlock_unique_fields
|
36
|
-
return unless @locked_unique_fields
|
37
|
-
@locked_unique_fields.pop.unlock until @locked_unique_fields.empty?
|
25
|
+
.map { |f, validator| [f, *validator.scope].map { |k| [k, read_attribute(k)] } }
|
26
|
+
.map { |params| self.class._uniqueness_key_name_from_params(params) }
|
27
|
+
.sort.each { |key| _lock_for_uniqueness_once(key) }
|
38
28
|
end
|
39
29
|
|
40
30
|
included do
|
41
31
|
singleton_class.send(:attr_accessor, :unique_validators)
|
42
32
|
self.unique_validators = []
|
33
|
+
attr_accessor :locked_keys_for_uniqueness
|
43
34
|
end
|
44
35
|
|
45
36
|
module ClassMethods
|
37
|
+
def _uniqueness_key_name_from_params(params)
|
38
|
+
['uniq', NoBrainer.current_db, self.table_name,
|
39
|
+
*params.map { |k,v| [k.to_s, (v = v.to_s; v.empty? ? 'nil' : v)] }.sort
|
40
|
+
].join(':')
|
41
|
+
end
|
42
|
+
|
46
43
|
def validates_uniqueness_of(*attr_names)
|
47
44
|
validates_with(UniquenessValidator, _merge_attributes(attr_names))
|
48
45
|
end
|
@@ -54,14 +51,14 @@ module NoBrainer::Document::Validation::Uniqueness
|
|
54
51
|
end
|
55
52
|
|
56
53
|
class UniquenessValidator < ActiveModel::EachValidator
|
57
|
-
attr_accessor :scope
|
54
|
+
attr_accessor :scope, :model
|
58
55
|
|
59
56
|
def initialize(options={})
|
60
57
|
super
|
61
|
-
model = options[:class]
|
62
|
-
self.scope = [*options[:scope]]
|
63
|
-
([model] + model.descendants).each do |
|
64
|
-
|
58
|
+
self.model = options[:class]
|
59
|
+
self.scope = [*options[:scope]].map(&:to_sym)
|
60
|
+
([model] + model.descendants).each do |subclass|
|
61
|
+
subclass.unique_validators << self
|
65
62
|
end
|
66
63
|
end
|
67
64
|
|
@@ -70,7 +67,7 @@ module NoBrainer::Document::Validation::Uniqueness
|
|
70
67
|
end
|
71
68
|
|
72
69
|
def validate_each(doc, attr, value)
|
73
|
-
criteria =
|
70
|
+
criteria = self.model.unscoped.where(attr => value)
|
74
71
|
criteria = apply_scopes(criteria, doc)
|
75
72
|
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
76
73
|
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless criteria.empty?
|
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/lock.rb
CHANGED
@@ -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
|
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
|
@@ -13,9 +13,8 @@ class NoBrainer::Profiler::Logger
|
|
13
13
|
msg_duration = "[#{msg_duration}ms] "
|
14
14
|
|
15
15
|
env[:query_type] = NoBrainer::RQL.type_of(env[:query])
|
16
|
-
env[:custom_db_name] = env[:db_name] if env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
|
17
16
|
|
18
|
-
msg_db = "[#{env[:
|
17
|
+
msg_db = "[#{env[:options][:db]}] " if env[:options][:db]
|
19
18
|
msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
|
20
19
|
|
21
20
|
msg_exception = "#{env[:exception].class} #{env[:exception].message.split("\n").first}" if env[:exception]
|
@@ -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.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
|
|
@@ -22,7 +22,6 @@ class NoBrainer::QueryRunner::Profiler < NoBrainer::QueryRunner::Middleware
|
|
22
22
|
|
23
23
|
env[:model] = env[:criteria] && env[:criteria].model
|
24
24
|
env[:query_type] = NoBrainer::RQL.type_of(env[:query])
|
25
|
-
env[:custom_db_name] = env[:db_name] if env[:db_name] && env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
|
26
25
|
|
27
26
|
NoBrainer::Profiler.registered_profilers.each do |profiler|
|
28
27
|
begin
|
@@ -1,35 +1,55 @@
|
|
1
1
|
class NoBrainer::QueryRunner::RunOptions < NoBrainer::QueryRunner::Middleware
|
2
|
-
# XXX NoBrainer::Database#drop() uses Thread.current[:nobrainer_options]
|
3
|
-
|
4
2
|
def self.with_database(db_name, &block)
|
3
|
+
STDERR.puts "[NoBrainer] `with_database()' is deprecated, please use `with(db: ...)' instead"
|
5
4
|
with(:db => db_name, &block)
|
6
5
|
end
|
7
6
|
|
8
7
|
def self.with(options={}, &block)
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
STDERR.puts "[NoBrainer] `with(...)' is deprecated, please use `run_with(...)' instead"
|
9
|
+
run_with(options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.current_run_options
|
13
|
+
Thread.current[:nobrainer_run_with] || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.run_with(options={}, &block)
|
17
|
+
options = options.symbolize_keys
|
18
|
+
|
19
|
+
if options[:database]
|
20
|
+
STDERR.puts "[NoBrainer] `run_with(database: ...)' is deprecated, please use `run_with(db: ...)' instead"
|
21
|
+
options[:db] = options.delete(:database)
|
22
|
+
end
|
23
|
+
|
24
|
+
old_options = Thread.current[:nobrainer_run_with]
|
25
|
+
Thread.current[:nobrainer_run_with] = (old_options || {}).merge(options)
|
26
|
+
block.call
|
12
27
|
ensure
|
13
|
-
Thread.current[:
|
28
|
+
Thread.current[:nobrainer_run_with] = old_options
|
14
29
|
end
|
15
30
|
|
16
31
|
def call(env)
|
17
|
-
env[:options].symbolize_keys
|
18
|
-
|
19
|
-
env[:options].reverse_merge!(Thread.current[:nobrainer_options])
|
20
|
-
end
|
32
|
+
options = env[:options].symbolize_keys
|
33
|
+
options = self.class.current_run_options.merge(options)
|
21
34
|
|
22
35
|
if NoBrainer::Config.durability.to_s != 'hard'
|
23
|
-
|
36
|
+
options[:durability] ||= NoBrainer::Config.durability
|
24
37
|
end
|
25
38
|
|
26
|
-
|
27
|
-
|
28
|
-
|
39
|
+
options[:db] = options[:db].to_s if options[:db]
|
40
|
+
if options[:db].blank? || options[:db] == NoBrainer.default_db
|
41
|
+
options.delete(:db)
|
29
42
|
end
|
30
43
|
|
31
|
-
env[:criteria] =
|
44
|
+
env[:criteria] = options.delete(:criteria)
|
45
|
+
|
46
|
+
if options[:profile] && env[:criteria].try(:raw?) == false
|
47
|
+
STDERR.puts "[NoBrainer]"
|
48
|
+
STDERR.puts "[NoBrainer]\e[1;31m Please use `.raw' in your criteria when profiling\e[0m"
|
49
|
+
STDERR.puts "[NoBrainer]"
|
50
|
+
end
|
32
51
|
|
52
|
+
env[:options] = options
|
33
53
|
@runner.call(env)
|
34
54
|
end
|
35
55
|
end
|
@@ -15,24 +15,24 @@ class NoBrainer::QueryRunner::TableOnDemand < NoBrainer::QueryRunner::Middleware
|
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
-
def auto_create_table(env,
|
18
|
+
def auto_create_table(env, db_name, table_name)
|
19
19
|
model ||= NoBrainer::Document::Core._all.select { |m| m.table_name == table_name }.first
|
20
20
|
model ||= NoBrainer::Document::Core._all_nobrainer.select { |m| m.table_name == table_name }.first
|
21
21
|
|
22
22
|
if model.nil?
|
23
|
-
raise "Auto table creation is not working for `#{
|
23
|
+
raise "Auto table creation is not working for `#{db_name}.#{table_name}` -- Can't find the corresponding model."
|
24
24
|
end
|
25
25
|
|
26
|
-
if env[:last_auto_create_table] == [
|
27
|
-
raise "Auto table creation is not working for `#{
|
26
|
+
if env[:last_auto_create_table] == [db_name, table_name]
|
27
|
+
raise "Auto table creation is not working for `#{db_name}.#{table_name}`"
|
28
28
|
end
|
29
|
-
env[:last_auto_create_table] = [
|
29
|
+
env[:last_auto_create_table] = [db_name, table_name]
|
30
30
|
|
31
|
-
NoBrainer.
|
31
|
+
NoBrainer.run_with(:db => db_name) do
|
32
32
|
NoBrainer.table_create(table_name, :primary_key => model.lookup_field_alias(model.pk_name))
|
33
33
|
end
|
34
34
|
rescue RuntimeError => e
|
35
35
|
# We might have raced with another table create
|
36
|
-
raise unless e.message =~ /Table `#{
|
36
|
+
raise unless e.message =~ /Table `#{db_name}\.#{table_name}` already exists/
|
37
37
|
end
|
38
38
|
end
|
data/lib/nobrainer.rb
CHANGED
@@ -23,12 +23,14 @@ module NoBrainer
|
|
23
23
|
|
24
24
|
delegate :db_create, :db_drop, :db_list,
|
25
25
|
:table_create, :table_drop, :table_list,
|
26
|
-
:drop!, :purge!, :to => :connection
|
26
|
+
:drop!, :purge!, :default_db, :current_db, :to => :connection
|
27
27
|
|
28
|
-
delegate :configure, :logger,
|
29
|
-
delegate :run,
|
30
|
-
delegate :sync_indexes,
|
31
|
-
delegate :
|
28
|
+
delegate :configure, :logger, :to => 'NoBrainer::Config'
|
29
|
+
delegate :run, :to => 'NoBrainer::QueryRunner'
|
30
|
+
delegate :sync_indexes, :to => 'NoBrainer::Document::Index::Synchronizer'
|
31
|
+
delegate :current_run_options, :run_with, :to => 'NoBrainer::QueryRunner::RunOptions'
|
32
|
+
|
33
|
+
delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions' # deprecated
|
32
34
|
|
33
35
|
def jruby?
|
34
36
|
RUBY_PLATFORM == 'java'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Viennot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rethinkdb
|
@@ -105,11 +105,13 @@ files:
|
|
105
105
|
- lib/no_brainer/criteria/extend.rb
|
106
106
|
- lib/no_brainer/criteria/find.rb
|
107
107
|
- lib/no_brainer/criteria/first.rb
|
108
|
+
- lib/no_brainer/criteria/first_or_create.rb
|
108
109
|
- lib/no_brainer/criteria/index.rb
|
109
110
|
- lib/no_brainer/criteria/limit.rb
|
110
111
|
- lib/no_brainer/criteria/order_by.rb
|
111
112
|
- lib/no_brainer/criteria/pluck.rb
|
112
113
|
- lib/no_brainer/criteria/raw.rb
|
114
|
+
- lib/no_brainer/criteria/run.rb
|
113
115
|
- lib/no_brainer/criteria/scope.rb
|
114
116
|
- lib/no_brainer/criteria/update.rb
|
115
117
|
- lib/no_brainer/criteria/where.rb
|
@@ -172,7 +174,6 @@ files:
|
|
172
174
|
- lib/no_brainer/loader.rb
|
173
175
|
- lib/no_brainer/locale/en.yml
|
174
176
|
- lib/no_brainer/lock.rb
|
175
|
-
- lib/no_brainer/logger.rb
|
176
177
|
- lib/no_brainer/profiler.rb
|
177
178
|
- lib/no_brainer/profiler/controller_runtime.rb
|
178
179
|
- lib/no_brainer/profiler/logger.rb
|
data/lib/no_brainer/logger.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
class NoBrainer::Logger
|
2
|
-
def on_query(env)
|
3
|
-
# env[:end_time] = Time.now
|
4
|
-
# env[:duration] = env[:end_time] - env[:start_time]
|
5
|
-
# env[:exception] = exception
|
6
|
-
# env[:query_type] = NoBrainer::RQL.type_of(env[:query])
|
7
|
-
|
8
|
-
not_indexed = env[:criteria] && env[:criteria].where_present? &&
|
9
|
-
!env[:criteria].where_indexed? &&
|
10
|
-
!env[:criteria].model.try(:perf_warnings_disabled)
|
11
|
-
|
12
|
-
level = exception ? Logger::ERROR :
|
13
|
-
not_indexed ? Logger::INFO : Logger::DEBUG
|
14
|
-
return if NoBrainer.logger.nil? || NoBrainer.logger.level > level
|
15
|
-
|
16
|
-
msg_duration = (duration * 1000.0).round(1).to_s
|
17
|
-
msg_duration = " " * [0, 6 - msg_duration.size].max + msg_duration
|
18
|
-
msg_duration = "[#{msg_duration}ms] "
|
19
|
-
|
20
|
-
msg_db = "[#{env[:db_name]}] " if env[:db_name] && env[:db_name].to_s != NoBrainer.connection.parsed_uri[:db]
|
21
|
-
msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
|
22
|
-
|
23
|
-
msg_exception = "#{exception.class} #{exception.message.split("\n").first}" if exception
|
24
|
-
msg_exception ||= "perf: filtering without using an index" if not_indexed
|
25
|
-
|
26
|
-
msg_last = nil
|
27
|
-
|
28
|
-
if NoBrainer::Config.colorize_logger
|
29
|
-
query_color = case NoBrainer::RQL.type_of(env[:query])
|
30
|
-
when :write then "\e[1;31m" # red
|
31
|
-
when :read then "\e[1;32m" # green
|
32
|
-
when :management then "\e[1;33m" # yellow
|
33
|
-
end
|
34
|
-
msg_duration = [query_color, msg_duration].join
|
35
|
-
msg_db = ["\e[0;34m", msg_db, query_color].join if msg_db
|
36
|
-
if msg_exception
|
37
|
-
exception_color = "\e[0;31m" if level == Logger::ERROR
|
38
|
-
msg_exception = ["\e[0;39m", " -- ", exception_color, msg_exception].compact.join
|
39
|
-
end
|
40
|
-
msg_last = "\e[0m"
|
41
|
-
end
|
42
|
-
|
43
|
-
msg = [msg_duration, msg_db, msg_query, msg_exception, msg_last].join
|
44
|
-
NoBrainer.logger.add(level, msg)
|
45
|
-
end
|
46
|
-
end
|