bigrecord 0.0.7 → 0.0.8
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/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
|