nobrainer 0.28.0 → 0.29.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/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
|