nobrainer 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/no_brainer/autoload.rb +7 -1
- data/lib/no_brainer/config.rb +12 -2
- data/lib/no_brainer/connection.rb +2 -15
- data/lib/no_brainer/connection_manager.rb +4 -3
- data/lib/no_brainer/criteria/enumerable.rb +10 -1
- data/lib/no_brainer/criteria/first_or_create.rb +24 -7
- data/lib/no_brainer/document/aliases.rb +2 -2
- data/lib/no_brainer/document/association.rb +3 -0
- data/lib/no_brainer/document/attributes.rb +14 -23
- data/lib/no_brainer/document/dirty.rb +2 -2
- data/lib/no_brainer/document/index.rb +9 -4
- data/lib/no_brainer/document/lazy_fetch.rb +13 -13
- data/lib/no_brainer/document/primary_key.rb +17 -29
- data/lib/no_brainer/document/readonly.rb +10 -6
- data/lib/no_brainer/document/table_config.rb +17 -3
- data/lib/no_brainer/document/types.rb +19 -12
- data/lib/no_brainer/document/types/boolean.rb +12 -0
- data/lib/no_brainer/document/types/enum.rb +63 -0
- data/lib/no_brainer/document/types/time.rb +2 -1
- data/lib/no_brainer/document/validation/core.rb +1 -1
- data/lib/no_brainer/query_runner.rb +35 -17
- data/lib/no_brainer/query_runner/database_on_demand.rb +4 -1
- data/lib/no_brainer/query_runner/em_driver.rb +128 -0
- data/lib/no_brainer/query_runner/missing_index.rb +3 -5
- data/lib/no_brainer/query_runner/reconnect.rb +2 -49
- data/lib/no_brainer/query_runner/table_on_demand.rb +2 -1
- data/lib/no_brainer/railtie/database.rake +10 -13
- data/lib/no_brainer/rql.rb +9 -1
- data/lib/nobrainer.rb +11 -3
- data/lib/rails/generators/templates/nobrainer.rb +4 -8
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fbf539d3fd47eeec38c4ae88843297a7462efb5
|
4
|
+
data.tar.gz: e02bde6490ffc0eb21bf3fde637dc6a4dea0350d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 586f87afabedec2bc9716949298255c6456b7cbf9846a6b9e3090fe009cf481d68fd3eb852368a2658f1e4b59e432388f690cd2fce24606460229dbad1daf633
|
7
|
+
data.tar.gz: d55889b76d3a1e6dd48338fca51eb1ad636baa00bc20c21a992cf8e5e6d02a4f958f535dc95398a89c3695ceab87cf5ededbd915ac3b0f07f041dda16148e913
|
data/lib/no_brainer/autoload.rb
CHANGED
@@ -13,8 +13,14 @@ module NoBrainer::Autoload
|
|
13
13
|
super() { autoload(*constants) }
|
14
14
|
end
|
15
15
|
|
16
|
+
def eager_load!
|
17
|
+
super
|
18
|
+
@_autoloads.keys.map { |c| const_get(c) }
|
19
|
+
.each { |c| c.eager_load! if c.respond_to?(:eager_load!) }
|
20
|
+
end
|
21
|
+
|
16
22
|
def autoload_and_include(*constants)
|
17
|
-
|
23
|
+
eager_autoload(*constants)
|
18
24
|
constants.each { |constant| include const_get(constant) }
|
19
25
|
end
|
20
26
|
end
|
data/lib/no_brainer/config.rb
CHANGED
@@ -6,10 +6,10 @@ module NoBrainer::Config
|
|
6
6
|
:environment => { :default => ->{ default_environment } },
|
7
7
|
:rethinkdb_urls => { :default => ->{ [default_rethinkdb_url] } },
|
8
8
|
:ssl_options => { :default => ->{ nil } },
|
9
|
+
:driver => { :default => ->{ :regular }, :valid_values => [:regular, :em] },
|
9
10
|
:logger => { :default => ->{ default_logger } },
|
10
11
|
:colorize_logger => { :default => ->{ true }, :valid_values => [true, false] },
|
11
12
|
:warn_on_active_record => { :default => ->{ true }, :valid_values => [true, false] },
|
12
|
-
:max_retries_on_connection_failure => { :default => ->{ default_max_retries_on_connection_failure } },
|
13
13
|
:durability => { :default => ->{ default_durability }, :valid_values => [:hard, :soft] },
|
14
14
|
:table_options => { :default => ->{ {:shards => 1, :replicas => 1, :write_acks => :majority} },
|
15
15
|
:valid_keys => [:shards, :replicas, :primary_replica_tag, :write_acks, :durability] },
|
@@ -37,6 +37,11 @@ module NoBrainer::Config
|
|
37
37
|
STDERR.puts "[NoBrainer] The current behavior is now to always auto create tables"
|
38
38
|
end
|
39
39
|
|
40
|
+
def max_retries_on_connection_failure=(value)
|
41
|
+
STDERR.puts "[NoBrainer] config.max_retries_on_connection_failure has been removed."
|
42
|
+
STDERR.puts "[NoBrainer] Queries are no longer retried upon failures"
|
43
|
+
end
|
44
|
+
|
40
45
|
def apply_defaults
|
41
46
|
@applied_defaults_for = SETTINGS.keys.reject { |k| instance_variable_defined?("@#{k}") }
|
42
47
|
@applied_defaults_for.each { |k| __send__("#{k}=", SETTINGS[k][:default].call) }
|
@@ -53,6 +58,10 @@ module NoBrainer::Config
|
|
53
58
|
end
|
54
59
|
|
55
60
|
validate_urls
|
61
|
+
|
62
|
+
if driver == :em && per_thread_connection
|
63
|
+
raise "To use EventMachine, disable per_thread_connection"
|
64
|
+
end
|
56
65
|
end
|
57
66
|
|
58
67
|
def reset!
|
@@ -119,7 +128,7 @@ module NoBrainer::Config
|
|
119
128
|
def validate_urls
|
120
129
|
# This is not connecting, just validating the format.
|
121
130
|
dbs = rethinkdb_urls.compact.map { |url| NoBrainer::Connection.new(url).parsed_uri[:db] }.uniq
|
122
|
-
raise "Please specify
|
131
|
+
raise "Please specify the app_name and the environment, or a rethinkdb_url" if dbs.size == 0
|
123
132
|
raise "All the rethinkdb_urls must specify the same db name (instead of #{dbs.inspect})" if dbs.size != 1
|
124
133
|
end
|
125
134
|
|
@@ -132,6 +141,7 @@ module NoBrainer::Config
|
|
132
141
|
end
|
133
142
|
|
134
143
|
def default_max_retries_on_connection_failure
|
144
|
+
# TODO remove
|
135
145
|
dev_mode? ? 1 : 15
|
136
146
|
end
|
137
147
|
|
@@ -2,9 +2,10 @@ require 'rethinkdb'
|
|
2
2
|
require 'uri'
|
3
3
|
|
4
4
|
class NoBrainer::Connection
|
5
|
-
attr_accessor :parsed_uri
|
5
|
+
attr_accessor :parsed_uri, :orig_uri
|
6
6
|
|
7
7
|
def initialize(uri)
|
8
|
+
@orig_uri = uri
|
8
9
|
parse_uri(uri)
|
9
10
|
end
|
10
11
|
|
@@ -46,18 +47,4 @@ class NoBrainer::Connection
|
|
46
47
|
def current_db
|
47
48
|
NoBrainer.current_run_options.try(:[], :db) || default_db
|
48
49
|
end
|
49
|
-
|
50
|
-
def drop!
|
51
|
-
NoBrainer.run { |r| r.db_drop(current_db) }
|
52
|
-
end
|
53
|
-
|
54
|
-
# Note that truncating each table (purge!) is much faster than dropping the database (drop!)
|
55
|
-
def purge!
|
56
|
-
NoBrainer.run { |r| r.table_list }.each do |table_name|
|
57
|
-
# keeping the index meta store because indexes are not going away when purging
|
58
|
-
next if table_name == NoBrainer::Document::Index::MetaStore.table_name
|
59
|
-
NoBrainer.run { |r| r.table(table_name).delete }
|
60
|
-
end
|
61
|
-
true
|
62
|
-
end
|
63
50
|
end
|
@@ -62,11 +62,12 @@ module NoBrainer::ConnectionManager
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def _disconnect
|
65
|
-
self.current_connection.
|
66
|
-
|
65
|
+
c, self.current_connection = self.current_connection, nil
|
66
|
+
c.try(:close, :noreply_wait => false) rescue nil
|
67
67
|
end
|
68
68
|
|
69
69
|
def disconnect
|
70
|
+
return unless self.current_connection
|
70
71
|
synchronize { _disconnect }
|
71
72
|
end
|
72
73
|
|
@@ -74,7 +75,7 @@ module NoBrainer::ConnectionManager
|
|
74
75
|
synchronize do
|
75
76
|
@urls = nil
|
76
77
|
c = current_connection
|
77
|
-
_disconnect if c && !NoBrainer::Config.rethinkdb_urls.include?(c.
|
78
|
+
_disconnect if c && !NoBrainer::Config.rethinkdb_urls.include?(c.orig_uri)
|
78
79
|
end
|
79
80
|
end
|
80
81
|
end
|
@@ -3,7 +3,16 @@ module NoBrainer::Criteria::Enumerable
|
|
3
3
|
|
4
4
|
def each(options={}, &block)
|
5
5
|
return enum_for(:each, options) unless block
|
6
|
-
run.
|
6
|
+
run.tap { |cursor| @cursor = cursor }.each do |attrs|
|
7
|
+
return close if @close_cursor
|
8
|
+
block.call(instantiate_doc(attrs))
|
9
|
+
end
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
@close_cursor = true
|
15
|
+
@cursor.close if NoBrainer::Config.driver == :em
|
7
16
|
self
|
8
17
|
end
|
9
18
|
|
@@ -10,11 +10,11 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def upsert(attrs, save_options={})
|
13
|
-
_upsert(attrs, save_options.merge(:save_method => :save
|
13
|
+
_upsert(attrs, save_options.merge(:save_method => :save?, :update => true))
|
14
14
|
end
|
15
15
|
|
16
16
|
def upsert!(attrs, save_options={})
|
17
|
-
_upsert(attrs, save_options.merge(:save_method => :save
|
17
|
+
_upsert(attrs, save_options.merge(:save_method => :save!, :update => true))
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
@@ -22,15 +22,27 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
22
22
|
def _upsert(attrs, save_options)
|
23
23
|
attrs = attrs.symbolize_keys
|
24
24
|
unique_keys = get_model_unique_fields.detect { |keys| keys & attrs.keys == keys }
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
return where(attrs.slice(*unique_keys)).__send__(:_first_or_create, attrs, save_options) if unique_keys
|
26
|
+
|
27
|
+
# We can't do an upsert :( Let see if we can fail on a validator first...
|
28
|
+
instance = model.new(attrs)
|
29
|
+
unless instance.valid?
|
30
|
+
case save_options[:save_method]
|
31
|
+
when :save! then raise NoBrainer::Error::DocumentInvalid, instance
|
32
|
+
when :save? then return instance
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
raise "Could not find a uniqueness validator for the following keys: `#{attrs.keys.inspect}'."
|
28
37
|
end
|
29
38
|
|
30
39
|
def _first_or_create(create_params, save_options, &block)
|
31
40
|
raise "Cannot use .raw() with .first_or_create()" if raw?
|
32
41
|
raise "Use first_or_create() on the root class `#{model.root_class}'" unless model.is_root_class?
|
33
42
|
|
43
|
+
save_method = save_options.delete(:save_method)
|
44
|
+
should_update = save_options.delete(:update)
|
45
|
+
|
34
46
|
where_params = extract_where_params()
|
35
47
|
|
36
48
|
# Note that we are not matching a subset of the keys on the uniqueness
|
@@ -58,7 +70,13 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
58
70
|
new_instance._lock_for_uniqueness_once(lock_key_name)
|
59
71
|
|
60
72
|
old_instance = self.first
|
61
|
-
|
73
|
+
if old_instance
|
74
|
+
if should_update
|
75
|
+
old_instance.assign_attributes(create_params)
|
76
|
+
old_instance.__send__(save_method, save_options)
|
77
|
+
end
|
78
|
+
return old_instance
|
79
|
+
end
|
62
80
|
|
63
81
|
create_params = block.call if block
|
64
82
|
create_params = create_params.symbolize_keys
|
@@ -80,7 +98,6 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
80
98
|
end
|
81
99
|
|
82
100
|
new_instance.assign_attributes(create_params)
|
83
|
-
save_method = save_options.delete(:save_method)
|
84
101
|
new_instance.__send__(save_method, save_options)
|
85
102
|
return new_instance
|
86
103
|
ensure
|
@@ -11,7 +11,7 @@ module NoBrainer::Document::Aliases
|
|
11
11
|
end
|
12
12
|
|
13
13
|
module ClassMethods
|
14
|
-
def
|
14
|
+
def field(attr, options={})
|
15
15
|
if options[:store_as]
|
16
16
|
self.alias_map[attr.to_s] = options[:store_as].to_s
|
17
17
|
self.alias_reverse_map[options[:store_as].to_s] = attr.to_s
|
@@ -19,7 +19,7 @@ module NoBrainer::Document::Aliases
|
|
19
19
|
super
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def remove_field(attr, options={})
|
23
23
|
super
|
24
24
|
|
25
25
|
self.alias_map.delete(attr.to_s)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module NoBrainer::Document::Association
|
2
2
|
extend NoBrainer::Autoload
|
3
3
|
autoload :Core, :BelongsTo, :HasMany, :HasManyThrough, :HasOne, :HasOneThrough, :EagerLoader
|
4
|
+
eager_autoload :EagerLoader
|
4
5
|
METHODS = [:belongs_to, :has_many, :has_one]
|
5
6
|
|
6
7
|
extend ActiveSupport::Concern
|
@@ -24,6 +25,8 @@ module NoBrainer::Document::Association
|
|
24
25
|
define_method(association) do |target, options={}|
|
25
26
|
target = target.to_sym
|
26
27
|
|
28
|
+
options[:class_name] = options.delete(:class) if options[:class]
|
29
|
+
|
27
30
|
if r = self.association_metadata[target]
|
28
31
|
raise "Cannot change the :through option" unless r.options[:through] == options[:through]
|
29
32
|
r.options.merge!(options)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module NoBrainer::Document::Attributes
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type, :readonly, :primary_key,
|
3
|
-
:
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :readonly, :primary_key,
|
3
|
+
:lazy_fetch, :store_as, :validates, :required, :unique,
|
4
|
+
:uniq, :format, :in, :length, :min_length, :max_length,
|
5
|
+
:prefix, :suffix]
|
4
6
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] +
|
5
7
|
NoBrainer::SymbolDecoration::OPERATORS
|
6
8
|
|
@@ -115,43 +117,32 @@ module NoBrainer::Document::Attributes
|
|
115
117
|
super
|
116
118
|
end
|
117
119
|
|
118
|
-
|
119
|
-
# (c.f. primary key module). _field always receive an immutable options list.
|
120
|
-
def _field(attr, options={})
|
120
|
+
def field(attr, options={})
|
121
121
|
options.assert_valid_keys(*VALID_FIELD_OPTIONS)
|
122
|
+
unless attr.is_a?(Symbol)
|
123
|
+
raise "The field `#{attr}' must be declared with a Symbol" # we're just being lazy here...
|
124
|
+
end
|
122
125
|
if attr.in?(RESERVED_FIELD_NAMES)
|
123
126
|
raise "The field name `:#{attr}' is reserved. Please use another one."
|
124
127
|
end
|
125
128
|
|
126
|
-
attr = attr.to_s
|
127
|
-
inject_in_layer :attributes do
|
128
|
-
define_method("#{attr}=") { |value| _write_attribute(attr, value) }
|
129
|
-
define_method("#{attr}") { _read_attribute(attr) }
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def field(attr, options={})
|
134
|
-
attr = attr.to_sym
|
135
|
-
|
136
129
|
subclass_tree.each do |subclass|
|
137
130
|
subclass.fields[attr] ||= {}
|
138
131
|
subclass.fields[attr].deep_merge!(options)
|
139
132
|
end
|
140
133
|
|
141
|
-
|
134
|
+
attr = attr.to_s
|
135
|
+
inject_in_layer :attributes do
|
136
|
+
define_method("#{attr}=") { |value| _write_attribute(attr, value) }
|
137
|
+
define_method("#{attr}") { _read_attribute(attr) }
|
138
|
+
end
|
142
139
|
end
|
143
140
|
|
144
|
-
def
|
141
|
+
def remove_field(attr, options={})
|
145
142
|
inject_in_layer :attributes do
|
146
143
|
remove_method("#{attr}=")
|
147
144
|
remove_method("#{attr}")
|
148
145
|
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def remove_field(attr, options={})
|
152
|
-
attr = attr.to_sym
|
153
|
-
|
154
|
-
_remove_field(attr, options)
|
155
146
|
|
156
147
|
subclass_tree.each do |subclass|
|
157
148
|
subclass.fields.delete(attr)
|
@@ -75,7 +75,7 @@ module NoBrainer::Document::Dirty
|
|
75
75
|
end
|
76
76
|
|
77
77
|
module ClassMethods
|
78
|
-
def
|
78
|
+
def field(attr, options={})
|
79
79
|
super
|
80
80
|
attr = attr.to_s
|
81
81
|
|
@@ -97,7 +97,7 @@ module NoBrainer::Document::Dirty
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
-
def
|
100
|
+
def remove_field(attr, options={})
|
101
101
|
super
|
102
102
|
inject_in_layer :dirty_tracking do
|
103
103
|
remove_method("#{attr}_change")
|
@@ -12,7 +12,12 @@ module NoBrainer::Document::Index
|
|
12
12
|
|
13
13
|
module ClassMethods
|
14
14
|
def index(name, *args)
|
15
|
-
name = name
|
15
|
+
name = case name
|
16
|
+
when String then name.to_sym
|
17
|
+
when Symbol then name
|
18
|
+
else raise ArgumentError, "Incorrect index specification"
|
19
|
+
end
|
20
|
+
|
16
21
|
options = args.extract_options!
|
17
22
|
options.assert_valid_keys(*VALID_INDEX_OPTIONS)
|
18
23
|
|
@@ -54,14 +59,14 @@ module NoBrainer::Document::Index
|
|
54
59
|
!!indexes[name.to_sym]
|
55
60
|
end
|
56
61
|
|
57
|
-
def
|
62
|
+
def field(attr, options={})
|
58
63
|
if has_index?(attr) && indexes[attr].kind != :single
|
59
64
|
raise "The index `#{attr}' is already declared. Please remove its definition first."
|
60
65
|
end
|
61
66
|
|
62
67
|
super
|
63
68
|
|
64
|
-
store_as = {:store_as =>
|
69
|
+
store_as = {:store_as => fields[attr][:store_as]}
|
65
70
|
case options[:index]
|
66
71
|
when nil then
|
67
72
|
when Hash then index(attr, store_as.merge(options[:index]))
|
@@ -71,7 +76,7 @@ module NoBrainer::Document::Index
|
|
71
76
|
end
|
72
77
|
end
|
73
78
|
|
74
|
-
def
|
79
|
+
def remove_field(attr, options={})
|
75
80
|
remove_index(attr) if fields[attr][:index]
|
76
81
|
super
|
77
82
|
end
|
@@ -28,18 +28,18 @@ module NoBrainer::Document::LazyFetch
|
|
28
28
|
super
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
31
|
+
def field(attr, options={})
|
32
32
|
super
|
33
33
|
attr = attr.to_s
|
34
|
-
model = self
|
35
|
-
inject_in_layer :lazy_fetch do
|
36
|
-
if options[:lazy_fetch]
|
37
|
-
model.subclass_tree.each { |subclass| subclass.fields_to_lazy_fetch << attr }
|
38
|
-
else
|
39
|
-
model.subclass_tree.each { |subclass| subclass.fields_to_lazy_fetch.delete(attr) }
|
40
|
-
end
|
41
34
|
|
42
|
-
|
35
|
+
case options[:lazy_fetch]
|
36
|
+
when true then subclass_tree.each { |subclass| subclass.fields_to_lazy_fetch << attr }
|
37
|
+
when false then subclass_tree.each { |subclass| subclass.fields_to_lazy_fetch.delete(attr) }
|
38
|
+
end
|
39
|
+
|
40
|
+
inject_in_layer :lazy_fetch do
|
41
|
+
# Lazy loading can also specified through criteria, we have to define
|
42
|
+
# this method regardless of the provided options.
|
43
43
|
define_method("#{attr}") do
|
44
44
|
return super() unless @lazy_fetch
|
45
45
|
|
@@ -55,12 +55,12 @@ module NoBrainer::Document::LazyFetch
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
|
60
|
-
subclass_tree.each { |subclass| subclass.fields_to_lazy_fetch.delete(attr) }
|
58
|
+
def remove_field(attr, options={})
|
59
|
+
subclass_tree.each { |subclass| subclass.fields_to_lazy_fetch.delete(attr.to_s) }
|
61
60
|
inject_in_layer :lazy_fetch do
|
62
|
-
remove_method("#{attr}")
|
61
|
+
remove_method("#{attr}")
|
63
62
|
end
|
63
|
+
super
|
64
64
|
end
|
65
65
|
|
66
66
|
def all
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module NoBrainer::Document::PrimaryKey
|
2
2
|
extend NoBrainer::Autoload
|
3
|
-
|
3
|
+
eager_autoload :Generator
|
4
4
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
include ActiveModel::Conversion
|
@@ -34,50 +34,38 @@ module NoBrainer::Document::PrimaryKey
|
|
34
34
|
end
|
35
35
|
|
36
36
|
module ClassMethods
|
37
|
-
def define_default_pk
|
38
|
-
class_variable_set(:@@pk_name, nil)
|
39
|
-
field NoBrainer::Document::PrimaryKey::DEFAULT_PK_NAME, :primary_key => :default
|
40
|
-
end
|
41
|
-
|
42
|
-
def define_pk(attr)
|
43
|
-
return if pk_name == attr
|
44
|
-
if fields[pk_name].try(:[], :primary_key) == :default
|
45
|
-
remove_field(pk_name, :set_default_pk => false)
|
46
|
-
end
|
47
|
-
class_variable_set(:@@pk_name, attr)
|
48
|
-
end
|
49
|
-
|
50
37
|
def pk_name
|
51
38
|
class_variable_get(:@@pk_name)
|
52
39
|
end
|
53
40
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
41
|
+
def define_default_pk
|
42
|
+
class_variable_set(:@@pk_name, nil)
|
43
|
+
|
44
|
+
# TODO Maybe we should let the user configure the pk generator
|
45
|
+
pk_generator = NoBrainer::Document::PrimaryKey::Generator
|
46
|
+
|
47
|
+
field NoBrainer::Document::PrimaryKey::DEFAULT_PK_NAME, :primary_key => true,
|
48
|
+
:type => pk_generator.field_type, :default => ->{ pk_generator.generate }
|
57
49
|
end
|
58
50
|
|
59
51
|
def field(attr, options={})
|
60
|
-
|
61
|
-
options = options.merge(:readonly => true) if options[:readonly].nil?
|
62
|
-
options = options.merge(:index => true)
|
52
|
+
return super unless options[:primary_key]
|
63
53
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if options[:type].in?([pk_generator.field_type, nil]) && !options.key?(:default)
|
68
|
-
options[:type] = pk_generator.field_type
|
69
|
-
options[:default] = ->{ pk_generator.generate }
|
70
|
-
end
|
54
|
+
if attr != pk_name
|
55
|
+
remove_field(pk_name, :set_default_pk => false) if pk_name
|
56
|
+
class_variable_set(:@@pk_name, attr)
|
71
57
|
end
|
72
58
|
|
59
|
+
options[:index] = true
|
60
|
+
options[:readonly] = true
|
73
61
|
super
|
74
62
|
end
|
75
63
|
|
76
|
-
def
|
77
|
-
super
|
64
|
+
def remove_field(attr, options={})
|
78
65
|
if fields[attr][:primary_key] && options[:set_default_pk] != false
|
79
66
|
define_default_pk
|
80
67
|
end
|
68
|
+
super
|
81
69
|
end
|
82
70
|
end
|
83
71
|
end
|
@@ -2,21 +2,25 @@ module NoBrainer::Document::Readonly
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
module ClassMethods
|
5
|
-
def
|
5
|
+
def field(attr, options={})
|
6
6
|
super
|
7
7
|
inject_in_layer :readonly do
|
8
|
-
|
8
|
+
case options[:readonly]
|
9
|
+
when true
|
9
10
|
define_method("#{attr}=") do |value|
|
10
|
-
|
11
|
+
unless new_record?
|
12
|
+
if read_attribute(attr) != value
|
13
|
+
raise NoBrainer::Error::ReadonlyField.new("#{attr} is readonly")
|
14
|
+
end
|
15
|
+
end
|
11
16
|
super(value)
|
12
17
|
end
|
13
|
-
|
14
|
-
remove_method("#{attr}=") if method_defined?("#{attr}=")
|
18
|
+
when false then remove_method("#{attr}=") if method_defined?("#{attr}=")
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
19
|
-
def
|
23
|
+
def remove_field(attr, options={})
|
20
24
|
super
|
21
25
|
inject_in_layer :readonly do
|
22
26
|
remove_method("#{attr}=") if method_defined?("#{attr}=")
|
@@ -99,7 +99,7 @@ module NoBrainer::Document::TableConfig
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def sync_indexes(options={})
|
102
|
-
#
|
102
|
+
# NoBrainer internal models don't have indexes.
|
103
103
|
models = NoBrainer::Document.all(:types => [:user])
|
104
104
|
NoBrainer::Document::Index::Synchronizer.new(models).sync_indexes(options)
|
105
105
|
end
|
@@ -110,8 +110,22 @@ module NoBrainer::Document::TableConfig
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def rebalance(options={})
|
113
|
-
|
114
|
-
|
113
|
+
NoBrainer.run { |r| r.table_list }.each do |table_name|
|
114
|
+
NoBrainer.run { |r| r.table(table_name).rebalance }
|
115
|
+
end
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
def drop!
|
120
|
+
NoBrainer.run { |r| r.db_drop(NoBrainer.current_db) }
|
121
|
+
end
|
122
|
+
|
123
|
+
def purge!
|
124
|
+
NoBrainer.run { |r| r.table_list }.each do |table_name|
|
125
|
+
# keeping the index meta store because indexes are not going away when purging
|
126
|
+
next if table_name == NoBrainer::Document::Index::MetaStore.table_name
|
127
|
+
NoBrainer.run { |r| r.table(table_name).delete }
|
128
|
+
end
|
115
129
|
true
|
116
130
|
end
|
117
131
|
end
|
@@ -58,19 +58,20 @@ module NoBrainer::Document::Types
|
|
58
58
|
cast_model_to_db_for(k, super)
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
61
|
+
def field(attr, options={})
|
62
62
|
super
|
63
63
|
|
64
|
-
|
64
|
+
type = options[:type]
|
65
|
+
return unless type
|
65
66
|
|
66
|
-
raise "Please use a class for the type option" unless
|
67
|
-
case
|
67
|
+
raise "Please use a class for the type option" unless type.is_a?(Class)
|
68
|
+
case type.to_s
|
68
69
|
when "NoBrainer::Geo::Circle" then raise "Cannot store circles :("
|
69
70
|
when "NoBrainer::Geo::Polygon", "NoBrainer::Geo::LineString"
|
70
71
|
raise "Make a request on github if you'd like to store polygons/linestrings"
|
71
72
|
end
|
72
73
|
|
73
|
-
NoBrainer::Document::Types.load_type_extensions(
|
74
|
+
NoBrainer::Document::Types.load_type_extensions(type) if type
|
74
75
|
|
75
76
|
inject_in_layer :types do
|
76
77
|
define_method("#{attr}=") do |value|
|
@@ -83,22 +84,28 @@ module NoBrainer::Document::Types
|
|
83
84
|
end
|
84
85
|
super(value)
|
85
86
|
end
|
87
|
+
end
|
86
88
|
|
87
|
-
|
89
|
+
if type.respond_to?(:nobrainer_field_defined)
|
90
|
+
type.nobrainer_field_defined(self, attr, options)
|
88
91
|
end
|
89
92
|
end
|
90
93
|
|
91
|
-
def
|
92
|
-
|
94
|
+
def remove_field(attr, options={})
|
95
|
+
if type = fields[attr][:type]
|
96
|
+
inject_in_layer :types do
|
97
|
+
remove_method("#{attr}=")
|
98
|
+
end
|
93
99
|
|
94
|
-
|
95
|
-
|
96
|
-
|
100
|
+
if type.respond_to?(:nobrainer_field_undefined)
|
101
|
+
type.nobrainer_field_undefined(self, attr, options)
|
102
|
+
end
|
97
103
|
end
|
104
|
+
super
|
98
105
|
end
|
99
106
|
end
|
100
107
|
|
101
|
-
%w(binary boolean text geo).each do |type|
|
108
|
+
%w(binary boolean text geo enum).each do |type|
|
102
109
|
require File.join(File.dirname(__FILE__), 'types', type)
|
103
110
|
const_set(type.camelize, NoBrainer.const_get(type.camelize))
|
104
111
|
end
|
@@ -19,6 +19,18 @@ class NoBrainer::Boolean
|
|
19
19
|
else raise InvalidType
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
def nobrainer_field_defined(model, attr, options={})
|
24
|
+
model.inject_in_layer :types do
|
25
|
+
define_method("#{attr}?") { !!read_attribute(attr) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def nobrainer_field_undefined(model, attr, options={})
|
30
|
+
model.inject_in_layer :types do
|
31
|
+
remove_method("#{attr}?")
|
32
|
+
end
|
33
|
+
end
|
22
34
|
end
|
23
35
|
extend NoBrainerExtensions
|
24
36
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class NoBrainer::Enum
|
2
|
+
def initialize; raise; end
|
3
|
+
def self.inspect; 'Enum'; end
|
4
|
+
def self.to_s; inspect; end
|
5
|
+
def self.name; inspect; end
|
6
|
+
|
7
|
+
module NoBrainerExtensions
|
8
|
+
InvalidType = NoBrainer::Error::InvalidType
|
9
|
+
|
10
|
+
def nobrainer_cast_user_to_model(value)
|
11
|
+
Symbol.nobrainer_cast_user_to_model(value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def nobrainer_cast_db_to_model(value)
|
15
|
+
Symbol.nobrainer_cast_db_to_model(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def nobrainer_field_defined(model, attr, options={})
|
19
|
+
NoBrainer::Document::Types.load_type_extensions(Symbol)
|
20
|
+
|
21
|
+
unless options[:in].present?
|
22
|
+
raise "When using Enum on `#{model}.#{attr}', you must provide the `:in` option to specify values"
|
23
|
+
end
|
24
|
+
unless options[:in].all? { |v| v.is_a?(Symbol) }
|
25
|
+
raise "The `:in` option must specify symbol values"
|
26
|
+
end
|
27
|
+
|
28
|
+
model.inject_in_layer :enum do
|
29
|
+
extend ActiveSupport::Concern
|
30
|
+
|
31
|
+
const_set(:ClassMethods, Module.new) unless const_defined?(:ClassMethods)
|
32
|
+
|
33
|
+
options[:in].each do |value|
|
34
|
+
method = NoBrainer::Enum::NoBrainerExtensions.method_name(value, attr, options)
|
35
|
+
if method_defined?("#{method}?")
|
36
|
+
raise "The method `#{method}' is already taken. You may specify a :prefix or :suffix option"
|
37
|
+
end
|
38
|
+
|
39
|
+
define_method("#{method}?") { read_attribute(attr) == value }
|
40
|
+
define_method("#{method}!") { write_attribute(attr, value) }
|
41
|
+
const_get(:ClassMethods).__send__(:define_method, "#{method}") { where(attr => value) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def nobrainer_field_undefined(model, attr, options={})
|
47
|
+
model.inject_in_layer :enum do
|
48
|
+
model.fields[attr][:in].each do |value|
|
49
|
+
method = NoBrainer::Enum::NoBrainerExtensions.method_name(value, attr, model.fields[attr])
|
50
|
+
remove_method("#{method}?")
|
51
|
+
remove_method("#{method}!")
|
52
|
+
const_get(:ClassMethods).__send__(:remove_method, "#{method}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.method_name(value, attr, options)
|
58
|
+
[options[:prefix] == true ? attr : options[:prefix], value,
|
59
|
+
options[:suffix] == true ? attr : options[:suffix]].compact.join("_")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
extend NoBrainerExtensions
|
63
|
+
end
|
@@ -8,7 +8,8 @@ class Time
|
|
8
8
|
case value
|
9
9
|
when Time then time = value
|
10
10
|
when String
|
11
|
-
value = value.strip
|
11
|
+
value = value.strip
|
12
|
+
value = value.sub(/Z$/, '+00:00') unless NoBrainer.jruby?
|
12
13
|
# Using DateTime to preserve the timezone offset
|
13
14
|
dt = DateTime.parse(value) rescue (raise InvalidType)
|
14
15
|
time = Time.new(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.zone)
|
@@ -10,31 +10,49 @@ module NoBrainer::QueryRunner
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
autoload :Driver, :DatabaseOnDemand, :TableOnDemand, :WriteError,
|
14
|
-
:Reconnect, :
|
15
|
-
:ConnectionLock
|
13
|
+
autoload :EMDriver, :Driver, :DatabaseOnDemand, :TableOnDemand, :WriteError,
|
14
|
+
:Reconnect, :RunOptions, :Profiler, :MissingIndex, :ConnectionLock
|
16
15
|
|
17
16
|
class << self
|
18
|
-
attr_accessor :stack
|
19
|
-
|
20
17
|
def run(*args, &block)
|
21
18
|
options = args.extract_options!
|
22
19
|
raise ArgumentError unless args.size == 1 || block
|
23
20
|
query = args.first || block.call(RethinkDB::RQL.new)
|
24
21
|
stack.call(:query => query, :options => options)
|
25
22
|
end
|
26
|
-
end
|
27
23
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
24
|
+
def stack
|
25
|
+
case NoBrainer::Config.driver
|
26
|
+
when :regular then normal_stack
|
27
|
+
when :em then em_stack
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def normal_stack
|
32
|
+
@normal_stack ||= ::Middleware::Builder.new do
|
33
|
+
use RunOptions
|
34
|
+
use MissingIndex
|
35
|
+
use DatabaseOnDemand
|
36
|
+
use TableOnDemand
|
37
|
+
use Profiler
|
38
|
+
use WriteError
|
39
|
+
use ConnectionLock
|
40
|
+
use Reconnect
|
41
|
+
use Driver
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def em_stack
|
46
|
+
@em_stack ||= ::Middleware::Builder.new do
|
47
|
+
use RunOptions
|
48
|
+
use MissingIndex
|
49
|
+
use DatabaseOnDemand
|
50
|
+
use TableOnDemand
|
51
|
+
use Profiler
|
52
|
+
use WriteError
|
53
|
+
use Reconnect
|
54
|
+
use EMDriver
|
55
|
+
end
|
56
|
+
end
|
39
57
|
end
|
40
58
|
end
|
@@ -3,6 +3,9 @@ class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middlew
|
|
3
3
|
@runner.call(env)
|
4
4
|
rescue RuntimeError => e
|
5
5
|
if db_name = handle_database_on_demand_exception?(env, e)
|
6
|
+
# Don't auto create on db_drop.
|
7
|
+
return {} if NoBrainer::RQL.db_drop?(env[:query])
|
8
|
+
|
6
9
|
auto_create_database(env, db_name)
|
7
10
|
retry
|
8
11
|
end
|
@@ -10,7 +13,7 @@ class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middlew
|
|
10
13
|
end
|
11
14
|
|
12
15
|
def handle_database_on_demand_exception?(env, e)
|
13
|
-
|
16
|
+
/^Database `(.+)` does not exist\.$/.match(e.message).try(:[], 1)
|
14
17
|
end
|
15
18
|
|
16
19
|
private
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'fiber'
|
3
|
+
|
4
|
+
class NoBrainer::QueryRunner::EMDriver < NoBrainer::QueryRunner::Middleware
|
5
|
+
def call(env)
|
6
|
+
options = env[:options]
|
7
|
+
options = options.merge(:db => RethinkDB::RQL.new.db(options[:db])) if options[:db]
|
8
|
+
|
9
|
+
handler = ResponseHandler.new
|
10
|
+
query_handler = env[:query].em_run(NoBrainer.connection.raw, handler, options)
|
11
|
+
handler.on_dispatch(query_handler)
|
12
|
+
handler.value
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.sync(&block)
|
16
|
+
# Similar to em-synchrony's sync.
|
17
|
+
f = Fiber.current
|
18
|
+
block.call(proc do |val|
|
19
|
+
if f == Fiber.current
|
20
|
+
return val
|
21
|
+
else
|
22
|
+
f.resume(val)
|
23
|
+
end
|
24
|
+
end)
|
25
|
+
Fiber.yield
|
26
|
+
end
|
27
|
+
|
28
|
+
class ResponseHandler < RethinkDB::Handler
|
29
|
+
def initialize
|
30
|
+
@ready = EventMachine::DefaultDeferrable.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def close_query_handle
|
34
|
+
@query_handle.close
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_dispatch(caller)
|
38
|
+
@query_handle = caller
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_open(caller)
|
42
|
+
@has_data = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_close(caller)
|
46
|
+
return if @has_atom
|
47
|
+
return on_error(RethinkDB::RqlRuntimeError.new("NoBrainer EM driver: No data received"), caller) unless @has_data
|
48
|
+
@queue ? push(:close) : set_atom([])
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_error(err, caller)
|
52
|
+
@error = err
|
53
|
+
push(err)
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_atom(val, caller)
|
57
|
+
set_atom(val)
|
58
|
+
end
|
59
|
+
|
60
|
+
def on_array(arr, caller)
|
61
|
+
set_atom(arr)
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_stream_val(val, caller)
|
65
|
+
push([val])
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_unhandled_change(val, caller)
|
69
|
+
push([val])
|
70
|
+
end
|
71
|
+
|
72
|
+
def push(v)
|
73
|
+
raise "internal error: unexpected stream" if @has_atom
|
74
|
+
@queue ||= EventMachine::Queue.new
|
75
|
+
@queue.push(v)
|
76
|
+
response_ready!
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_atom(v)
|
80
|
+
raise "internal error: unexpected atom" if @queue
|
81
|
+
@has_atom = true
|
82
|
+
@value = v
|
83
|
+
response_ready!
|
84
|
+
end
|
85
|
+
|
86
|
+
def response_ready!
|
87
|
+
@ready.succeed(nil) if @ready
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait_for_response
|
91
|
+
NoBrainer::QueryRunner::EMDriver.sync { |w| @ready.callback(&w) } if @ready
|
92
|
+
@ready = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def value
|
96
|
+
wait_for_response
|
97
|
+
raise @error if @error
|
98
|
+
@has_atom ? @value : Cursor.new(self, @queue)
|
99
|
+
end
|
100
|
+
|
101
|
+
class Cursor
|
102
|
+
include Enumerable
|
103
|
+
def initialize(handler, queue)
|
104
|
+
@handler = handler
|
105
|
+
@queue = queue
|
106
|
+
end
|
107
|
+
|
108
|
+
def close
|
109
|
+
@handler.close_query_handle
|
110
|
+
end
|
111
|
+
|
112
|
+
def each(&block)
|
113
|
+
return enum_for(:each) unless block
|
114
|
+
|
115
|
+
raise "Can only iterate over a cursor once." if @iterated
|
116
|
+
@iterated = true
|
117
|
+
|
118
|
+
loop do
|
119
|
+
case result = NoBrainer::QueryRunner::EMDriver.sync { |w| @queue.pop(&w) }
|
120
|
+
when :close then return self
|
121
|
+
when Exception then raise result
|
122
|
+
else result.each { |v| block.call(v) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
|
2
2
|
def call(env)
|
3
3
|
@runner.call(env)
|
4
|
-
rescue
|
5
|
-
if
|
6
|
-
index_name =
|
7
|
-
db_name = $2
|
8
|
-
table_name = $3
|
4
|
+
rescue RuntimeError => e
|
5
|
+
if match_data = /^Index `(.+)` was not found on table `(.+)\.(.+)`\.$/.match(e.message)
|
6
|
+
_, index_name, db_name, table_name = *match_data
|
9
7
|
|
10
8
|
model = NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
|
11
9
|
index = model.indexes.values.select { |i| i.aliased_name == index_name.to_sym }.first if model
|
@@ -2,56 +2,18 @@ class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
|
|
2
2
|
def call(env)
|
3
3
|
@runner.call(env)
|
4
4
|
rescue StandardError => e
|
5
|
-
if is_connection_error_exception?(e)
|
6
|
-
context ||= {}
|
7
|
-
# XXX Possibly dangerous, as we could reexecute a non idempotent operation
|
8
|
-
retry if reconnect(e, context)
|
9
|
-
end
|
5
|
+
NoBrainer.disconnect if is_connection_error_exception?(e)
|
10
6
|
raise
|
11
7
|
end
|
12
8
|
|
13
9
|
private
|
14
10
|
|
15
|
-
def max_tries
|
16
|
-
NoBrainer::Config.max_retries_on_connection_failure
|
17
|
-
end
|
18
|
-
|
19
|
-
def reconnect(e, context)
|
20
|
-
context[:connection_retries] ||= max_tries
|
21
|
-
context[:previous_connection] ||= NoBrainer.connection
|
22
|
-
NoBrainer.disconnect
|
23
|
-
|
24
|
-
unless context[:lost_connection_logged]
|
25
|
-
context[:lost_connection_logged] = true
|
26
|
-
|
27
|
-
msg = server_not_ready?(e) ? "Server %s not ready: %s" : "Connection issue with %s: %s"
|
28
|
-
NoBrainer.logger.warn(msg % [context[:previous_connection].try(:uri), exception_msg(e)])
|
29
|
-
end
|
30
|
-
|
31
|
-
if context[:connection_retries].zero?
|
32
|
-
NoBrainer.logger.info("Retry limit exceeded (#{max_tries}). Giving up.")
|
33
|
-
return false
|
34
|
-
end
|
35
|
-
context[:connection_retries] -= 1
|
36
|
-
|
37
|
-
sleep 1
|
38
|
-
|
39
|
-
c = NoBrainer.connection
|
40
|
-
NoBrainer.logger.info("Connecting to #{c.uri}... (last error: #{exception_msg(e)})")
|
41
|
-
c.connect
|
42
|
-
|
43
|
-
true
|
44
|
-
rescue StandardError => e
|
45
|
-
retry if is_connection_error_exception?(e)
|
46
|
-
raise
|
47
|
-
end
|
48
|
-
|
49
11
|
def is_connection_error_exception?(e)
|
50
12
|
case e
|
51
13
|
when Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE,
|
52
14
|
Errno::ECONNRESET, Errno::ETIMEDOUT, IOError
|
53
15
|
true
|
54
|
-
when RethinkDB::
|
16
|
+
when RethinkDB::RqlError
|
55
17
|
e.message =~ /lost contact/ ||
|
56
18
|
e.message =~ /(P|p)rimary .* not available/||
|
57
19
|
e.message =~ /Connection.*closed/
|
@@ -59,13 +21,4 @@ class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
|
|
59
21
|
false
|
60
22
|
end
|
61
23
|
end
|
62
|
-
|
63
|
-
def exception_msg(e)
|
64
|
-
e.is_a?(RethinkDB::RqlRuntimeError) ? e.message.split("\n").first : e.to_s
|
65
|
-
end
|
66
|
-
|
67
|
-
def server_not_ready?(e)
|
68
|
-
e.message =~ /lost contact/ ||
|
69
|
-
e.message =~ /(P|p)rimary .* not available/
|
70
|
-
end
|
71
24
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class NoBrainer::QueryRunner::TableOnDemand < NoBrainer::QueryRunner::Middleware
|
2
2
|
def call(env)
|
3
3
|
@runner.call(env)
|
4
|
+
# Not matching in RqlRuntimeError because we can get a DocumentNotPersisted
|
4
5
|
rescue RuntimeError => e
|
5
6
|
if table_info = handle_table_on_demand_exception?(env, e)
|
6
7
|
auto_create_table(env, *table_info)
|
@@ -10,7 +11,7 @@ class NoBrainer::QueryRunner::TableOnDemand < NoBrainer::QueryRunner::Middleware
|
|
10
11
|
end
|
11
12
|
|
12
13
|
def handle_table_on_demand_exception?(env, e)
|
13
|
-
|
14
|
+
/^Table `(.+)\.(.+)` does not exist\.$/.match(e.message).try(:[], 1..2)
|
14
15
|
end
|
15
16
|
|
16
17
|
private
|
@@ -4,17 +4,20 @@ namespace :nobrainer do
|
|
4
4
|
NoBrainer.drop!
|
5
5
|
end
|
6
6
|
|
7
|
-
desc '
|
7
|
+
desc 'Rebalance all tables'
|
8
|
+
task :rebalance => :environment do
|
9
|
+
NoBrainer.rebalance(:verbose => true)
|
10
|
+
end
|
11
|
+
|
8
12
|
task :sync_indexes => :environment do
|
9
13
|
NoBrainer.sync_indexes(:verbose => true)
|
10
14
|
end
|
11
15
|
|
12
|
-
desc 'Synchronize table configuration'
|
13
16
|
task :sync_table_config => :environment do
|
14
17
|
NoBrainer.sync_table_config(:verbose => true)
|
15
18
|
end
|
16
19
|
|
17
|
-
desc 'Synchronize
|
20
|
+
desc 'Synchronize schema'
|
18
21
|
task :sync_schema => :environment do
|
19
22
|
NoBrainer.sync_schema(:verbose => true)
|
20
23
|
end
|
@@ -28,17 +31,11 @@ namespace :nobrainer do
|
|
28
31
|
Rails.application.load_seed
|
29
32
|
end
|
30
33
|
|
31
|
-
desc 'Equivalent to :sync_schema_quiet + :seed'
|
32
34
|
task :setup => [:sync_schema_quiet, :seed]
|
33
35
|
|
34
|
-
desc 'Equivalent to :drop + :
|
35
|
-
task :reset => [:drop, :
|
36
|
+
desc 'Equivalent to :drop + :sync_schema + :seed'
|
37
|
+
task :reset => [:drop, :sync_schema_quiet, :seed]
|
36
38
|
|
37
|
-
task :create => :
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
task :migrate => :environment do
|
42
|
-
# noop
|
43
|
-
end
|
39
|
+
task :create => [:sync_schema]
|
40
|
+
task :migrate => [:sync_schema]
|
44
41
|
end
|
data/lib/no_brainer/rql.rb
CHANGED
@@ -17,8 +17,16 @@ module NoBrainer::RQL
|
|
17
17
|
type_of(rql) == :write
|
18
18
|
end
|
19
19
|
|
20
|
+
def get_rql_statement(rql)
|
21
|
+
rql.is_a?(RethinkDB::RQL) && rql.body.is_a?(Array) && rql.body.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def db_drop?(rql)
|
25
|
+
get_rql_statement(rql) == DB_DROP
|
26
|
+
end
|
27
|
+
|
20
28
|
def type_of(rql)
|
21
|
-
case
|
29
|
+
case get_rql_statement(rql)
|
22
30
|
when UPDATE, DELETE, REPLACE, INSERT
|
23
31
|
:write
|
24
32
|
when DB_CREATE, DB_DROP, DB_LIST, TABLE_CREATE, TABLE_DROP, TABLE_LIST,
|
data/lib/nobrainer.rb
CHANGED
@@ -20,8 +20,7 @@ module NoBrainer
|
|
20
20
|
|
21
21
|
class << self
|
22
22
|
delegate :connection, :disconnect, :to => 'NoBrainer::ConnectionManager'
|
23
|
-
|
24
|
-
delegate :drop!, :purge!, :default_db, :current_db, :to => :connection
|
23
|
+
delegate :default_db, :current_db, :to => :connection
|
25
24
|
|
26
25
|
delegate :configure, :logger, :to => 'NoBrainer::Config'
|
27
26
|
delegate :run, :to => 'NoBrainer::QueryRunner'
|
@@ -29,13 +28,22 @@ module NoBrainer
|
|
29
28
|
|
30
29
|
delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions' # deprecated
|
31
30
|
|
32
|
-
delegate :sync_indexes, :sync_table_config, :sync_schema, :rebalance,
|
31
|
+
delegate :sync_indexes, :sync_table_config, :sync_schema, :rebalance,
|
32
|
+
:drop!, :purge!, :to => 'NoBrainer::Document::TableConfig'
|
33
33
|
|
34
34
|
delegate :eager_load, :to => 'NoBrainer::Document::Association::EagerLoader'
|
35
35
|
|
36
36
|
def jruby?
|
37
37
|
RUBY_PLATFORM == 'java'
|
38
38
|
end
|
39
|
+
|
40
|
+
def eager_load!
|
41
|
+
# XXX This forces all the NoBrainer code to be loaded in memory.
|
42
|
+
# Not to be confused with eager_load() that operates on documents.
|
43
|
+
# We assume that NoBrainer is already configured at this point.
|
44
|
+
super
|
45
|
+
NoBrainer::QueryRunner.stack # load the code for the current stack
|
46
|
+
end
|
39
47
|
end
|
40
48
|
|
41
49
|
Fork.hook unless jruby?
|
@@ -22,6 +22,10 @@ NoBrainer.configure do |config|
|
|
22
22
|
# an SSL connection to the RethinkDB servers.
|
23
23
|
# config.ssl_options = nil
|
24
24
|
|
25
|
+
# driver specifies which driver to use. You may use :regular or :em.
|
26
|
+
# Use :em if you use EventMachine with em-synchrony.
|
27
|
+
# config.driver = :regular
|
28
|
+
|
25
29
|
# NoBrainer uses logger to emit debugging information.
|
26
30
|
# The default logger is the Rails logger if run with Rails,
|
27
31
|
# otherwise Logger.new(STDERR) with a WARN level.
|
@@ -39,14 +43,6 @@ NoBrainer.configure do |config|
|
|
39
43
|
# You can turn off the warning if you want to use both.
|
40
44
|
# config.warn_on_active_record = true
|
41
45
|
|
42
|
-
# When the network connection is lost, NoBrainer can retry running a given
|
43
|
-
# query a few times before giving up. Note that this can be a problem with
|
44
|
-
# non idempotent write queries such as increments.
|
45
|
-
# Setting it to 0 disable retries during reconnections.
|
46
|
-
# The default is 1 for development or test environment, otherwise 15.
|
47
|
-
# config.max_retries_on_connection_failure = \
|
48
|
-
# config.default_max_retries_on_connection_failure
|
49
|
-
|
50
46
|
# Configures the durability for database writes.
|
51
47
|
# The default is :soft for development or test environment, otherwise :hard.
|
52
48
|
# config.durability = config.default_durability
|
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.29.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-08-
|
11
|
+
date: 2015-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rethinkdb
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: 2.1.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
26
|
+
version: 2.1.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -154,6 +154,7 @@ files:
|
|
154
154
|
- lib/no_brainer/document/types/binary.rb
|
155
155
|
- lib/no_brainer/document/types/boolean.rb
|
156
156
|
- lib/no_brainer/document/types/date.rb
|
157
|
+
- lib/no_brainer/document/types/enum.rb
|
157
158
|
- lib/no_brainer/document/types/float.rb
|
158
159
|
- lib/no_brainer/document/types/geo.rb
|
159
160
|
- lib/no_brainer/document/types/integer.rb
|
@@ -184,6 +185,7 @@ files:
|
|
184
185
|
- lib/no_brainer/query_runner/connection_lock.rb
|
185
186
|
- lib/no_brainer/query_runner/database_on_demand.rb
|
186
187
|
- lib/no_brainer/query_runner/driver.rb
|
188
|
+
- lib/no_brainer/query_runner/em_driver.rb
|
187
189
|
- lib/no_brainer/query_runner/missing_index.rb
|
188
190
|
- lib/no_brainer/query_runner/profiler.rb
|
189
191
|
- lib/no_brainer/query_runner/reconnect.rb
|