bigrecord 0.0.5
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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +44 -0
- data/Rakefile +17 -0
- data/VERSION +1 -0
- data/doc/bigrecord_specs.rdoc +36 -0
- data/doc/getting_started.rdoc +157 -0
- data/examples/bigrecord.yml +25 -0
- data/generators/bigrecord/bigrecord_generator.rb +17 -0
- data/generators/bigrecord/templates/bigrecord.rake +47 -0
- data/generators/bigrecord_migration/bigrecord_migration_generator.rb +13 -0
- data/generators/bigrecord_migration/templates/migration.rb +9 -0
- data/generators/bigrecord_model/bigrecord_model_generator.rb +28 -0
- data/generators/bigrecord_model/templates/migration.rb +13 -0
- data/generators/bigrecord_model/templates/model.rb +7 -0
- data/generators/bigrecord_model/templates/model_spec.rb +12 -0
- data/init.rb +9 -0
- data/install.rb +22 -0
- data/lib/big_record/abstract_base.rb +1088 -0
- data/lib/big_record/action_view_extensions.rb +266 -0
- data/lib/big_record/ar_associations/association_collection.rb +194 -0
- data/lib/big_record/ar_associations/association_proxy.rb +158 -0
- data/lib/big_record/ar_associations/belongs_to_association.rb +57 -0
- data/lib/big_record/ar_associations/belongs_to_many_association.rb +57 -0
- data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +164 -0
- data/lib/big_record/ar_associations/has_many_association.rb +191 -0
- data/lib/big_record/ar_associations/has_one_association.rb +80 -0
- data/lib/big_record/ar_associations.rb +1608 -0
- data/lib/big_record/ar_reflection.rb +223 -0
- data/lib/big_record/attribute_methods.rb +75 -0
- data/lib/big_record/base.rb +618 -0
- data/lib/big_record/br_associations/association_collection.rb +194 -0
- data/lib/big_record/br_associations/association_proxy.rb +153 -0
- data/lib/big_record/br_associations/belongs_to_association.rb +52 -0
- data/lib/big_record/br_associations/belongs_to_many_association.rb +293 -0
- data/lib/big_record/br_associations/cached_item_proxy.rb +194 -0
- data/lib/big_record/br_associations/cached_item_proxy_factory.rb +62 -0
- data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +168 -0
- data/lib/big_record/br_associations/has_one_association.rb +80 -0
- data/lib/big_record/br_associations.rb +978 -0
- data/lib/big_record/br_reflection.rb +151 -0
- data/lib/big_record/callbacks.rb +367 -0
- data/lib/big_record/connection_adapters/abstract/connection_specification.rb +279 -0
- data/lib/big_record/connection_adapters/abstract/database_statements.rb +175 -0
- data/lib/big_record/connection_adapters/abstract/quoting.rb +58 -0
- data/lib/big_record/connection_adapters/abstract_adapter.rb +190 -0
- data/lib/big_record/connection_adapters/column.rb +491 -0
- data/lib/big_record/connection_adapters/hbase_adapter.rb +432 -0
- data/lib/big_record/connection_adapters/view.rb +27 -0
- data/lib/big_record/connection_adapters.rb +10 -0
- data/lib/big_record/deletion.rb +73 -0
- data/lib/big_record/dynamic_schema.rb +92 -0
- data/lib/big_record/embedded.rb +71 -0
- data/lib/big_record/embedded_associations/association_proxy.rb +148 -0
- data/lib/big_record/family_span_columns.rb +89 -0
- data/lib/big_record/fixtures.rb +1025 -0
- data/lib/big_record/migration.rb +380 -0
- data/lib/big_record/routing_ext.rb +65 -0
- data/lib/big_record/timestamp.rb +51 -0
- data/lib/big_record/validations.rb +830 -0
- data/lib/big_record.rb +125 -0
- data/lib/bigrecord.rb +1 -0
- data/rails/init.rb +9 -0
- data/spec/connections/bigrecord.yml +13 -0
- data/spec/connections/cassandra/connection.rb +2 -0
- data/spec/connections/hbase/connection.rb +2 -0
- data/spec/debug.log +281 -0
- data/spec/integration/br_associations_spec.rb +80 -0
- data/spec/lib/animal.rb +12 -0
- data/spec/lib/book.rb +10 -0
- data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +14 -0
- data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +9 -0
- data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +9 -0
- data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +9 -0
- data/spec/lib/company.rb +14 -0
- data/spec/lib/embedded/web_link.rb +12 -0
- data/spec/lib/employee.rb +33 -0
- data/spec/lib/migrations/20090706182535_add_animals_table.rb +13 -0
- data/spec/lib/migrations/20090706190623_add_books_table.rb +15 -0
- data/spec/lib/migrations/20090706193019_add_companies_table.rb +14 -0
- data/spec/lib/migrations/20090706194512_add_employees_table.rb +13 -0
- data/spec/lib/migrations/20090706195741_add_zoos_table.rb +13 -0
- data/spec/lib/novel.rb +5 -0
- data/spec/lib/zoo.rb +17 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/unit/abstract_base_spec.rb +287 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +56 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +51 -0
- data/spec/unit/adapters/hbase_adapter_spec.rb +15 -0
- data/spec/unit/ar_associations_spec.rb +8 -0
- data/spec/unit/base_spec.rb +6 -0
- data/spec/unit/br_associations_spec.rb +58 -0
- data/spec/unit/embedded_spec.rb +43 -0
- data/spec/unit/find_spec.rb +34 -0
- data/spec/unit/hash_helper_spec.rb +44 -0
- data/spec/unit/migration_spec.rb +144 -0
- data/spec/unit/model_spec.rb +315 -0
- data/spec/unit/validations_spec.rb +182 -0
- data/tasks/bigrecord_tasks.rake +47 -0
- data/tasks/data_store.rb +46 -0
- data/tasks/gem.rb +22 -0
- data/tasks/rdoc.rb +8 -0
- data/tasks/spec.rb +34 -0
- metadata +189 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module BigRecord
|
|
4
|
+
class Base
|
|
5
|
+
class ConnectionSpecification #:nodoc:
|
|
6
|
+
attr_reader :config, :adapter_method
|
|
7
|
+
def initialize (config, adapter_method)
|
|
8
|
+
@config, @adapter_method = config, adapter_method
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Check for activity after at least +verification_timeout+ seconds.
|
|
13
|
+
# Defaults to 0 (always check.)
|
|
14
|
+
cattr_accessor :verification_timeout, :instance_writer => false
|
|
15
|
+
@@verification_timeout = 0
|
|
16
|
+
|
|
17
|
+
# The class -> [adapter_method, config] map
|
|
18
|
+
@@defined_connections = {}
|
|
19
|
+
|
|
20
|
+
# The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
|
|
21
|
+
@@active_connections = {}
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# Retrieve the connection cache.
|
|
25
|
+
def thread_safe_active_connections #:nodoc:
|
|
26
|
+
@@active_connections[Thread.current.object_id] ||= {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def single_threaded_active_connections #:nodoc:
|
|
30
|
+
@@active_connections
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# pick up the right active_connection method from @@allow_concurrency
|
|
34
|
+
if @@allow_concurrency
|
|
35
|
+
alias_method :active_connections, :thread_safe_active_connections
|
|
36
|
+
else
|
|
37
|
+
alias_method :active_connections, :single_threaded_active_connections
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# set concurrency support flag (not thread safe, like most of the methods in this file)
|
|
41
|
+
def allow_concurrency=(threaded) #:nodoc:
|
|
42
|
+
logger.debug "allow_concurrency=#{threaded}" if logger
|
|
43
|
+
return if @@allow_concurrency == threaded
|
|
44
|
+
clear_all_cached_connections!
|
|
45
|
+
@@allow_concurrency = threaded
|
|
46
|
+
method_prefix = threaded ? "thread_safe" : "single_threaded"
|
|
47
|
+
sing = (class << self; self; end)
|
|
48
|
+
[:active_connections, :scoped_methods].each do |method|
|
|
49
|
+
sing.send(:alias_method, method, "#{method_prefix}_#{method}")
|
|
50
|
+
end
|
|
51
|
+
log_connections if logger
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def active_connection_name #:nodoc:
|
|
55
|
+
@active_connection_name ||=
|
|
56
|
+
if active_connections[name] || @@defined_connections[name]
|
|
57
|
+
name
|
|
58
|
+
elsif self == BigRecord::Base
|
|
59
|
+
nil
|
|
60
|
+
else
|
|
61
|
+
superclass.active_connection_name
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def clear_active_connection_name #:nodoc:
|
|
66
|
+
@active_connection_name = nil
|
|
67
|
+
subclasses.each { |klass| klass.clear_active_connection_name }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns the connection currently associated with the class. This can
|
|
71
|
+
# also be used to "borrow" the connection to do database work unrelated
|
|
72
|
+
# to any of the specific Active Records.
|
|
73
|
+
def connection
|
|
74
|
+
if @active_connection_name && (conn = active_connections[@active_connection_name])
|
|
75
|
+
conn
|
|
76
|
+
else
|
|
77
|
+
# retrieve_connection sets the cache key.
|
|
78
|
+
conn = retrieve_connection
|
|
79
|
+
active_connections[@active_connection_name] = conn
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Clears the cache which maps classes to connections.
|
|
84
|
+
def clear_active_connections!
|
|
85
|
+
clear_cache!(@@active_connections) do |name, conn|
|
|
86
|
+
conn.disconnect!
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Clears the cache which maps classes
|
|
91
|
+
def clear_reloadable_connections!
|
|
92
|
+
@@active_connections.each do |name, conn|
|
|
93
|
+
if conn.requires_reloading?
|
|
94
|
+
conn.disconnect!
|
|
95
|
+
@@active_connections.delete(name)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Verify active connections.
|
|
101
|
+
def verify_active_connections! #:nodoc:
|
|
102
|
+
if @@allow_concurrency
|
|
103
|
+
remove_stale_cached_threads!(@@active_connections) do |name, conn|
|
|
104
|
+
conn.disconnect!
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
active_connections.each_value do |connection|
|
|
109
|
+
connection.verify!(@@verification_timeout)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
def clear_cache!(cache, thread_id = nil, &block)
|
|
115
|
+
if cache
|
|
116
|
+
if @@allow_concurrency
|
|
117
|
+
thread_id ||= Thread.current.object_id
|
|
118
|
+
thread_cache, cache = cache, cache[thread_id]
|
|
119
|
+
return unless cache
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
cache.each(&block) if block_given?
|
|
123
|
+
cache.clear
|
|
124
|
+
end
|
|
125
|
+
ensure
|
|
126
|
+
if thread_cache && @@allow_concurrency
|
|
127
|
+
thread_cache.delete(thread_id)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Remove stale threads from the cache.
|
|
132
|
+
def remove_stale_cached_threads!(cache, &block)
|
|
133
|
+
stale = Set.new(cache.keys)
|
|
134
|
+
|
|
135
|
+
Thread.list.each do |thread|
|
|
136
|
+
stale.delete(thread.object_id) if thread.alive?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
stale.each do |thread_id|
|
|
140
|
+
clear_cache!(cache, thread_id, &block)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def clear_all_cached_connections!
|
|
145
|
+
if @@allow_concurrency
|
|
146
|
+
@@active_connections.each_value do |connection_hash_for_thread|
|
|
147
|
+
connection_hash_for_thread.each_value {|conn| conn.disconnect! }
|
|
148
|
+
connection_hash_for_thread.clear
|
|
149
|
+
end
|
|
150
|
+
else
|
|
151
|
+
@@active_connections.each_value {|conn| conn.disconnect! }
|
|
152
|
+
end
|
|
153
|
+
@@active_connections.clear
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Returns the connection currently associated with the class. This can
|
|
158
|
+
# also be used to "borrow" the connection to do database work that isn't
|
|
159
|
+
# easily done without going straight to SQL.
|
|
160
|
+
def connection
|
|
161
|
+
self.class.connection
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Establishes the connection to the database. Accepts a hash as input where
|
|
165
|
+
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
|
166
|
+
# example for regular databases (MySQL, Postgresql, etc):
|
|
167
|
+
#
|
|
168
|
+
# ActiveRecord::Base.establish_connection(
|
|
169
|
+
# :adapter => "mysql",
|
|
170
|
+
# :host => "localhost",
|
|
171
|
+
# :username => "myuser",
|
|
172
|
+
# :password => "mypass",
|
|
173
|
+
# :database => "somedatabase"
|
|
174
|
+
# )
|
|
175
|
+
#
|
|
176
|
+
# Example for SQLite database:
|
|
177
|
+
#
|
|
178
|
+
# ActiveRecord::Base.establish_connection(
|
|
179
|
+
# :adapter => "sqlite",
|
|
180
|
+
# :database => "path/to/dbfile"
|
|
181
|
+
# )
|
|
182
|
+
#
|
|
183
|
+
# Also accepts keys as strings (for parsing from yaml for example):
|
|
184
|
+
# ActiveRecord::Base.establish_connection(
|
|
185
|
+
# "adapter" => "sqlite",
|
|
186
|
+
# "database" => "path/to/dbfile"
|
|
187
|
+
# )
|
|
188
|
+
#
|
|
189
|
+
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
|
190
|
+
# may be returned on an error.
|
|
191
|
+
def self.establish_connection(spec = nil)
|
|
192
|
+
case spec
|
|
193
|
+
when nil
|
|
194
|
+
raise AdapterNotSpecified unless defined? RAILS_ENV
|
|
195
|
+
establish_connection(RAILS_ENV)
|
|
196
|
+
when ConnectionSpecification
|
|
197
|
+
clear_active_connection_name
|
|
198
|
+
@active_connection_name = name
|
|
199
|
+
@@defined_connections[name] = spec
|
|
200
|
+
when Symbol, String
|
|
201
|
+
if configuration = configurations[spec.to_s]
|
|
202
|
+
establish_connection(configuration)
|
|
203
|
+
else
|
|
204
|
+
raise AdapterNotSpecified, "#{spec} database is not configured"
|
|
205
|
+
end
|
|
206
|
+
else
|
|
207
|
+
spec = spec.symbolize_keys
|
|
208
|
+
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
|
209
|
+
adapter_method = "#{spec[:adapter]}_connection"
|
|
210
|
+
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
|
|
211
|
+
remove_connection
|
|
212
|
+
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Locate the connection of the nearest super class. This can be an
|
|
217
|
+
# active or defined connections: if it is the latter, it will be
|
|
218
|
+
# opened and set as the active connection for the class it was defined
|
|
219
|
+
# for (not necessarily the current class).
|
|
220
|
+
def self.retrieve_connection #:nodoc:
|
|
221
|
+
# Name is nil if establish_connection hasn't been called for
|
|
222
|
+
# some class along the inheritance chain up to AR::Base yet.
|
|
223
|
+
if name = active_connection_name
|
|
224
|
+
if conn = active_connections[name]
|
|
225
|
+
# Verify the connection.
|
|
226
|
+
conn.verify!(@@verification_timeout)
|
|
227
|
+
elsif spec = @@defined_connections[name]
|
|
228
|
+
# Activate this connection specification.
|
|
229
|
+
klass = name.constantize
|
|
230
|
+
klass.connection = spec
|
|
231
|
+
conn = active_connections[name]
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
conn or raise ConnectionNotEstablished
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Returns true if a connection that's accessible to this class have already been opened.
|
|
239
|
+
def self.connected?
|
|
240
|
+
active_connections[active_connection_name] ? true : false
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Remove the connection for this class. This will close the active
|
|
244
|
+
# connection and the defined connection (if they exist). The result
|
|
245
|
+
# can be used as argument for establish_connection, for easy
|
|
246
|
+
# re-establishing of the connection.
|
|
247
|
+
def self.remove_connection(klass=self)
|
|
248
|
+
spec = @@defined_connections[klass.name]
|
|
249
|
+
konn = active_connections[klass.name]
|
|
250
|
+
@@defined_connections.delete_if { |key, value| value == spec }
|
|
251
|
+
active_connections.delete_if { |key, value| value == konn }
|
|
252
|
+
konn.disconnect! if konn
|
|
253
|
+
spec.config if spec
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Set the connection for the class.
|
|
257
|
+
def self.connection=(spec) #:nodoc:
|
|
258
|
+
if spec.kind_of?(BigRecord::ConnectionAdapters::AbstractAdapter)
|
|
259
|
+
active_connections[name] = spec
|
|
260
|
+
elsif spec.kind_of?(ConnectionSpecification)
|
|
261
|
+
config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
|
|
262
|
+
self.connection = self.send(spec.adapter_method, config)
|
|
263
|
+
elsif spec.nil?
|
|
264
|
+
raise ConnectionNotEstablished
|
|
265
|
+
else
|
|
266
|
+
establish_connection spec
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# connection state logging
|
|
271
|
+
def self.log_connections #:nodoc:
|
|
272
|
+
if logger
|
|
273
|
+
logger.info "Defined connections: #{@@defined_connections.inspect}"
|
|
274
|
+
logger.info "Active connections: #{active_connections.inspect}"
|
|
275
|
+
logger.info "Active connection name: #{@active_connection_name}"
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
module BigRecord
|
|
2
|
+
module ConnectionAdapters # :nodoc:
|
|
3
|
+
module DatabaseStatements
|
|
4
|
+
# # Returns an array of record hashes with the column names as keys and
|
|
5
|
+
# # column values as values.
|
|
6
|
+
# def select_all(sql, name = nil)
|
|
7
|
+
# select(sql, name)
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# # Returns a record hash with the column names as keys and column values
|
|
11
|
+
# # as values.
|
|
12
|
+
# def select_one(sql, name = nil)
|
|
13
|
+
# result = select_all(sql, name)
|
|
14
|
+
# result.first if result
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# # Returns a single value from a record
|
|
18
|
+
# def select_value(sql, name = nil)
|
|
19
|
+
# if result = select_one(sql, name)
|
|
20
|
+
# result.values.first
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # Returns an array of the values of the first column in a select:
|
|
25
|
+
# # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
|
26
|
+
# def select_values(sql, name = nil)
|
|
27
|
+
# result = select_rows(sql, name)
|
|
28
|
+
# result.map { |v| v[0] }
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# # Returns an array of arrays containing the field values.
|
|
32
|
+
# # Order is the same as that returned by #columns.
|
|
33
|
+
# def select_rows(sql, name = nil)
|
|
34
|
+
# raise NotImplementedError, "select_rows is an abstract method"
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# # Executes the SQL statement in the context of this connection.
|
|
38
|
+
# def execute(sql, name = nil)
|
|
39
|
+
# raise NotImplementedError, "execute is an abstract method"
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# # Returns the last auto-generated ID from the affected table.
|
|
43
|
+
# def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
44
|
+
# insert_sql(sql, name, pk, id_value, sequence_name)
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# # Executes the update statement and returns the number of rows affected.
|
|
48
|
+
# def update(sql, name = nil)
|
|
49
|
+
# update_sql(sql, name)
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# # Executes the delete statement and returns the number of rows affected.
|
|
53
|
+
# def delete(sql, name = nil)
|
|
54
|
+
# delete_sql(sql, name)
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# # Wrap a block in a transaction. Returns result of block.
|
|
58
|
+
# def transaction(start_db_transaction = true)
|
|
59
|
+
# transaction_open = false
|
|
60
|
+
# begin
|
|
61
|
+
# if block_given?
|
|
62
|
+
# if start_db_transaction
|
|
63
|
+
# begin_db_transaction
|
|
64
|
+
# transaction_open = true
|
|
65
|
+
# end
|
|
66
|
+
# yield
|
|
67
|
+
# end
|
|
68
|
+
# rescue Exception => database_transaction_rollback
|
|
69
|
+
# if transaction_open
|
|
70
|
+
# transaction_open = false
|
|
71
|
+
# rollback_db_transaction
|
|
72
|
+
# end
|
|
73
|
+
# raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
|
74
|
+
# end
|
|
75
|
+
# ensure
|
|
76
|
+
# if transaction_open
|
|
77
|
+
# begin
|
|
78
|
+
# commit_db_transaction
|
|
79
|
+
# rescue Exception => database_transaction_rollback
|
|
80
|
+
# rollback_db_transaction
|
|
81
|
+
# raise
|
|
82
|
+
# end
|
|
83
|
+
# end
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# # Begins the transaction (and turns off auto-committing).
|
|
87
|
+
# def begin_db_transaction() end
|
|
88
|
+
#
|
|
89
|
+
# # Commits the transaction (and turns on auto-committing).
|
|
90
|
+
# def commit_db_transaction() end
|
|
91
|
+
#
|
|
92
|
+
# # Rolls back the transaction (and turns on auto-committing). Must be
|
|
93
|
+
# # done if the transaction block raises an exception or returns false.
|
|
94
|
+
# def rollback_db_transaction() end
|
|
95
|
+
#
|
|
96
|
+
# # Alias for #add_limit_offset!.
|
|
97
|
+
# def add_limit!(sql, options)
|
|
98
|
+
# add_limit_offset!(sql, options) if options
|
|
99
|
+
# end
|
|
100
|
+
#
|
|
101
|
+
# # Appends +LIMIT+ and +OFFSET+ options to an SQL statement.
|
|
102
|
+
# # This method *modifies* the +sql+ parameter.
|
|
103
|
+
# # ===== Examples
|
|
104
|
+
# # add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
|
105
|
+
# # generates
|
|
106
|
+
# # SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
|
107
|
+
# def add_limit_offset!(sql, options)
|
|
108
|
+
# if limit = options[:limit]
|
|
109
|
+
# sql << " LIMIT #{limit}"
|
|
110
|
+
# if offset = options[:offset]
|
|
111
|
+
# sql << " OFFSET #{offset}"
|
|
112
|
+
# end
|
|
113
|
+
# end
|
|
114
|
+
# end
|
|
115
|
+
#
|
|
116
|
+
# # Appends a locking clause to an SQL statement.
|
|
117
|
+
# # This method *modifies* the +sql+ parameter.
|
|
118
|
+
# # # SELECT * FROM suppliers FOR UPDATE
|
|
119
|
+
# # add_lock! 'SELECT * FROM suppliers', :lock => true
|
|
120
|
+
# # add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
|
|
121
|
+
# def add_lock!(sql, options)
|
|
122
|
+
# case lock = options[:lock]
|
|
123
|
+
# when true; sql << ' FOR UPDATE'
|
|
124
|
+
# when String; sql << " #{lock}"
|
|
125
|
+
# end
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# def default_sequence_name(table, column)
|
|
129
|
+
# nil
|
|
130
|
+
# end
|
|
131
|
+
#
|
|
132
|
+
# # Set the sequence to the max value of the table's column.
|
|
133
|
+
# def reset_sequence!(table, column, sequence = nil)
|
|
134
|
+
# # Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
|
135
|
+
# end
|
|
136
|
+
|
|
137
|
+
# Inserts the given fixture into the table. Overridden in adapters that require
|
|
138
|
+
# something beyond a simple insert (eg. Oracle).
|
|
139
|
+
def insert_fixture(fixture, table_name)
|
|
140
|
+
# execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
|
141
|
+
attributes = fixture.to_hash.dup
|
|
142
|
+
id = attributes.delete("id")
|
|
143
|
+
raise ArgumentError, "the id is missing" unless id
|
|
144
|
+
update(table_name, id, attributes, Time.now.to_bigrecord_timestamp)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# def empty_insert_statement(table_name)
|
|
148
|
+
# "INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)"
|
|
149
|
+
# end
|
|
150
|
+
#
|
|
151
|
+
# protected
|
|
152
|
+
# # Returns an array of record hashes with the column names as keys and
|
|
153
|
+
# # column values as values.
|
|
154
|
+
# def select(sql, name = nil)
|
|
155
|
+
# raise NotImplementedError, "select is an abstract method"
|
|
156
|
+
# end
|
|
157
|
+
#
|
|
158
|
+
# # Returns the last auto-generated ID from the affected table.
|
|
159
|
+
# def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
160
|
+
# execute(sql, name)
|
|
161
|
+
# id_value
|
|
162
|
+
# end
|
|
163
|
+
#
|
|
164
|
+
# # Executes the update statement and returns the number of rows affected.
|
|
165
|
+
# def update_sql(sql, name = nil)
|
|
166
|
+
# execute(sql, name)
|
|
167
|
+
# end
|
|
168
|
+
#
|
|
169
|
+
# # Executes the delete statement and returns the number of rows affected.
|
|
170
|
+
# def delete_sql(sql, name = nil)
|
|
171
|
+
# update_sql(sql, name)
|
|
172
|
+
# end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module BigRecord
|
|
2
|
+
module ConnectionAdapters # :nodoc:
|
|
3
|
+
module Quoting
|
|
4
|
+
# Quotes the column value to help prevent
|
|
5
|
+
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
|
6
|
+
def quote(value, column = nil)
|
|
7
|
+
# records are quoted as their primary key
|
|
8
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
|
9
|
+
|
|
10
|
+
case value
|
|
11
|
+
when String, ActiveSupport::Multibyte::Chars
|
|
12
|
+
value = value.to_s
|
|
13
|
+
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
|
14
|
+
"'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
|
15
|
+
elsif column && [:integer, :float].include?(column.type)
|
|
16
|
+
value = column.type == :integer ? value.to_i : value.to_f
|
|
17
|
+
value.to_s
|
|
18
|
+
else
|
|
19
|
+
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
|
20
|
+
end
|
|
21
|
+
when NilClass then "NULL"
|
|
22
|
+
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
|
23
|
+
when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
|
|
24
|
+
when Float, Fixnum, Bignum then value.to_s
|
|
25
|
+
# BigDecimals need to be output in a non-normalized form and quoted.
|
|
26
|
+
when BigDecimal then value.to_s('F')
|
|
27
|
+
when Date then "'#{value.to_s}'"
|
|
28
|
+
when Time, DateTime then "'#{quoted_date(value)}'"
|
|
29
|
+
else "'#{quote_string(value.to_yaml)}'"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
|
34
|
+
# characters.
|
|
35
|
+
def quote_string(s)
|
|
36
|
+
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns a quoted form of the column name. This is highly adapter
|
|
40
|
+
# specific.
|
|
41
|
+
def quote_column_name(name)
|
|
42
|
+
name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def quoted_true
|
|
46
|
+
"'t'"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def quoted_false
|
|
50
|
+
"'f'"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def quoted_date(value)
|
|
54
|
+
value.strftime("%Y-%m-%d %H:%M:%S")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
require 'benchmark'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
require 'bigdecimal/util'
|
|
5
|
+
|
|
6
|
+
module BigRecord
|
|
7
|
+
module ConnectionAdapters # :nodoc:
|
|
8
|
+
# All the concrete database adapters follow the interface laid down in this class.
|
|
9
|
+
# You can use this interface directly by borrowing the database connection from the Base with
|
|
10
|
+
# Base.connection.
|
|
11
|
+
#
|
|
12
|
+
# Most of the methods in the adapter are useful during migrations. Most
|
|
13
|
+
# notably, SchemaStatements#create_table, SchemaStatements#drop_table,
|
|
14
|
+
# SchemaStatements#add_index, SchemaStatements#remove_index,
|
|
15
|
+
# SchemaStatements#add_column, SchemaStatements#change_column and
|
|
16
|
+
# SchemaStatements#remove_column are very useful.
|
|
17
|
+
class AbstractAdapter
|
|
18
|
+
include Quoting, DatabaseStatements#, SchemaStatements
|
|
19
|
+
@@row_even = true
|
|
20
|
+
|
|
21
|
+
def initialize(connection, logger = nil) #:nodoc:
|
|
22
|
+
@connection, @logger = connection, logger
|
|
23
|
+
@runtime = 0
|
|
24
|
+
@last_verification = 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns the human-readable name of the adapter. Use mixed case - one
|
|
28
|
+
# can always use downcase if needed.
|
|
29
|
+
def adapter_name
|
|
30
|
+
'Abstract'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Does this adapter support migrations? Backend specific, as the
|
|
34
|
+
# abstract adapter always returns +false+.
|
|
35
|
+
def supports_migrations?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
|
40
|
+
# for all adapters except sqlite.
|
|
41
|
+
def supports_count_distinct?
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def supports_ddl_transactions?
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Should primary key values be selected from their corresponding
|
|
50
|
+
# sequence before the insert statement? If true, next_sequence_value
|
|
51
|
+
# is called before each insert to set the record's primary key.
|
|
52
|
+
# This is false for all adapters but Firebird.
|
|
53
|
+
def prefetch_primary_key?(table_name = nil)
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def reset_runtime #:nodoc:
|
|
58
|
+
rt, @runtime = @runtime, 0
|
|
59
|
+
rt
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# QUOTING ==================================================
|
|
63
|
+
|
|
64
|
+
# Override to return the quoted table name if the database needs it
|
|
65
|
+
def quote_table_name(name)
|
|
66
|
+
name
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# REFERENTIAL INTEGRITY ====================================
|
|
70
|
+
|
|
71
|
+
# Override to turn off referential integrity while executing +&block+
|
|
72
|
+
def disable_referential_integrity(&block)
|
|
73
|
+
yield
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# CONNECTION MANAGEMENT ====================================
|
|
77
|
+
|
|
78
|
+
# Is this connection active and ready to perform queries?
|
|
79
|
+
def active?
|
|
80
|
+
@active != false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Close this connection and open a new one in its place.
|
|
84
|
+
def reconnect!
|
|
85
|
+
@active = true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Close this connection
|
|
89
|
+
def disconnect!
|
|
90
|
+
@active = false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns true if its safe to reload the connection between requests for development mode.
|
|
94
|
+
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
|
|
95
|
+
def requires_reloading?
|
|
96
|
+
false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Lazily verify this connection, calling +active?+ only if it hasn't
|
|
100
|
+
# been called for +timeout+ seconds.
|
|
101
|
+
def verify!(timeout)
|
|
102
|
+
now = Time.now.to_i
|
|
103
|
+
if (now - @last_verification) > timeout
|
|
104
|
+
reconnect! unless active?
|
|
105
|
+
@last_verification = now
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Provides access to the underlying database connection. Useful for
|
|
110
|
+
# when you need to call a proprietary method such as postgresql's lo_*
|
|
111
|
+
# methods
|
|
112
|
+
def raw_connection
|
|
113
|
+
@connection
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# DATABASE STATEMENTS ======================================
|
|
117
|
+
|
|
118
|
+
def update_raw(table_name, row, values, timestamp)
|
|
119
|
+
raise NotImplementedError
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def update(table_name, row, values, timestamp)
|
|
123
|
+
raise NotImplementedError
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def get_raw(table_name, row, column, options={})
|
|
127
|
+
raise NotImplementedError
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def get(table_name, row, column, options={})
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def get_columns_raw(table_name, row, columns, options={})
|
|
135
|
+
raise NotImplementedError
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def get_columns(table_name, row, columns, options={})
|
|
139
|
+
raise NotImplementedError
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def get_consecutive_rows_raw(table_name, start_row, limit, columns, stop_row = nil)
|
|
143
|
+
raise NotImplementedError
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def get_consecutive_rows(table_name, start_row, limit, columns, stop_row = nil)
|
|
147
|
+
raise NotImplementedError
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def delete(table_name, row)
|
|
151
|
+
raise NotImplementedError
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def delete_all(table_name)
|
|
155
|
+
raise NotImplementedError
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# SCHEMA STATEMENTS ========================================
|
|
159
|
+
|
|
160
|
+
def table_exists?(table_name)
|
|
161
|
+
raise NotImplementedError
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def create_table(table_name, column_families)
|
|
165
|
+
raise NotImplementedError
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def drop_table(table_name)
|
|
169
|
+
raise NotImplementedError
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
end # class AbstractAdapter
|
|
173
|
+
end # module ConnectionAdapters
|
|
174
|
+
end # module BigRecord
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# Open the time class to add logic for the hbase timestamp
|
|
178
|
+
class Time
|
|
179
|
+
# Return this time is the hbase timestamp format, i.e. a 'long'. The 4 high bytes contain
|
|
180
|
+
# the number of seconds since epoch and the 4 low bytes contain the microseconds. That
|
|
181
|
+
# format is an arbitrary one and could have been something else.
|
|
182
|
+
def to_bigrecord_timestamp
|
|
183
|
+
(self.to_i << 32) + self.usec
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def self.from_bigrecord_timestamp(timestamp)
|
|
187
|
+
Time.at(timestamp >> 32, timestamp & 0xFFFFFFFF)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
end
|