bigrecord 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|