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,94 @@
|
|
1
|
+
# include path hack
|
2
|
+
$:.push(File.dirname(__FILE__) + '/gen-rb')
|
3
|
+
|
4
|
+
require 'thrift'
|
5
|
+
require 'thrift/protocol/binary_protocol_accelerated'
|
6
|
+
require 'hql_service'
|
7
|
+
require File.dirname(__FILE__) + '/thrift_transport_monkey_patch'
|
8
|
+
|
9
|
+
module Hypertable
|
10
|
+
class ThriftClient < ThriftGen::HqlService::Client
|
11
|
+
def initialize(host, port = 38080, timeout_ms = 20000, do_open = true)
|
12
|
+
socket = Thrift::Socket.new(host, port, timeout_ms)
|
13
|
+
@transport = Thrift::FramedTransport.new(socket)
|
14
|
+
protocol = Thrift::BinaryProtocolAccelerated.new(@transport)
|
15
|
+
super(protocol)
|
16
|
+
open() if do_open
|
17
|
+
end
|
18
|
+
|
19
|
+
def open()
|
20
|
+
@transport.open()
|
21
|
+
@do_close = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def close()
|
25
|
+
@transport.close() if @do_close
|
26
|
+
end
|
27
|
+
|
28
|
+
# more convenience methods
|
29
|
+
|
30
|
+
def with_scanner(table, scan_spec)
|
31
|
+
scanner = open_scanner(table, scan_spec, true)
|
32
|
+
begin
|
33
|
+
yield scanner
|
34
|
+
ensure
|
35
|
+
close_scanner(scanner)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_mutator(table, flags=0, flush_interval=0)
|
40
|
+
mutator = open_mutator(table, flags, flush_interval);
|
41
|
+
begin
|
42
|
+
yield mutator
|
43
|
+
ensure
|
44
|
+
close_mutator(mutator, 0)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# scanner iterator
|
49
|
+
def each_cell(scanner)
|
50
|
+
cells = next_cells(scanner);
|
51
|
+
|
52
|
+
while (cells.size > 0)
|
53
|
+
cells.each {|cell| yield cell}
|
54
|
+
cells = next_cells(scanner);
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_cell_as_arrays(scanner)
|
59
|
+
cells = next_cells_as_arrays(scanner);
|
60
|
+
|
61
|
+
while (cells.size > 0)
|
62
|
+
cells.each {|cell| yield cell}
|
63
|
+
cells = next_cells_as_arrays(scanner);
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def each_row(scanner)
|
68
|
+
row = next_row(scanner);
|
69
|
+
|
70
|
+
while (row && row.size > 0)
|
71
|
+
yield row
|
72
|
+
row = next_row(scanner);
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def each_row_as_arrays(scanner)
|
77
|
+
row = next_row_as_arrays(scanner);
|
78
|
+
|
79
|
+
while (row && row.size > 0)
|
80
|
+
yield row
|
81
|
+
row = next_row_as_arrays(scanner);
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.with_thrift_client(host, port, timeout_ms = 20000)
|
87
|
+
client = ThriftClient.new(host, port, timeout_ms)
|
88
|
+
begin
|
89
|
+
yield client
|
90
|
+
ensure
|
91
|
+
client.close()
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Monkey patch for Thrift::FramedTransport typically found in:
|
2
|
+
# /usr/local/lib/ruby/site_ruby/1.8/thrift/transport/framed_transport.rb
|
3
|
+
#
|
4
|
+
# Rev 765279 (current recommended Thrift rev for Hypertable) does not
|
5
|
+
# reset buffer state when the connection is reopened. This patch will
|
6
|
+
# be submitted to Thrift once it has been tested for a few weeks in
|
7
|
+
# production.
|
8
|
+
|
9
|
+
module Thrift
|
10
|
+
class FramedTransport < BaseTransport
|
11
|
+
def initialize(transport, read=true, write=true)
|
12
|
+
reset_state
|
13
|
+
@transport = transport
|
14
|
+
@read = read
|
15
|
+
@write = write
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset_state
|
19
|
+
@rbuf = ''
|
20
|
+
@wbuf = ''
|
21
|
+
@index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def open
|
25
|
+
reset_state
|
26
|
+
@transport.open
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
# intentionally left blank
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper.rb')
|
2
|
+
|
3
|
+
class Book < ActiveRecord::HyperBase
|
4
|
+
has_many :chapters
|
5
|
+
has_and_belongs_to_many :authors
|
6
|
+
qualified_column :author_id, :qualifiers => ['.*']
|
7
|
+
qualified_column :chapter_id, :qualifiers => ['.*']
|
8
|
+
|
9
|
+
def self.create_table
|
10
|
+
hql = "CREATE TABLE #{table_name} (
|
11
|
+
'title',
|
12
|
+
'author_id',
|
13
|
+
'chapter_id'
|
14
|
+
)"
|
15
|
+
connection.execute(hql)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Chapter < ActiveRecord::HyperBase
|
20
|
+
belongs_to :book
|
21
|
+
|
22
|
+
def self.create_table
|
23
|
+
hql = "CREATE TABLE #{table_name} (
|
24
|
+
'title',
|
25
|
+
'book_id' MAX_VERSIONS=1
|
26
|
+
)"
|
27
|
+
connection.execute(hql)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Author < ActiveRecord::HyperBase
|
32
|
+
has_and_belongs_to_many :books
|
33
|
+
qualified_column :book_id, :qualifiers => ['.*']
|
34
|
+
|
35
|
+
def self.create_table
|
36
|
+
hql = "CREATE TABLE #{table_name} (
|
37
|
+
'name',
|
38
|
+
'book_id'
|
39
|
+
)"
|
40
|
+
connection.execute(hql)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ActiveRecord
|
45
|
+
module HyperRecord
|
46
|
+
describe HyperBase, '.has_and_belongs_to_many' do
|
47
|
+
before(:each) do
|
48
|
+
Book.drop_table
|
49
|
+
Author.drop_table
|
50
|
+
Book.create_table
|
51
|
+
Author.create_table
|
52
|
+
|
53
|
+
@b = Book.new({:title => "Curious George and the Electric Fence"})
|
54
|
+
@b.ROW = 'curious_george'
|
55
|
+
@b.save!
|
56
|
+
|
57
|
+
@a1 = Author.new({:name => 'Irvine Welsh', :ROW => 'irvine_welsh'})
|
58
|
+
@a1.save!
|
59
|
+
@a2 = Author.new({:name => 'Douglas Adams', :ROW => 'douglas_adams'})
|
60
|
+
@a2.save!
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should support addition of association elements using <<" do
|
64
|
+
@b.authors.should be_empty
|
65
|
+
@b.authors << @a1
|
66
|
+
@b.authors.should == [@a1]
|
67
|
+
@b.reload
|
68
|
+
@b.authors.should == [@a1]
|
69
|
+
@a1.books.should == [@b]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should allow multiple objects to be associated through HABTM" do
|
73
|
+
@b.authors.should be_empty
|
74
|
+
@b.authors << @a1
|
75
|
+
@b.authors << @a2
|
76
|
+
@b.authors.map{|x| x.ROW}.sort.should == [@a1, @a2].map{|x| x.ROW}.sort
|
77
|
+
@b.reload
|
78
|
+
@b.authors.map{|x| x.ROW}.sort.should == [@a1, @a2].map{|x| x.ROW}.sort
|
79
|
+
@a1.books.map{|x| x.ROW}.sort.should == [@b].map{|x| x.ROW}.sort
|
80
|
+
@a2.books.map{|x| x.ROW}.sort.should == [@b].map{|x| x.ROW}.sort
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should allow removal of association elements using clear" do
|
84
|
+
@b.authors.should be_empty
|
85
|
+
@b.authors << @a1
|
86
|
+
@b.authors.should == [@a1]
|
87
|
+
@b.authors.clear
|
88
|
+
@b.reload
|
89
|
+
@b.authors.should be_empty
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should allow an object to be created through the association" do
|
93
|
+
a = @b.authors.create({:name => 'Harper Lee', :ROW => 'harper_lee'})
|
94
|
+
a.new_record?.should be_false
|
95
|
+
a.reload
|
96
|
+
a.books.should == [@b]
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should allow an object to be newed through the association" do
|
100
|
+
@b.authors.should be_empty
|
101
|
+
a = @b.authors.new({:name => 'Harper Lee', :ROW => 'harper_lee'})
|
102
|
+
a.new_record?.should be_true
|
103
|
+
a.save!
|
104
|
+
a.reload
|
105
|
+
a.books.should be_empty
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should allow removal of association elements using delete" do
|
109
|
+
@b.authors.should be_empty
|
110
|
+
@b.authors << @a1
|
111
|
+
@b.authors << @a2
|
112
|
+
@b.authors.delete(@a2)
|
113
|
+
@b.reload
|
114
|
+
@b.authors.should == [@a1]
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should clean up association cells when an object is destroyed" do
|
118
|
+
@b.authors.should be_empty
|
119
|
+
@b.authors << @a1
|
120
|
+
@b.author_id.should == {@a1.ROW => 1}
|
121
|
+
@a1.destroy
|
122
|
+
@b.reload
|
123
|
+
@b.authors.should be_empty
|
124
|
+
@b.author_id.should == {}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe HyperBase, '.belongs_to_and_has_many' do
|
129
|
+
before(:each) do
|
130
|
+
Book.drop_table
|
131
|
+
Chapter.drop_table
|
132
|
+
Book.create_table
|
133
|
+
Chapter.create_table
|
134
|
+
|
135
|
+
@b = Book.new({:title => "Curious George and the Electric Fence"})
|
136
|
+
@b.ROW = 'curious_george'
|
137
|
+
@b.save!
|
138
|
+
|
139
|
+
@c1 = Chapter.new({:title => 'Ch 1', :ROW => 'c1'})
|
140
|
+
@c1.save!
|
141
|
+
@b.chapters << @c1
|
142
|
+
@c2 = Chapter.new({:title => 'Ch 2', :ROW => 'c2'})
|
143
|
+
@c2.save!
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should allow belongs_to assocs between two hyperrecord objects" do
|
147
|
+
@c1.book.should == @b
|
148
|
+
@c2.book.should == nil
|
149
|
+
@b.chapters.to_a.should == [@c1]
|
150
|
+
@b.chapters << @c2
|
151
|
+
@b.reload
|
152
|
+
@b.chapters.to_a.should == [@c1, @c2]
|
153
|
+
@c2.book_id.should == @b.ROW
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should clear has_many associations when requested" do
|
157
|
+
@b.chapters.to_a.should == [@c1]
|
158
|
+
@b.chapters.clear
|
159
|
+
@b.reload
|
160
|
+
@b.chapters.to_a.should be_empty
|
161
|
+
@b.chapter_id.should == {}
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should allow new records through has_many but note that the association cells are not written, so this method is to be avoided" do
|
165
|
+
@b.chapters.to_a.should == [@c1]
|
166
|
+
c = @b.chapters.new({:ROW => 'c3', :title => 'Ch 3'})
|
167
|
+
c.new_record?.should be_true
|
168
|
+
c.save!
|
169
|
+
@b.reload
|
170
|
+
@b.chapters.length.should == 1
|
171
|
+
@b.chapters.should == [@c1]
|
172
|
+
@b.chapter_id.should == {@c1.ROW => "1"}
|
173
|
+
@b.chapters << c
|
174
|
+
@b.reload
|
175
|
+
@b.chapters.should == [@c1, c]
|
176
|
+
@b.chapter_id.should == {@c1.ROW => "1", c.ROW => "1"}
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should allow create records through has_many" do
|
180
|
+
@b.chapters.to_a.should == [@c1]
|
181
|
+
c = @b.chapters.create({:ROW => 'c3', :title => 'Ch 3'})
|
182
|
+
c.new_record?.should be_false
|
183
|
+
@b.reload
|
184
|
+
@b.chapters.length.should == 2
|
185
|
+
@b.chapters.should == [@c1, c]
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should allow new records using << has_many" do
|
189
|
+
@b.chapters.to_a.should == [@c1]
|
190
|
+
c = Chapter.new({:ROW => 'c3', :title => 'Ch 3'})
|
191
|
+
c.new_record?.should be_true
|
192
|
+
@b.chapters << c
|
193
|
+
@b.reload
|
194
|
+
@b.chapters.length.should == 2
|
195
|
+
@b.chapters.should == [@c1, c]
|
196
|
+
c.reload
|
197
|
+
c.book.should == @b
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should support remove of has_many records through delete" do
|
201
|
+
@b.chapters.to_a.should == [@c1]
|
202
|
+
c = @b.chapters.create({:ROW => 'c3', :title => 'Ch 3'})
|
203
|
+
@b.reload
|
204
|
+
@b.chapters.should == [@c1, c]
|
205
|
+
@b.chapter_id.should == {@c1.ROW => "1", c.ROW => "1"}
|
206
|
+
@b.chapters.delete(@c1)
|
207
|
+
@b.reload
|
208
|
+
@b.chapters.should == [c]
|
209
|
+
@b.chapter_id.should == {c.ROW => "1"}
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should update belongs_to id value on assignment" do
|
213
|
+
@c2.book_id.should be_blank
|
214
|
+
@c2.book = @b
|
215
|
+
@c2.save!
|
216
|
+
@c2.reload
|
217
|
+
@c2.book_id.should == @b.id
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should silently ignore eager loading of belongs_to associations" do
|
221
|
+
c1 = Chapter.find(@c1.ROW, :include => [:book])
|
222
|
+
# note: no exception, loaded? is marked as true and assoc still works
|
223
|
+
c1.book.loaded?.should be_true
|
224
|
+
c1.book.should == @b
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should silently ignore eager loading of has_many associations" do
|
228
|
+
b = Book.find(@b.ROW, :include => [:chapters])
|
229
|
+
# note: no exception, loaded? is marked as false and assoc still works
|
230
|
+
b.chapters.loaded?.should be_false
|
231
|
+
b.chapters.to_a.should == [@c1]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|