nobrainer 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|