hyper_record 0.2.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/.gitignore +3 -0
- data/CHANGELOG +83 -0
- data/LICENSE +20 -0
- data/README +49 -0
- data/Rakefile +43 -0
- data/VERSION.yml +4 -0
- data/benchmark/save.rb +58 -0
- data/hyper_record.gemspec +76 -0
- data/init.rb +1 -0
- data/lib/active_record/connection_adapters/hyper_table_definition.rb +26 -0
- data/lib/active_record/connection_adapters/hypertable_adapter.rb +680 -0
- data/lib/active_record/connection_adapters/qualified_column.rb +57 -0
- data/lib/associations/hyper_has_and_belongs_to_many_association_extension.rb +107 -0
- data/lib/associations/hyper_has_many_association_extension.rb +87 -0
- data/lib/hyper_record.rb +636 -0
- data/lib/hypertable/gen-rb/client_constants.rb +12 -0
- data/lib/hypertable/gen-rb/client_service.rb +1436 -0
- data/lib/hypertable/gen-rb/client_types.rb +253 -0
- data/lib/hypertable/gen-rb/hql_constants.rb +12 -0
- data/lib/hypertable/gen-rb/hql_service.rb +281 -0
- data/lib/hypertable/gen-rb/hql_types.rb +73 -0
- data/lib/hypertable/thrift_client.rb +94 -0
- data/lib/hypertable/thrift_transport_monkey_patch.rb +29 -0
- data/pkg/hyper_record-0.2.8.gem +0 -0
- data/spec/fixtures/pages.yml +8 -0
- data/spec/fixtures/qualified_pages.yml +1 -0
- data/spec/lib/associations_spec.rb +235 -0
- data/spec/lib/hyper_record_spec.rb +948 -0
- data/spec/lib/hypertable_adapter_spec.rb +121 -0
- data/spec/spec_helper.rb +130 -0
- data/test/test_helper.rb +10 -0
- data/test/thrift_client_test.rb +590 -0
- metadata +99 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
# Like a regular database, each table in Hypertable has a fixed list
|
4
|
+
# of columns. However, Hypertable allows flexible schemas through the
|
5
|
+
# use of column qualifiers. Suppose a table is defined to have a single
|
6
|
+
# column called misc.
|
7
|
+
#
|
8
|
+
# CREATE TABLE pages (
|
9
|
+
# 'misc'
|
10
|
+
# )
|
11
|
+
#
|
12
|
+
# In Hypertable, each traditional database column is referred to as
|
13
|
+
# a column family. Each column family can have a theoretically infinite
|
14
|
+
# number of qualified instances. An instance of a qualified column
|
15
|
+
# is referred to using the column_family:qualifer notation. e.g.,
|
16
|
+
#
|
17
|
+
# misc:red
|
18
|
+
# misc:green
|
19
|
+
# misc:blue
|
20
|
+
#
|
21
|
+
# These qualified column instances do not need to be declared as part
|
22
|
+
# of the table schema. The table schema itself does not provide
|
23
|
+
# an indication of whether a column family has been used with qualifiers.
|
24
|
+
# As a results, we must explicitly declare intent to use a column family
|
25
|
+
# in a qualified manner in our class definition. The resulting AR
|
26
|
+
# object models the column family as a Hash.
|
27
|
+
#
|
28
|
+
# class Page < ActiveRecord::HyperBase
|
29
|
+
# qualified_column :misc
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# p = Page.new
|
33
|
+
# p.ROW = 'page_1'
|
34
|
+
# p.misc['url'] = 'http://www.zvents.com/'
|
35
|
+
# p.misc['hits'] = 127
|
36
|
+
# p.save
|
37
|
+
|
38
|
+
class QualifiedColumn < Column
|
39
|
+
attr_accessor :qualifiers
|
40
|
+
|
41
|
+
def initialize(name, default, sql_type = nil, null = true)
|
42
|
+
@qualifiers ||= []
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def klass
|
47
|
+
Hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def default
|
51
|
+
h = Hash.new
|
52
|
+
h.default = @default
|
53
|
+
h
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Since Hypertable does not support join within queries, the association
|
2
|
+
# information is written to each table using qualified columns instead of
|
3
|
+
# using a separate JOIN table (as is the case for has_and_belongs_to_many
|
4
|
+
# associations in a regular RDBMS).
|
5
|
+
#
|
6
|
+
# For instance, assume that you have two models (Book and Author) in a
|
7
|
+
# has_and_belongs_to_many association. The HABTM association in a traditional
|
8
|
+
# RDBMS requires a join table (authors_books) to record the associations
|
9
|
+
# between objects. In Hypertable, instead of using a separate join table,
|
10
|
+
# each table has a column dedicated to recording the associations.
|
11
|
+
# Specifically, the books table has an author_id column and the authors
|
12
|
+
# table has a book_id column.
|
13
|
+
#
|
14
|
+
# Column qualifiers are used so we can record as many associated objects are
|
15
|
+
# necessary. If an Author with the row key charles_dickens is added to a
|
16
|
+
# Book with the row key tale_of_two_cities, then a cell called
|
17
|
+
# author_id:charles_dickens is added to the Book object and a cell
|
18
|
+
# called book_id:tale_of_two_cities is added to the Author object. The
|
19
|
+
# value of the cells is inconsequential - their presence alone indicates
|
20
|
+
# an association between the objects.
|
21
|
+
#
|
22
|
+
# When an object in an HABTM association is destroyed, the corresponding
|
23
|
+
# entries in the associated table are removed (warning: don't use delete
|
24
|
+
# because it will leave behind stale association information)
|
25
|
+
|
26
|
+
module ActiveRecord
|
27
|
+
module Associations
|
28
|
+
module HyperHasAndBelongsToManyAssociationExtension
|
29
|
+
def self.included(base)
|
30
|
+
base.class_eval do
|
31
|
+
alias_method :find_without_hypertable, :find
|
32
|
+
alias_method :find, :find_with_hypertable
|
33
|
+
|
34
|
+
alias_method :delete_records_without_hypertable, :delete_records
|
35
|
+
alias_method :delete_records, :delete_records_with_hypertable
|
36
|
+
|
37
|
+
alias_method :insert_record_without_hypertable, :insert_record
|
38
|
+
alias_method :insert_record, :insert_record_with_hypertable
|
39
|
+
|
40
|
+
alias_method :create_record_without_hypertable, :create_record
|
41
|
+
alias_method :create_record, :create_record_with_hypertable
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_with_hypertable(*args)
|
46
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
47
|
+
associated_object_ids = @owner.send(@reflection.association_foreign_key).keys
|
48
|
+
@reflection.klass.find(associated_object_ids)
|
49
|
+
else
|
50
|
+
find_without_hypertable(*args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Record the association in the assocation columns.
|
55
|
+
def insert_record_with_hypertable(record, force=true)
|
56
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
57
|
+
@owner.send(@reflection.association_foreign_key)[record.ROW] = 1
|
58
|
+
@owner.write_cells([@owner.connection.cell_native_array(@owner.ROW, @reflection.association_foreign_key, record.ROW, "1")])
|
59
|
+
record.send(@reflection.primary_key_name)[@owner.ROW] = 1
|
60
|
+
record.write_cells([@owner.connection.cell_native_array(record.ROW, @reflection.primary_key_name, @owner.ROW, "1")])
|
61
|
+
else
|
62
|
+
insert_record_without_hypertable(record, force)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Remove the association from the assocation columns.
|
67
|
+
def delete_records_with_hypertable(records)
|
68
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
69
|
+
cells_to_delete_by_table = Hash.new{|h,k| h[k] = []}
|
70
|
+
|
71
|
+
records.each {|r|
|
72
|
+
# remove association cells from in memory object
|
73
|
+
@owner.send(@reflection.association_foreign_key).delete(r.ROW)
|
74
|
+
r.send(@reflection.primary_key_name).delete(@owner.ROW)
|
75
|
+
|
76
|
+
# make list of cells that need to be removed from hypertable
|
77
|
+
cells_to_delete_by_table[@owner.class.table_name] << @owner.connection.cell_native_array(@owner.ROW, @reflection.association_foreign_key, r.ROW)
|
78
|
+
cells_to_delete_by_table[r.class.table_name] << @owner.connection.cell_native_array(r.ROW, @reflection.primary_key_name, @owner.ROW)
|
79
|
+
}
|
80
|
+
|
81
|
+
for table in cells_to_delete_by_table.keys
|
82
|
+
@owner.delete_cells(cells_to_delete_by_table[table], table)
|
83
|
+
end
|
84
|
+
else
|
85
|
+
delete_records_without_hypertable(records)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
def create_record_with_hypertable(attributes)
|
91
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
92
|
+
r = @reflection.klass.create(attributes)
|
93
|
+
insert_record_with_hypertable(r)
|
94
|
+
r
|
95
|
+
else
|
96
|
+
create_record_without_hypertable(attributes) {|record|
|
97
|
+
insert_record_without_hypertable(record, true)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class HasAndBelongsToManyAssociation
|
104
|
+
include HyperHasAndBelongsToManyAssociationExtension
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
module HyperHasManyAssociationExtension
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
alias_method :find_without_hypertable, :find
|
7
|
+
alias_method :find, :find_with_hypertable
|
8
|
+
|
9
|
+
alias_method :delete_records_without_hypertable, :delete_records
|
10
|
+
alias_method :delete_records, :delete_records_with_hypertable
|
11
|
+
|
12
|
+
alias_method :insert_record_without_hypertable, :insert_record
|
13
|
+
alias_method :insert_record, :insert_record_with_hypertable
|
14
|
+
|
15
|
+
alias_method :create_record_without_hypertable, :create_record
|
16
|
+
alias_method :create_record, :create_record_with_hypertable
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_with_hypertable(*args)
|
21
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
22
|
+
associated_object_ids = @owner.send(@reflection.association_foreign_key).keys
|
23
|
+
@reflection.klass.find(associated_object_ids)
|
24
|
+
else
|
25
|
+
find_without_hypertable(*args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def insert_record_with_hypertable(record)
|
30
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
31
|
+
raise "missing ROW key on record" if record.ROW.blank?
|
32
|
+
@owner.send(@reflection.association_foreign_key)[record.ROW] = 1
|
33
|
+
@owner.write_cells([@owner.connection.cell_native_array(
|
34
|
+
@owner.ROW,
|
35
|
+
@reflection.association_foreign_key,
|
36
|
+
record.ROW,
|
37
|
+
"1"
|
38
|
+
)])
|
39
|
+
record.send("#{@reflection.primary_key_name}=", @owner.ROW)
|
40
|
+
record.save
|
41
|
+
self.reset
|
42
|
+
else
|
43
|
+
insert_record_without_hypertable(record)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete_records_with_hypertable(records)
|
48
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
49
|
+
cells_to_delete = []
|
50
|
+
records.each {|r|
|
51
|
+
# remove association cells from in memory object
|
52
|
+
r.send("#{@reflection.primary_key_name}=", nil)
|
53
|
+
r.save
|
54
|
+
|
55
|
+
# make list of cells that need to be removed from hypertable
|
56
|
+
cells_to_delete << @owner.connection.cell_native_array(
|
57
|
+
@owner.ROW,
|
58
|
+
@reflection.association_foreign_key,
|
59
|
+
r.ROW
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
@owner.delete_cells(cells_to_delete, @owner.class.table_name)
|
64
|
+
else
|
65
|
+
delete_records_without_hypertable(records)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def create_record_with_hypertable(attributes)
|
71
|
+
if @reflection.klass <= ActiveRecord::HyperBase
|
72
|
+
r = @reflection.klass.create(attributes)
|
73
|
+
insert_record_with_hypertable(r)
|
74
|
+
r
|
75
|
+
else
|
76
|
+
create_record_without_hypertable(attributes) {|record|
|
77
|
+
insert_record_without_hypertable(record)
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class HasManyAssociation
|
84
|
+
include HyperHasManyAssociationExtension
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|