bigrecord 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +0 -0
- data/README.rdoc +9 -1
- data/Rakefile +0 -0
- data/VERSION +1 -1
- data/examples/bigrecord.yml +2 -5
- data/generators/bigrecord/bigrecord_generator.rb +0 -0
- data/generators/bigrecord/templates/bigrecord.rake +0 -0
- data/generators/bigrecord_migration/bigrecord_migration_generator.rb +0 -0
- data/generators/bigrecord_migration/templates/migration.rb +0 -0
- data/generators/bigrecord_model/bigrecord_model_generator.rb +0 -0
- data/generators/bigrecord_model/templates/migration.rb +0 -0
- data/generators/bigrecord_model/templates/model.rb +0 -0
- data/generators/bigrecord_model/templates/model_spec.rb +0 -0
- data/{doc → guides}/bigrecord_specs.rdoc +0 -0
- data/guides/deployment.rdoc +21 -0
- data/{doc → guides}/getting_started.rdoc +30 -18
- data/init.rb +0 -0
- data/install.rb +0 -0
- data/lib/big_record/abstract_base.rb +50 -28
- data/lib/big_record/action_view_extensions.rb +1 -1
- data/lib/big_record/ar_associations/association_collection.rb +0 -0
- data/lib/big_record/ar_associations/association_proxy.rb +0 -0
- data/lib/big_record/ar_associations/belongs_to_association.rb +0 -0
- data/lib/big_record/ar_associations/belongs_to_many_association.rb +0 -0
- data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +0 -0
- data/lib/big_record/ar_associations/has_many_association.rb +0 -0
- data/lib/big_record/ar_associations/has_one_association.rb +0 -0
- data/lib/big_record/ar_associations.rb +0 -0
- data/lib/big_record/ar_reflection.rb +0 -0
- data/lib/big_record/attribute_methods.rb +0 -0
- data/lib/big_record/base.rb +49 -24
- data/lib/big_record/br_associations/association_collection.rb +0 -0
- data/lib/big_record/br_associations/association_proxy.rb +0 -0
- data/lib/big_record/br_associations/belongs_to_association.rb +0 -0
- data/lib/big_record/br_associations/belongs_to_many_association.rb +0 -0
- data/lib/big_record/br_associations/cached_item_proxy.rb +0 -0
- data/lib/big_record/br_associations/cached_item_proxy_factory.rb +0 -0
- data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +0 -0
- data/lib/big_record/br_associations/has_one_association.rb +0 -0
- data/lib/big_record/br_associations.rb +0 -0
- data/lib/big_record/br_reflection.rb +0 -0
- data/lib/big_record/callbacks.rb +0 -0
- data/lib/big_record/connection_adapters/abstract/connection_specification.rb +0 -0
- data/lib/big_record/connection_adapters/abstract/database_statements.rb +0 -0
- data/lib/big_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/big_record/connection_adapters/abstract_adapter.rb +0 -0
- data/lib/big_record/connection_adapters/column.rb +30 -0
- data/lib/big_record/connection_adapters/hbase_adapter.rb +0 -0
- data/lib/big_record/connection_adapters/hbase_rest_adapter.rb +424 -0
- data/lib/big_record/connection_adapters/view.rb +63 -0
- data/lib/big_record/connection_adapters.rb +1 -0
- data/lib/big_record/deletion.rb +0 -0
- data/lib/big_record/dynamic_schema.rb +0 -0
- data/lib/big_record/embedded.rb +128 -1
- data/lib/big_record/embedded_associations/association_proxy.rb +0 -0
- data/lib/big_record/family_span_columns.rb +0 -0
- data/lib/big_record/fixtures.rb +0 -0
- data/lib/big_record/migration.rb +30 -8
- data/lib/big_record/routing_ext.rb +6 -4
- data/lib/big_record/timestamp.rb +0 -0
- data/lib/big_record/validations.rb +0 -0
- data/lib/big_record.rb +5 -3
- data/lib/bigrecord.rb +0 -0
- data/rails/init.rb +3 -2
- data/spec/adapter_benchmark.rb +55 -0
- data/spec/connections/bigrecord.yml +4 -2
- data/spec/connections/cassandra/connection.rb +0 -0
- data/spec/connections/hbase/connection.rb +0 -0
- data/spec/debug.log +156 -40724
- data/spec/integration/br_associations_spec.rb +0 -0
- data/spec/lib/animal.rb +0 -0
- data/spec/lib/book.rb +0 -0
- data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +0 -0
- data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +0 -0
- data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +0 -0
- data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +0 -0
- data/spec/lib/company.rb +0 -0
- data/spec/lib/embedded/web_link.rb +0 -0
- data/spec/lib/employee.rb +0 -0
- data/spec/lib/migrations/20090706182535_add_animals_table.rb +0 -0
- data/spec/lib/migrations/20090706190623_add_books_table.rb +0 -0
- data/spec/lib/migrations/20090706193019_add_companies_table.rb +0 -0
- data/spec/lib/migrations/20090706194512_add_employees_table.rb +0 -0
- data/spec/lib/migrations/20090706195741_add_zoos_table.rb +0 -0
- data/spec/lib/novel.rb +2 -0
- data/spec/lib/zoo.rb +0 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_helper.rb +7 -1
- data/spec/unit/abstract_base_spec.rb +0 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -0
- data/spec/unit/adapters/hbase_adapter_spec.rb +0 -0
- data/spec/unit/ar_associations_spec.rb +0 -0
- data/spec/unit/base_spec.rb +0 -0
- data/spec/unit/br_associations_spec.rb +0 -0
- data/spec/unit/embedded_spec.rb +0 -0
- data/spec/unit/find_spec.rb +0 -0
- data/spec/unit/hash_helper_spec.rb +0 -0
- data/spec/unit/migration_spec.rb +0 -0
- data/spec/unit/model_spec.rb +0 -0
- data/spec/unit/validations_spec.rb +0 -0
- data/tasks/bigrecord_tasks.rake +0 -0
- data/tasks/data_store.rb +8 -1
- data/tasks/gem.rb +2 -2
- data/tasks/rdoc.rb +14 -8
- data/tasks/spec.rb +0 -0
- metadata +10 -6
@@ -2,7 +2,7 @@ module BigRecord
|
|
2
2
|
module ConnectionAdapters # :nodoc:
|
3
3
|
module Quoting
|
4
4
|
# Quotes the column value to help prevent
|
5
|
-
#
|
5
|
+
# SQL injection attacks [http://en.wikipedia.org/wiki/SQL_injection].
|
6
6
|
def quote(value, column = nil)
|
7
7
|
# records are quoted as their primary key
|
8
8
|
return value.quoted_id if value.respond_to?(:quoted_id)
|
File without changes
|
@@ -4,6 +4,36 @@ require 'bigdecimal/util'
|
|
4
4
|
|
5
5
|
module BigRecord
|
6
6
|
module ConnectionAdapters #:nodoc:
|
7
|
+
|
8
|
+
# = Column/Attribute Definition
|
9
|
+
#
|
10
|
+
# As long as a model has at least one column family set up for it, then
|
11
|
+
# columns (a.k.a. model attributes) can then be defined for the model.
|
12
|
+
#
|
13
|
+
# The following is an example of a model named book.rb that has a
|
14
|
+
# column family called "attribute" set up for it:
|
15
|
+
#
|
16
|
+
# class Book < BigRecord::Base
|
17
|
+
# column 'attribute:title', :string
|
18
|
+
# column :author, :string
|
19
|
+
# column :description, :string
|
20
|
+
# column :links, :string, :collection => true
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# This simple model defines 4 columns of type string. An important thing
|
24
|
+
# to notice here is that the first column 'attribute:title' has the column
|
25
|
+
# family prepended to it. This is identical to just passing the symbol
|
26
|
+
# :title to the column method, and the default behaviour is to prepend
|
27
|
+
# the column family (attribute) automatically if one is not defined.
|
28
|
+
#
|
29
|
+
# Furthermore, in HBase, there's the option of storing collections for a
|
30
|
+
# given column. This will return an array for the links attribute on a
|
31
|
+
# Book record.
|
32
|
+
#
|
33
|
+
# == Types and Options
|
34
|
+
#
|
35
|
+
# @see BigRecord::AbstractBase.column
|
36
|
+
#
|
7
37
|
class Column
|
8
38
|
module Format
|
9
39
|
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
File without changes
|
@@ -0,0 +1,424 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'hbase'
|
3
|
+
|
4
|
+
module BigRecord
|
5
|
+
class Base
|
6
|
+
# Establishes a connection to the database that's used by all Active Record objects.
|
7
|
+
def self.hbase_rest_connection(config) # :nodoc:
|
8
|
+
config = config.symbolize_keys
|
9
|
+
|
10
|
+
api_address = config[:api_address]
|
11
|
+
|
12
|
+
hbase = HBase::Client.new(api_address)
|
13
|
+
|
14
|
+
ConnectionAdapters::HbaseRestAdapter.new(hbase, logger, [], config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ConnectionAdapters
|
19
|
+
class HbaseRestAdapter < AbstractAdapter
|
20
|
+
@@emulate_booleans = true
|
21
|
+
cattr_accessor :emulate_booleans
|
22
|
+
|
23
|
+
LOST_CONNECTION_ERROR_MESSAGES = [
|
24
|
+
"Server shutdown in progress",
|
25
|
+
"Broken pipe",
|
26
|
+
"Lost connection to HBase server during query",
|
27
|
+
"HBase server has gone away"
|
28
|
+
]
|
29
|
+
|
30
|
+
# data types
|
31
|
+
TYPE_NULL = 0x00;
|
32
|
+
TYPE_STRING = 0x01; # utf-8 strings
|
33
|
+
TYPE_BOOLEAN = 0x04; # delegate to YAML
|
34
|
+
TYPE_BINARY = 0x07; # byte[] => no conversion
|
35
|
+
|
36
|
+
# string charset
|
37
|
+
CHARSET = "utf-8"
|
38
|
+
|
39
|
+
# utility constants
|
40
|
+
NULL = "\000"
|
41
|
+
|
42
|
+
def initialize(connection, logger, connection_options, config)
|
43
|
+
super(connection, logger)
|
44
|
+
@connection_options, @config = connection_options, config
|
45
|
+
|
46
|
+
connect
|
47
|
+
end
|
48
|
+
|
49
|
+
def configuration
|
50
|
+
@config.clone
|
51
|
+
end
|
52
|
+
|
53
|
+
def adapter_name #:nodoc:
|
54
|
+
'HBase-Rest'
|
55
|
+
end
|
56
|
+
|
57
|
+
def supports_migrations? #:nodoc:
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
# CONNECTION MANAGEMENT ====================================
|
62
|
+
|
63
|
+
def active?
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def reconnect!
|
68
|
+
end
|
69
|
+
|
70
|
+
def disconnect!
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# DATABASE STATEMENTS ======================================
|
75
|
+
|
76
|
+
def columns_to_hbase_format(data = {})
|
77
|
+
# Convert it to the hbase-ruby format
|
78
|
+
# TODO: Add this function to hbase-ruby instead.
|
79
|
+
data.map{|col, content| {:name => col.to_s, :value => content}}.compact
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_raw(table_name, row, values, timestamp)
|
83
|
+
result = nil
|
84
|
+
|
85
|
+
columns = columns_to_hbase_format(values)
|
86
|
+
timestamp = Time.now.to_bigrecord_timestamp
|
87
|
+
|
88
|
+
log "UPDATE #{table_name} SET #{values.inspect if values} WHERE ROW=#{row};" do
|
89
|
+
@connection.create_row(table_name, row, timestamp, columns)
|
90
|
+
end
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
def update(table_name, row, values, timestamp)
|
95
|
+
serialized_collection = {}
|
96
|
+
values.each do |column, value|
|
97
|
+
serialized_collection[column] = serialize(value)
|
98
|
+
end
|
99
|
+
update_raw(table_name, row, serialized_collection, timestamp)
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_raw(table_name, row, column, options={})
|
103
|
+
result = nil
|
104
|
+
timestamp = options[:timestamp] || nil
|
105
|
+
log "SELECT (#{column}) FROM #{table_name} WHERE ROW=#{row};" do
|
106
|
+
columns = @connection.show_row(table_name, row, timestamp, column, options).columns
|
107
|
+
|
108
|
+
result = (columns.size == 1) ? columns.first.value : columns.map(&:value)
|
109
|
+
end
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
def get(table_name, row, column, options={})
|
114
|
+
serialized_result = get_raw(table_name, row, column, options)
|
115
|
+
result = nil
|
116
|
+
if serialized_result.is_a?(Array)
|
117
|
+
result = serialized_result.collect{|e| deserialize(e)}
|
118
|
+
else
|
119
|
+
result = deserialize(serialized_result)
|
120
|
+
end
|
121
|
+
result
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_columns_raw(table_name, row, columns = nil, options={})
|
125
|
+
result = {}
|
126
|
+
|
127
|
+
timestamp = options[:timestamp] || nil
|
128
|
+
|
129
|
+
log "SELECT (#{columns.join(", ")}) FROM #{table_name} WHERE ROW=#{row};" do
|
130
|
+
row = @connection.show_row(table_name, row, timestamp, columns, options)
|
131
|
+
result.merge!({'id' => row.name})
|
132
|
+
columns = row.columns
|
133
|
+
columns.each{ |col| result.merge!({col.name => col.value}) }
|
134
|
+
end
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_columns(table_name, row, columns, options={})
|
139
|
+
row_cols = get_columns_raw(table_name, row, columns, options)
|
140
|
+
return nil unless row_cols
|
141
|
+
|
142
|
+
result = {}
|
143
|
+
row_cols.each do |key,value|
|
144
|
+
begin
|
145
|
+
result[key] =
|
146
|
+
if key == 'id'
|
147
|
+
value
|
148
|
+
else
|
149
|
+
deserialize(value)
|
150
|
+
end
|
151
|
+
rescue Exception => e
|
152
|
+
puts "Could not load column value #{key} for row=#{row.name}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
result
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_consecutive_rows_raw(table_name, start_row, limit, columns, stop_row = nil)
|
159
|
+
result = nil
|
160
|
+
log "SCAN (#{columns.join(", ")}) FROM #{table_name} WHERE START_ROW=#{start_row} AND STOP_ROW=#{stop_row} LIMIT=#{limit};" do
|
161
|
+
options = {:start_row => start_row, :end_row => stop_row, :columns => columns}
|
162
|
+
scanner = @connection.open_scanner(table_name, options)
|
163
|
+
result = @connection.get_rows(scanner, limit)
|
164
|
+
@connection.close_scanner(scanner)
|
165
|
+
end
|
166
|
+
result
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_consecutive_rows(table_name, start_row, limit, columns, stop_row = nil)
|
170
|
+
rows = get_consecutive_rows_raw(table_name, start_row, limit, columns, stop_row)
|
171
|
+
|
172
|
+
result = rows.collect do |row|
|
173
|
+
cols = {}
|
174
|
+
row.columns.each do |col|
|
175
|
+
key = col.name
|
176
|
+
value = col.value
|
177
|
+
begin
|
178
|
+
cols[key] =
|
179
|
+
if key == 'id'
|
180
|
+
value
|
181
|
+
else
|
182
|
+
deserialize(value)
|
183
|
+
end
|
184
|
+
rescue Exception => e
|
185
|
+
puts "Could not load column value #{key} for row=#{row.name}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
cols
|
189
|
+
end
|
190
|
+
result
|
191
|
+
end
|
192
|
+
|
193
|
+
def delete(table_name, row, timestamp = nil)
|
194
|
+
timestamp ||= Time.now.to_bigrecord_timestamp
|
195
|
+
result = nil
|
196
|
+
log "DELETE FROM #{table_name} WHERE ROW=#{row};" do
|
197
|
+
result = @connection.delete_row(table_name, row, timestamp)
|
198
|
+
end
|
199
|
+
result
|
200
|
+
end
|
201
|
+
|
202
|
+
def truncate_table(table_name)
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
# SCHEMA STATEMENTS ========================================
|
207
|
+
|
208
|
+
def initialize_schema_migrations_table
|
209
|
+
sm_table = BigRecord::Migrator.schema_migrations_table_name
|
210
|
+
|
211
|
+
unless table_exists?(sm_table)
|
212
|
+
create_table(sm_table) do |t|
|
213
|
+
t.family :attribute, :versions => 1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_all_schema_versions
|
219
|
+
sm_table = BigRecord::Migrator.schema_migrations_table_name
|
220
|
+
|
221
|
+
get_consecutive_rows(sm_table, nil, nil, ["attribute:version"]).map{|version| version["attribute:version"]}
|
222
|
+
end
|
223
|
+
|
224
|
+
def table_exists?(table_name)
|
225
|
+
log "TABLE EXISTS? #{table_name};" do
|
226
|
+
@connection.list_tables.map(&:name).include?(table_name)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def create_table(table_name, options = {})
|
231
|
+
table_definition = TableDefinition.new
|
232
|
+
|
233
|
+
yield table_definition if block_given?
|
234
|
+
|
235
|
+
if options[:force] && table_exists?(table_name)
|
236
|
+
drop_table(table_name)
|
237
|
+
end
|
238
|
+
|
239
|
+
result = nil
|
240
|
+
log "CREATE TABLE #{table_name} (#{table_definition.column_families_list});" do
|
241
|
+
result = @connection.create_table(table_name, *table_definition.to_adapter_format)
|
242
|
+
end
|
243
|
+
result
|
244
|
+
end
|
245
|
+
|
246
|
+
def drop_table(table_name)
|
247
|
+
result = nil
|
248
|
+
log "DROP TABLE #{table_name};" do
|
249
|
+
result = @connection.destroy_table(table_name)
|
250
|
+
end
|
251
|
+
result
|
252
|
+
end
|
253
|
+
|
254
|
+
def add_column_family(table_name, column_name, options = {})
|
255
|
+
end
|
256
|
+
|
257
|
+
alias :add_family :add_column_family
|
258
|
+
|
259
|
+
def remove_column_family(table_name, column_name)
|
260
|
+
end
|
261
|
+
|
262
|
+
alias :remove_family :remove_column_family
|
263
|
+
|
264
|
+
def modify_column_family(table_name, column_name, options = {})
|
265
|
+
end
|
266
|
+
|
267
|
+
alias :modify_family :modify_column_family
|
268
|
+
|
269
|
+
# Serialize the given value
|
270
|
+
def serialize(value)
|
271
|
+
case value
|
272
|
+
when NilClass then NULL
|
273
|
+
when String then build_serialized_value(TYPE_STRING, value)
|
274
|
+
else value.to_yaml
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Serialize an object in a given type
|
279
|
+
def build_serialized_value(type, value)
|
280
|
+
type.chr + value
|
281
|
+
end
|
282
|
+
|
283
|
+
# Deserialize the given string. This method supports both the pure YAML format and
|
284
|
+
# the type header format.
|
285
|
+
def deserialize(str)
|
286
|
+
return unless str
|
287
|
+
|
288
|
+
# stay compatible with the old serialization code
|
289
|
+
# YAML documents start with "--- " so if we find that sequence at the beginning we
|
290
|
+
# consider it as a serialized YAML value, else it's the new format with the type header
|
291
|
+
if str[0..3] == "--- "
|
292
|
+
YAML::load(str) if str
|
293
|
+
else
|
294
|
+
deserialize_with_header(str)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Deserialize the given string assumed to be in the type header format.
|
299
|
+
def deserialize_with_header(data)
|
300
|
+
return unless data and data.size >= 2
|
301
|
+
|
302
|
+
# the type of the data is encoded in the first byte
|
303
|
+
type = data[0];
|
304
|
+
|
305
|
+
case type
|
306
|
+
when TYPE_NULL then nil
|
307
|
+
when TYPE_STRING then data[1..-1]
|
308
|
+
when TYPE_BINARY then data[1..-1]
|
309
|
+
else nil
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
private
|
314
|
+
|
315
|
+
def connect
|
316
|
+
end
|
317
|
+
|
318
|
+
protected
|
319
|
+
def log(str, name = nil)
|
320
|
+
if block_given?
|
321
|
+
if @logger and @logger.level <= Logger::INFO
|
322
|
+
result = nil
|
323
|
+
seconds = Benchmark.realtime { result = yield }
|
324
|
+
@runtime += seconds
|
325
|
+
log_info(str, name, seconds)
|
326
|
+
result
|
327
|
+
else
|
328
|
+
yield
|
329
|
+
end
|
330
|
+
else
|
331
|
+
log_info(str, name, 0)
|
332
|
+
nil
|
333
|
+
end
|
334
|
+
rescue Exception => e
|
335
|
+
# Log message and raise exception.
|
336
|
+
# Set last_verfication to 0, so that connection gets verified
|
337
|
+
# upon reentering the request loop
|
338
|
+
@last_verification = 0
|
339
|
+
message = "#{e.class.name}: #{e.message}: #{str}"
|
340
|
+
log_info(message, name, 0)
|
341
|
+
raise e
|
342
|
+
end
|
343
|
+
|
344
|
+
def log_info(str, name, runtime)
|
345
|
+
return unless @logger
|
346
|
+
|
347
|
+
@logger.debug(
|
348
|
+
format_log_entry(
|
349
|
+
"#{name.nil? ? "HBASE" : name} (#{sprintf("%f", runtime)})",
|
350
|
+
str.gsub(/ +/, " ")
|
351
|
+
)
|
352
|
+
)
|
353
|
+
end
|
354
|
+
|
355
|
+
def format_log_entry(message, dump = nil)
|
356
|
+
if BigRecord::Base.colorize_logging
|
357
|
+
if @@row_even
|
358
|
+
@@row_even = false
|
359
|
+
message_color, dump_color = "4;36;1", "0;1"
|
360
|
+
else
|
361
|
+
@@row_even = true
|
362
|
+
message_color, dump_color = "4;35;1", "0"
|
363
|
+
end
|
364
|
+
|
365
|
+
log_entry = " \e[#{message_color}m#{message}\e[0m "
|
366
|
+
log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
|
367
|
+
log_entry
|
368
|
+
else
|
369
|
+
"%s %s" % [message, dump]
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
class TableDefinition
|
375
|
+
|
376
|
+
##
|
377
|
+
# Given an column descriptor's options hash from Bigrecord, translate it into
|
378
|
+
# the format for this adapter's ColumnDescriptor.
|
379
|
+
#
|
380
|
+
def self.translate_to_adapter_format(options)
|
381
|
+
# Translating to the hbase-ruby column descriptor
|
382
|
+
# TODO: Refactor this
|
383
|
+
hbase_params = {}
|
384
|
+
|
385
|
+
hbase_params[:name] = options[:name].to_s if options[:name]
|
386
|
+
hbase_params[:compression] = options[:compression] if options[:compression]
|
387
|
+
hbase_params[:max_versions] = options[:versions] if options[:versions]
|
388
|
+
hbase_params[:bloomfilter] = options[:bloom_filter] if options[:bloom_filter]
|
389
|
+
|
390
|
+
hbase_params
|
391
|
+
end
|
392
|
+
|
393
|
+
def initialize
|
394
|
+
@column_families = []
|
395
|
+
end
|
396
|
+
|
397
|
+
# Returns a column family for the column with name +name+.
|
398
|
+
def [](name)
|
399
|
+
@column_families.find {|column| column[:name].to_s == name.to_s}
|
400
|
+
end
|
401
|
+
|
402
|
+
def column_family(name, options = {})
|
403
|
+
hbase_params = self.class.translate_to_adapter_format(options.merge({:name => name}))
|
404
|
+
|
405
|
+
column = self[name] || hbase_params
|
406
|
+
|
407
|
+
@column_families << column unless @column_families.include? column
|
408
|
+
self
|
409
|
+
end
|
410
|
+
|
411
|
+
alias :family :column_family
|
412
|
+
|
413
|
+
def to_adapter_format
|
414
|
+
@column_families
|
415
|
+
end
|
416
|
+
|
417
|
+
def column_families_list
|
418
|
+
@column_families.map{|x| x[:name]}.join(", ")
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
|
423
|
+
end
|
424
|
+
end
|
@@ -1,5 +1,68 @@
|
|
1
1
|
module BigRecord
|
2
2
|
module ConnectionAdapters
|
3
|
+
|
4
|
+
# = Named Views
|
5
|
+
#
|
6
|
+
# To use column-oriented databases more efficiently, it helps to know
|
7
|
+
# exactly which specific columns are required for a query.
|
8
|
+
#
|
9
|
+
# For example, supposing we're designing two queries:
|
10
|
+
# (A) used on the front page of a website.
|
11
|
+
# (B) used on the summary page for a particular item.
|
12
|
+
#
|
13
|
+
# Since we know these two queries will be serving up different information,
|
14
|
+
# we can also figure out which columns are needed by each. In general,
|
15
|
+
# query (A) would only require a subset of columns of (B). This
|
16
|
+
# functionality is analogous to the SQL command "SELECT column1,column2 ..."
|
17
|
+
# for defining specific columns to query for.
|
18
|
+
#
|
19
|
+
# Named views are defined in models by using the {BigRecord::Base.view}
|
20
|
+
# macro like in the following example:
|
21
|
+
#
|
22
|
+
# class Book < BigRecord::Base
|
23
|
+
# column :title, :string
|
24
|
+
# column :author, :string
|
25
|
+
# column :description, :string
|
26
|
+
# column :links, :string, :collection => true
|
27
|
+
#
|
28
|
+
# view :front_page, :title, :author
|
29
|
+
# view :summary_page, :title, :author, :description
|
30
|
+
#
|
31
|
+
# # Override default if you don't want all columns returned
|
32
|
+
# view :default, :title, :author, :description
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Now, whenever you work with a Book record, it will only returned the
|
36
|
+
# columns you specify according to the view option you pass. i.e.
|
37
|
+
#
|
38
|
+
# >> Book.find(:first, :view => :front_page)
|
39
|
+
# => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:title: "Hello Thar", attribute:author: "Greg">
|
40
|
+
#
|
41
|
+
# >> Book.find(:first, :view => :summary_page)
|
42
|
+
# => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:description: "Masterpiece!", attribute:title: "Hello Thar", attribute:author: "Greg">
|
43
|
+
#
|
44
|
+
# >> Book.find(:first, :view => :default)
|
45
|
+
# => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:description: "Masterpiece!", attribute:title: "Hello Thar", attribute:links: ["link1", "link2", "link3", "link4"], attribute:author: "Greg">
|
46
|
+
#
|
47
|
+
# Any attributes that were not loaded in as part of the view will be
|
48
|
+
# lazy-loaded on request. This is very inefficient and should be avoided
|
49
|
+
# at all costs! It's better to load more columns in, than to have
|
50
|
+
# any attributes lazy-loaded afterwards.
|
51
|
+
#
|
52
|
+
# Note: A Bigrecord model will return all the columns within the default
|
53
|
+
# column family (when :view option is left blank, for example).
|
54
|
+
# You can override the :default name view to change this behaviour.
|
55
|
+
#
|
56
|
+
# It's also possible to define specific columns to load at query time.
|
57
|
+
# To do this, you can just pass an array of columns to the :columns
|
58
|
+
# option of the find method and it will return only those attributes:
|
59
|
+
#
|
60
|
+
# >> Book.find(:first, :columns => [:author, :description])
|
61
|
+
# => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:description: "Masterpiece!", attribute:author: "Greg">
|
62
|
+
#
|
63
|
+
# Once again, any attributes not loaded during query time will be
|
64
|
+
# lazy-loaded on request.
|
65
|
+
#
|
3
66
|
class View
|
4
67
|
attr_reader :name, :owner
|
5
68
|
|
data/lib/big_record/deletion.rb
CHANGED
File without changes
|
File without changes
|
data/lib/big_record/embedded.rb
CHANGED
@@ -1,9 +1,134 @@
|
|
1
1
|
module BigRecord
|
2
|
+
|
3
|
+
# = Embedded Records
|
4
|
+
#
|
5
|
+
# Since a single column in a column-oriented database is perfectly suited
|
6
|
+
# to handle large amounts of data, BigRecord gives you the option to store
|
7
|
+
# entire records within a single column of another {BigRecord::Base} record.
|
8
|
+
#
|
9
|
+
# These are known as Embedded records, and they behave similarly to
|
10
|
+
# {BigRecord::Base} objects, except that their data is physically stored
|
11
|
+
# within another {BigRecord::Base} record, and they don't exist unless
|
12
|
+
# associated with one. Furthermore, they don't possess any find or querying
|
13
|
+
# functionality.
|
14
|
+
#
|
15
|
+
# So what are the benefits of Embedded records?
|
16
|
+
# * Cleaner organization of models
|
17
|
+
# * Avoids the need to create entire tables and associations for models that
|
18
|
+
# exist only in the context of another model.
|
19
|
+
# * Allows more complicated functionality to be encompassed within an
|
20
|
+
# embedded record, instead of in a parent model.
|
21
|
+
#
|
22
|
+
# All of this has been very abstract so far, therefore examples are in order.
|
23
|
+
#
|
24
|
+
# == Examples
|
25
|
+
#
|
26
|
+
# Let's say we start off with the following arbitrarily created models:
|
27
|
+
#
|
28
|
+
# app/model/book.rb:
|
29
|
+
# class Book < BigRecord::Base
|
30
|
+
# column :title, :string
|
31
|
+
# column :author, :string
|
32
|
+
# column :description, :string
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# app/model/company.rb:
|
36
|
+
# class Company < BigRecord::Base
|
37
|
+
# column :name, :string
|
38
|
+
# column :address, :string
|
39
|
+
# column :description, :string
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# Now, let's say we want the ability to create and associate weblinks
|
43
|
+
# with each of these models. This is a trivial modification if all we do is
|
44
|
+
# create a new column in each model called "weblink" (or something similar),
|
45
|
+
# and have a string that stores a URL.
|
46
|
+
#
|
47
|
+
# However, what if we wanted these weblinks to have more metadata attached
|
48
|
+
# and more complex functionality added to it? Then our only choice is to
|
49
|
+
# create a new model called WebLink (for example), with its own table, set of
|
50
|
+
# attributes and methods. Then we have our Book and Company models associate
|
51
|
+
# to these newly created WebLink models, giving us something like this:
|
52
|
+
#
|
53
|
+
# app/model/web_link.rb:
|
54
|
+
# class WebLink < BigRecord::Base
|
55
|
+
# column :name, :string
|
56
|
+
# column :url, :string
|
57
|
+
# column :description, :string
|
58
|
+
# column :submitted_by, :string
|
59
|
+
# column :book_id, :string
|
60
|
+
# column :company_id, :string
|
61
|
+
#
|
62
|
+
# # Could use a polymorphic association here, of course.
|
63
|
+
# belongs_to_bigrecord :book, :foreign_key => "attribute:book_id"
|
64
|
+
# belongs_to_bigrecord :company, :foreign_key => "attribute:company_id"
|
65
|
+
#
|
66
|
+
# # other methods ...
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# and likewise an association to WebLink from the Book and Company models.
|
70
|
+
#
|
71
|
+
# Now notice the problem here? A simple concept like adding a WebLink with
|
72
|
+
# some metadata has increased the model logic and created some unnecessary
|
73
|
+
# associations. In this situation, a WebLink doesn't need to exist except
|
74
|
+
# when associated to a certain model. Therefore this association should be
|
75
|
+
# implicit somehow.
|
76
|
+
#
|
77
|
+
# Enter Embedded records. We will now instead define WebLink as follows:
|
78
|
+
#
|
79
|
+
# app/model/web_link.rb:
|
80
|
+
# class WebLink < BigRecord::Embedded
|
81
|
+
# column :name, :string
|
82
|
+
# column :url, :string
|
83
|
+
# column :description, :string
|
84
|
+
# column :submitted_by, :string
|
85
|
+
#
|
86
|
+
# # other methods ...
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# And modify our Base records like so:
|
90
|
+
#
|
91
|
+
# app/model/book.rb:
|
92
|
+
# class Book < BigRecord::Base
|
93
|
+
# column :title, :string
|
94
|
+
# column :author, :string
|
95
|
+
# column :description, :string
|
96
|
+
# column :web_link, 'WebLink'
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# app/model/company.rb:
|
100
|
+
# class Company < BigRecord::Base
|
101
|
+
# column :name, :string
|
102
|
+
# column :address, :string
|
103
|
+
# column :description, :string
|
104
|
+
# column :web_link, 'WebLink'
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# Now we can encompass any WebLink specific attributes and methods within
|
108
|
+
# the WebLink embedded class, and use them with any other Base model.
|
109
|
+
#
|
110
|
+
# To use WebLink now, we treat it as though it were a regular model, except
|
111
|
+
# that we don't execute saves on it. For example:
|
112
|
+
#
|
113
|
+
# >> amazon_link = WebLink.new(:name => "Amazon Link to Book", :url => "http://amazon.com/some/book", :description => "Amazon sells this book for cheap!")
|
114
|
+
# => #<WebLink created_at: "2009-11-12 17:11:57", name: "Amazon Link to Book", url: "http://amazon.com/some/book", description: "Amazon sells this book for cheap!", submitted_by: nil, id: "2b619a68-e462-475d-8e04-01ba2aace11a">
|
115
|
+
# >> amazon_link.save
|
116
|
+
# BigRecord::NotImplemented: BigRecord::NotImplemented
|
117
|
+
# # => [...]
|
118
|
+
# >> book = Book.find(:first)
|
119
|
+
# # => [...]
|
120
|
+
# >> book.web_link = amazon_link
|
121
|
+
# >> book.save
|
122
|
+
#
|
123
|
+
# Now any subsequent access to that book object we just saved to will have
|
124
|
+
# a WebLink record available with it.
|
125
|
+
#
|
2
126
|
class Embedded < AbstractBase
|
3
127
|
|
4
128
|
def initialize(attrs = nil)
|
5
129
|
super
|
6
|
-
# Regenerate the id unless it's already there
|
130
|
+
# Regenerate the id unless it's already there
|
131
|
+
# (i.e. we're instantiating an existing property)
|
7
132
|
@attributes["id"] ||= generate_id
|
8
133
|
end
|
9
134
|
|
@@ -16,11 +141,13 @@ module BigRecord
|
|
16
141
|
end
|
17
142
|
|
18
143
|
protected
|
144
|
+
|
19
145
|
def generate_id
|
20
146
|
UUIDTools::UUID.random_create.to_s
|
21
147
|
end
|
22
148
|
|
23
149
|
public
|
150
|
+
|
24
151
|
class << self
|
25
152
|
def store_primary_key?
|
26
153
|
true
|