ok_hbase 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +22 -0
- data/examples/README.md +46 -0
- data/examples/advanced/README.md +36 -0
- data/examples/advanced/perf_read.rb +146 -0
- data/examples/advanced/perf_write.rb +143 -0
- data/examples/advanced/table_read.rb +115 -0
- data/examples/advanced/table_write.rb +128 -0
- data/examples/table_scan.rb +97 -0
- data/examples/table_write.rb +97 -0
- data/lib/ok_hbase/active_model.rb +35 -0
- data/lib/ok_hbase/client.rb +42 -0
- data/lib/ok_hbase/concerns/custom_row/class_methods.rb +13 -0
- data/lib/ok_hbase/concerns/custom_row.rb +40 -0
- data/lib/ok_hbase/concerns/indexable/class_methods.rb +13 -0
- data/lib/ok_hbase/concerns/indexable.rb +101 -0
- data/lib/ok_hbase/concerns/row.rb +85 -0
- data/lib/ok_hbase/concerns/table/batch.rb +95 -0
- data/lib/ok_hbase/concerns/table/class_methods.rb +13 -0
- data/lib/ok_hbase/concerns/table/instrumentation.rb +48 -0
- data/lib/ok_hbase/concerns/table.rb +241 -0
- data/lib/ok_hbase/concerns.rb +13 -0
- data/lib/ok_hbase/connection.rb +157 -0
- data/lib/ok_hbase/row.rb +21 -0
- data/lib/ok_hbase/table.rb +10 -0
- data/lib/ok_hbase/version.rb +3 -0
- data/lib/ok_hbase.rb +39 -0
- data/lib/thrift/hbase/hbase.rb +2643 -0
- data/lib/thrift/hbase/hbase_constants.rb +14 -0
- data/lib/thrift/hbase/hbase_types.rb +252 -0
- data/ok-hbase.gemspec +23 -0
- data/spec/ok_hbase/connection_spec.rb +99 -0
- data/spec/ok_hbase/table_spec.rb +149 -0
- data/spec/ok_hbase_spec.rb +24 -0
- data/spec/spec_helper.rb +20 -0
- data/tasks/bump.rb +30 -0
- metadata +122 -0
@@ -0,0 +1,252 @@
|
|
1
|
+
#
|
2
|
+
# Autogenerated by Thrift Compiler (0.9.0)
|
3
|
+
#
|
4
|
+
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
5
|
+
#
|
6
|
+
|
7
|
+
module Apache
|
8
|
+
module Hadoop
|
9
|
+
module Hbase
|
10
|
+
module Thrift
|
11
|
+
# TCell - Used to transport a cell value (byte[]) and the timestamp it was
|
12
|
+
# stored with together as a result for get and getRow methods. This promotes
|
13
|
+
# the timestamp of a cell to a first-class value, making it easy to take
|
14
|
+
# note of temporal data. Cell is used all the way from HStore up to HTable.
|
15
|
+
class TCell
|
16
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
17
|
+
VALUE = 1
|
18
|
+
TIMESTAMP = 2
|
19
|
+
|
20
|
+
FIELDS = {
|
21
|
+
VALUE => {:type => ::Thrift::Types::STRING, :name => 'value', :binary => true},
|
22
|
+
TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp'}
|
23
|
+
}
|
24
|
+
|
25
|
+
def struct_fields; FIELDS; end
|
26
|
+
|
27
|
+
def validate
|
28
|
+
end
|
29
|
+
|
30
|
+
::Thrift::Struct.generate_accessors self
|
31
|
+
end
|
32
|
+
|
33
|
+
# An HColumnDescriptor contains information about a column family
|
34
|
+
# such as the number of versions, compression settings, etc. It is
|
35
|
+
# used as input when creating a table or adding a column.
|
36
|
+
class ColumnDescriptor
|
37
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
38
|
+
NAME = 1
|
39
|
+
MAXVERSIONS = 2
|
40
|
+
COMPRESSION = 3
|
41
|
+
INMEMORY = 4
|
42
|
+
BLOOMFILTERTYPE = 5
|
43
|
+
BLOOMFILTERVECTORSIZE = 6
|
44
|
+
BLOOMFILTERNBHASHES = 7
|
45
|
+
BLOCKCACHEENABLED = 8
|
46
|
+
TIMETOLIVE = 9
|
47
|
+
|
48
|
+
FIELDS = {
|
49
|
+
NAME => {:type => ::Thrift::Types::STRING, :name => 'name', :binary => true},
|
50
|
+
MAXVERSIONS => {:type => ::Thrift::Types::I32, :name => 'maxVersions', :default => 3},
|
51
|
+
COMPRESSION => {:type => ::Thrift::Types::STRING, :name => 'compression', :default => %q"NONE"},
|
52
|
+
INMEMORY => {:type => ::Thrift::Types::BOOL, :name => 'inMemory', :default => false},
|
53
|
+
BLOOMFILTERTYPE => {:type => ::Thrift::Types::STRING, :name => 'bloomFilterType', :default => %q"NONE"},
|
54
|
+
BLOOMFILTERVECTORSIZE => {:type => ::Thrift::Types::I32, :name => 'bloomFilterVectorSize', :default => 0},
|
55
|
+
BLOOMFILTERNBHASHES => {:type => ::Thrift::Types::I32, :name => 'bloomFilterNbHashes', :default => 0},
|
56
|
+
BLOCKCACHEENABLED => {:type => ::Thrift::Types::BOOL, :name => 'blockCacheEnabled', :default => false},
|
57
|
+
TIMETOLIVE => {:type => ::Thrift::Types::I32, :name => 'timeToLive', :default => -1}
|
58
|
+
}
|
59
|
+
|
60
|
+
def struct_fields; FIELDS; end
|
61
|
+
|
62
|
+
def validate
|
63
|
+
end
|
64
|
+
|
65
|
+
::Thrift::Struct.generate_accessors self
|
66
|
+
end
|
67
|
+
|
68
|
+
# A TRegionInfo contains information about an HTable region.
|
69
|
+
class TRegionInfo
|
70
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
71
|
+
STARTKEY = 1
|
72
|
+
ENDKEY = 2
|
73
|
+
ID = 3
|
74
|
+
NAME = 4
|
75
|
+
VERSION = 5
|
76
|
+
|
77
|
+
FIELDS = {
|
78
|
+
STARTKEY => {:type => ::Thrift::Types::STRING, :name => 'startKey', :binary => true},
|
79
|
+
ENDKEY => {:type => ::Thrift::Types::STRING, :name => 'endKey', :binary => true},
|
80
|
+
ID => {:type => ::Thrift::Types::I64, :name => 'id'},
|
81
|
+
NAME => {:type => ::Thrift::Types::STRING, :name => 'name', :binary => true},
|
82
|
+
VERSION => {:type => ::Thrift::Types::BYTE, :name => 'version'}
|
83
|
+
}
|
84
|
+
|
85
|
+
def struct_fields; FIELDS; end
|
86
|
+
|
87
|
+
def validate
|
88
|
+
end
|
89
|
+
|
90
|
+
::Thrift::Struct.generate_accessors self
|
91
|
+
end
|
92
|
+
|
93
|
+
# A Mutation object is used to either update or delete a column-value.
|
94
|
+
class Mutation
|
95
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
96
|
+
ISDELETE = 1
|
97
|
+
COLUMN = 2
|
98
|
+
VALUE = 3
|
99
|
+
|
100
|
+
FIELDS = {
|
101
|
+
ISDELETE => {:type => ::Thrift::Types::BOOL, :name => 'isDelete', :default => false},
|
102
|
+
COLUMN => {:type => ::Thrift::Types::STRING, :name => 'column', :binary => true},
|
103
|
+
VALUE => {:type => ::Thrift::Types::STRING, :name => 'value', :binary => true}
|
104
|
+
}
|
105
|
+
|
106
|
+
def struct_fields; FIELDS; end
|
107
|
+
|
108
|
+
def validate
|
109
|
+
end
|
110
|
+
|
111
|
+
::Thrift::Struct.generate_accessors self
|
112
|
+
end
|
113
|
+
|
114
|
+
# A BatchMutation object is used to apply a number of Mutations to a single row.
|
115
|
+
class BatchMutation
|
116
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
117
|
+
ROW = 1
|
118
|
+
MUTATIONS = 2
|
119
|
+
|
120
|
+
FIELDS = {
|
121
|
+
ROW => {:type => ::Thrift::Types::STRING, :name => 'row', :binary => true},
|
122
|
+
MUTATIONS => {:type => ::Thrift::Types::LIST, :name => 'mutations', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Apache::Hadoop::Hbase::Thrift::Mutation}}
|
123
|
+
}
|
124
|
+
|
125
|
+
def struct_fields; FIELDS; end
|
126
|
+
|
127
|
+
def validate
|
128
|
+
end
|
129
|
+
|
130
|
+
::Thrift::Struct.generate_accessors self
|
131
|
+
end
|
132
|
+
|
133
|
+
# Holds row name and then a map of columns to cells.
|
134
|
+
class TRowResult
|
135
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
136
|
+
ROW = 1
|
137
|
+
COLUMNS = 2
|
138
|
+
|
139
|
+
FIELDS = {
|
140
|
+
ROW => {:type => ::Thrift::Types::STRING, :name => 'row', :binary => true},
|
141
|
+
COLUMNS => {:type => ::Thrift::Types::MAP, :name => 'columns', :key => {:type => ::Thrift::Types::STRING, :binary => true}, :value => {:type => ::Thrift::Types::STRUCT, :class => ::Apache::Hadoop::Hbase::Thrift::TCell}}
|
142
|
+
}
|
143
|
+
|
144
|
+
def struct_fields; FIELDS; end
|
145
|
+
|
146
|
+
def validate
|
147
|
+
end
|
148
|
+
|
149
|
+
::Thrift::Struct.generate_accessors self
|
150
|
+
end
|
151
|
+
|
152
|
+
# A Scan object is used to specify scanner parameters when opening a scanner.
|
153
|
+
class TScan
|
154
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
155
|
+
STARTROW = 1
|
156
|
+
STOPROW = 2
|
157
|
+
TIMESTAMP = 3
|
158
|
+
COLUMNS = 4
|
159
|
+
CACHING = 5
|
160
|
+
FILTERSTRING = 6
|
161
|
+
|
162
|
+
FIELDS = {
|
163
|
+
STARTROW => {:type => ::Thrift::Types::STRING, :name => 'startRow', :binary => true, :optional => true},
|
164
|
+
STOPROW => {:type => ::Thrift::Types::STRING, :name => 'stopRow', :binary => true, :optional => true},
|
165
|
+
TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp', :optional => true},
|
166
|
+
COLUMNS => {:type => ::Thrift::Types::LIST, :name => 'columns', :element => {:type => ::Thrift::Types::STRING, :binary => true}, :optional => true},
|
167
|
+
CACHING => {:type => ::Thrift::Types::I32, :name => 'caching', :optional => true},
|
168
|
+
FILTERSTRING => {:type => ::Thrift::Types::STRING, :name => 'filterString', :binary => true, :optional => true}
|
169
|
+
}
|
170
|
+
|
171
|
+
def struct_fields; FIELDS; end
|
172
|
+
|
173
|
+
def validate
|
174
|
+
end
|
175
|
+
|
176
|
+
::Thrift::Struct.generate_accessors self
|
177
|
+
end
|
178
|
+
|
179
|
+
# An IOError exception signals that an error occurred communicating
|
180
|
+
# to the Hbase master or an Hbase region server. Also used to return
|
181
|
+
# more general Hbase error conditions.
|
182
|
+
class IOError < ::Thrift::Exception
|
183
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
184
|
+
def initialize(message=nil)
|
185
|
+
super()
|
186
|
+
self.message = message
|
187
|
+
end
|
188
|
+
|
189
|
+
MESSAGE = 1
|
190
|
+
|
191
|
+
FIELDS = {
|
192
|
+
MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message'}
|
193
|
+
}
|
194
|
+
|
195
|
+
def struct_fields; FIELDS; end
|
196
|
+
|
197
|
+
def validate
|
198
|
+
end
|
199
|
+
|
200
|
+
::Thrift::Struct.generate_accessors self
|
201
|
+
end
|
202
|
+
|
203
|
+
# An IllegalArgument exception indicates an illegal or invalid
|
204
|
+
# argument was passed into a procedure.
|
205
|
+
class IllegalArgument < ::Thrift::Exception
|
206
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
207
|
+
def initialize(message=nil)
|
208
|
+
super()
|
209
|
+
self.message = message
|
210
|
+
end
|
211
|
+
|
212
|
+
MESSAGE = 1
|
213
|
+
|
214
|
+
FIELDS = {
|
215
|
+
MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message'}
|
216
|
+
}
|
217
|
+
|
218
|
+
def struct_fields; FIELDS; end
|
219
|
+
|
220
|
+
def validate
|
221
|
+
end
|
222
|
+
|
223
|
+
::Thrift::Struct.generate_accessors self
|
224
|
+
end
|
225
|
+
|
226
|
+
# An AlreadyExists exceptions signals that a table with the specified
|
227
|
+
# name already exists
|
228
|
+
class AlreadyExists < ::Thrift::Exception
|
229
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
230
|
+
def initialize(message=nil)
|
231
|
+
super()
|
232
|
+
self.message = message
|
233
|
+
end
|
234
|
+
|
235
|
+
MESSAGE = 1
|
236
|
+
|
237
|
+
FIELDS = {
|
238
|
+
MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message'}
|
239
|
+
}
|
240
|
+
|
241
|
+
def struct_fields; FIELDS; end
|
242
|
+
|
243
|
+
def validate
|
244
|
+
end
|
245
|
+
|
246
|
+
::Thrift::Struct.generate_accessors self
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
data/ok-hbase.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ok_hbase/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "ok_hbase"
|
8
|
+
gem.version = OkHbase::VERSION
|
9
|
+
gem.authors = ["Nathan Keyes"]
|
10
|
+
gem.email = ["keyes@okcupidlabs.com"]
|
11
|
+
gem.description = %q{Lightweight Ruby Hbase Client}
|
12
|
+
gem.summary = %q{Lightweight Ruby Hbase Client}
|
13
|
+
gem.homepage = "http://okcwest.github.io/ok_hbase/"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
|
21
|
+
gem.add_dependency 'thrift', '0.9.0'
|
22
|
+
gem.add_dependency 'activesupport'
|
23
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module OkHbase
|
4
|
+
describe Connection do
|
5
|
+
describe ".create_table" do
|
6
|
+
let(:conn) { Connection.new auto_connect: true, timeout: 60 }
|
7
|
+
|
8
|
+
it "should create tables with the right column families" do
|
9
|
+
name = "ok_hbase_test_table"
|
10
|
+
column_families = {
|
11
|
+
'a' => {
|
12
|
+
'max_versions' => 5,
|
13
|
+
'compression' => 'GZ',
|
14
|
+
'in_memory' => true,
|
15
|
+
'bloom_filter_type' => 'ROW',
|
16
|
+
'block_cache_enabled' => true,
|
17
|
+
|
18
|
+
#TODO find out if these aren't being set properly or just aren't reported properly. 0 is the default
|
19
|
+
'bloom_filter_vector_size' => 0,
|
20
|
+
'bloom_filter_nb_hashes' => 0,
|
21
|
+
|
22
|
+
#TODO find out why this doesn't get reported properly. -1 is the default
|
23
|
+
'time_to_live' => -1
|
24
|
+
},
|
25
|
+
'b' => {
|
26
|
+
'max_versions' => 15,
|
27
|
+
'compression' => 'NONE',
|
28
|
+
'in_memory' => true,
|
29
|
+
'bloom_filter_type' => 'ROWCOL',
|
30
|
+
'block_cache_enabled' => true,
|
31
|
+
|
32
|
+
'bloom_filter_vector_size' => 0,
|
33
|
+
'bloom_filter_nb_hashes' => 0,
|
34
|
+
|
35
|
+
'time_to_live' => -1
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
expected_families = Hash[column_families.map { |cf, data| [cf, { 'name' => "#{cf}:" }.merge(data)] }]
|
40
|
+
|
41
|
+
#sanity check
|
42
|
+
conn.tables.should_not include(name)
|
43
|
+
|
44
|
+
conn.create_table(name, column_families)
|
45
|
+
|
46
|
+
conn.tables.should include(name)
|
47
|
+
|
48
|
+
table = conn.table(name)
|
49
|
+
|
50
|
+
table.families.should == expected_families
|
51
|
+
|
52
|
+
#cleanup
|
53
|
+
conn.delete_table(name, true)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
it "should create tables with the right name" do
|
58
|
+
name = "ok_hbase_test_table"
|
59
|
+
column_families = {
|
60
|
+
'd' => {}
|
61
|
+
}
|
62
|
+
|
63
|
+
#sanity check
|
64
|
+
conn.tables.should_not include(name)
|
65
|
+
|
66
|
+
conn.create_table(name, column_families)
|
67
|
+
|
68
|
+
conn.tables.should include(name)
|
69
|
+
|
70
|
+
#cleanup
|
71
|
+
conn.delete_table(name, true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe ".open" do
|
76
|
+
let(:conn) { Connection.new }
|
77
|
+
|
78
|
+
it "should open a connection" do
|
79
|
+
expect { conn.open }.to change { conn.open? }.to(true)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe ".close" do
|
84
|
+
let(:conn) { Connection.new auto_connect: true }
|
85
|
+
|
86
|
+
it "should close a connection" do
|
87
|
+
expect { conn.close }.to change { conn.open? }.to(false)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe ".tables" do
|
92
|
+
let(:conn) { Connection.new auto_connect: true }
|
93
|
+
|
94
|
+
it "should return an array of table names" do
|
95
|
+
conn.tables.should be_an Array
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module OkHbase
|
4
|
+
describe Table do
|
5
|
+
let(:row_key1) { 'row1' }
|
6
|
+
let(:row_key2) { 'row2' }
|
7
|
+
let(:row_data1) { { 'd:foo' => 'Foo1', 'd:bar' => 'Bar1', 'd:baz' => 'Baz1' } }
|
8
|
+
let(:row_data2) { { 'd:foo' => 'Foo2', 'd:bar' => 'Bar2', 'd:baz' => 'Baz2' } }
|
9
|
+
let!(:timestamp) { (Time.now.to_f * 1000).to_i } # hbase timestamps are in milisecnds
|
10
|
+
|
11
|
+
test_table_name = 'ok_hbase_test_table'
|
12
|
+
conn = Connection.new(auto_connect: true, timeout: 60)
|
13
|
+
|
14
|
+
before(:all) do
|
15
|
+
conn.create_table(test_table_name, d: { max_versions: 3 })
|
16
|
+
end
|
17
|
+
|
18
|
+
after(:all) do
|
19
|
+
conn.delete_table(test_table_name, true)
|
20
|
+
end
|
21
|
+
|
22
|
+
subject { Table.new(test_table_name, conn) }
|
23
|
+
|
24
|
+
describe '#_scanner' do
|
25
|
+
let(:scanner_opts) { {
|
26
|
+
start_row: 'start',
|
27
|
+
stop_row: 'stop',
|
28
|
+
timestamp: Time.now,
|
29
|
+
columns: ['d:foo', 'd:bar', 'i:baz'],
|
30
|
+
caching: 1000,
|
31
|
+
filter_string: 'filter',
|
32
|
+
} }
|
33
|
+
|
34
|
+
it 'should set all the options' do
|
35
|
+
scanner = subject._scanner(scanner_opts)
|
36
|
+
|
37
|
+
scanner.startRow.should == scanner_opts[:start_row]
|
38
|
+
scanner.stopRow.should == scanner_opts[:stop_row]
|
39
|
+
scanner.timestamp.should == scanner_opts[:timestamp]
|
40
|
+
scanner.columns.should == scanner_opts[:columns]
|
41
|
+
scanner.caching.should == scanner_opts[:caching]
|
42
|
+
scanner.filterString.should == scanner_opts[:filter_string]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.scan' do
|
47
|
+
|
48
|
+
it 'should convert a row prefix to a start and stop rows' do
|
49
|
+
subject.put('scan_row1', row_data1)
|
50
|
+
subject.put('scan_row2', row_data1)
|
51
|
+
|
52
|
+
opts = { row_prefix: 'scan' }
|
53
|
+
subject.should_receive(:_scanner).with(hash_including(start_row: 'scan', stop_row: 'scao')).and_return {
|
54
|
+
Apache::Hadoop::Hbase::Thrift::TScan.new(
|
55
|
+
startRow: 'scan',
|
56
|
+
stopRow: 'scao',
|
57
|
+
caching: 1000,
|
58
|
+
)
|
59
|
+
}
|
60
|
+
|
61
|
+
subject.scan(opts) do |row_key, cols|
|
62
|
+
cols.should == row_data1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '.regions' do
|
68
|
+
it 'should list all region having rows for the table' do
|
69
|
+
regions = subject.regions
|
70
|
+
regions.should be_an Array
|
71
|
+
regions.size.should be >= 1
|
72
|
+
|
73
|
+
regions.each do |region|
|
74
|
+
region.keys.should include('start_key', 'end_key', 'id', 'name', 'version')
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'CRUD' do
|
81
|
+
|
82
|
+
describe '.put' do
|
83
|
+
|
84
|
+
it 'should write to the row' do
|
85
|
+
subject.put(row_key1, row_data1)
|
86
|
+
|
87
|
+
subject.row(row_key1).should == row_data1
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should write to the row with a timestamp' do
|
91
|
+
|
92
|
+
subject.put(row_key1, row_data1, timestamp)
|
93
|
+
|
94
|
+
subject.row(row_key1, nil, nil, true).should == Hash[row_data1.map { |k, v| [k, [v, timestamp]] }]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '.cells' do
|
99
|
+
it 'should retrieve values' do
|
100
|
+
|
101
|
+
subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo1'), timestamp-10)
|
102
|
+
subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo2'), timestamp-5)
|
103
|
+
subject.put(row_key2, row_data2, timestamp)
|
104
|
+
|
105
|
+
subject.cells(row_key2, 'd:foo').should == ['Foo2', 'OldFoo2', 'OldFoo1']
|
106
|
+
|
107
|
+
subject.cells(row_key2, 'd:foo', nil, timestamp-1).should == ['OldFoo2', 'OldFoo1']
|
108
|
+
subject.cells(row_key2, 'd:foo', nil, timestamp-5).should == ['OldFoo1']
|
109
|
+
|
110
|
+
subject.cells(row_key2, 'd:foo', nil, nil, true).should == [['Foo2', timestamp], ['OldFoo2', timestamp-5], ['OldFoo1', timestamp-10]]
|
111
|
+
subject.cells(row_key2, 'd:foo', 2, nil, true).should == [['Foo2', timestamp], ['OldFoo2', timestamp-5]]
|
112
|
+
subject.cells(row_key2, 'd:foo', 1, nil, true).should == [['Foo2', timestamp]]
|
113
|
+
|
114
|
+
subject.cells(row_key2, 'd:foo', nil, nil, false).should == ['Foo2', 'OldFoo2', 'OldFoo1']
|
115
|
+
subject.cells(row_key2, 'd:foo', 2, nil, false).should == ['Foo2', 'OldFoo2']
|
116
|
+
subject.cells(row_key2, 'd:foo', 1, nil, false).should == ['Foo2']
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe ".rows" do
|
122
|
+
it 'should retrieve the specified rows' do
|
123
|
+
|
124
|
+
subject.put(row_key1, row_data1, timestamp-10)
|
125
|
+
subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo1'), timestamp-10)
|
126
|
+
subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo2'), timestamp-5)
|
127
|
+
subject.put(row_key2, row_data2, timestamp)
|
128
|
+
|
129
|
+
subject.rows([row_key1, row_key2]).should == [row_data1, row_data2]
|
130
|
+
subject.rows([row_key1, row_key2], nil, timestamp-9).should == [row_data1, row_data2.merge('d:foo' => 'OldFoo1')]
|
131
|
+
|
132
|
+
subject.rows([row_key1, row_key2], row_data1.keys - ['d:foo']).should == [row_data1.except('d:foo'), row_data2.except('d:foo')]
|
133
|
+
subject.rows([row_key1, row_key2], row_data1.keys - ['d:foo'], timestamp-5).should == [row_data1.except('d:foo'), row_data2.except('d:foo')]
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe ".delete" do
|
139
|
+
it 'should retrieve the specified rows' do
|
140
|
+
subject.put(row_key1, row_data1)
|
141
|
+
|
142
|
+
subject.delete(row_key1, ['d:bar', 'd:baz'])
|
143
|
+
|
144
|
+
subject.row(row_key1).should == row_data1.except('d:bar', 'd:baz')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# require 'spec_helper'
|
3
|
+
|
4
|
+
describe OkHbase do
|
5
|
+
|
6
|
+
describe "#increment_string" do
|
7
|
+
let(:normal_string) { "foo baq" }
|
8
|
+
let(:incremented_normal_string) { "foo bar" }
|
9
|
+
|
10
|
+
let(:utf8_string) { "Vulgar fraction one half: ¼".force_encoding(Encoding::UTF_8) }
|
11
|
+
let(:incremented_utf8_string) { "Vulgar fraction one half: ½".force_encoding(Encoding::UTF_8) }
|
12
|
+
let(:max_byte_string) { [255, 255, 255].pack('c*') }
|
13
|
+
|
14
|
+
it "should increment the last byte byte < 255" do
|
15
|
+
OkHbase::increment_string(normal_string).should == incremented_normal_string
|
16
|
+
OkHbase::increment_string(utf8_string).should == incremented_utf8_string
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return nil when all bytes are 255" do
|
20
|
+
OkHbase::increment_string(max_byte_string).should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
|
4
|
+
|
5
|
+
# needs to be at the top
|
6
|
+
require 'simplecov'
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter "/thrift/"
|
9
|
+
end
|
10
|
+
|
11
|
+
Bundler.require :default, :test
|
12
|
+
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
require 'bundler'
|
16
|
+
require 'awesome_print'
|
17
|
+
|
18
|
+
require 'ok_hbase'
|
19
|
+
|
20
|
+
|
data/tasks/bump.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
desc 'shortcut for bump:patch'
|
2
|
+
task :bump => 'bump:patch'
|
3
|
+
|
4
|
+
namespace :bump do
|
5
|
+
desc 'Bump x.y.Z'
|
6
|
+
task :patch
|
7
|
+
|
8
|
+
desc 'Bump x.Y.z'
|
9
|
+
task :minor
|
10
|
+
|
11
|
+
desc 'Bump X.y.z'
|
12
|
+
task :major
|
13
|
+
end
|
14
|
+
|
15
|
+
# extracted and modified from https://github.com/grosser/project_template
|
16
|
+
rule /^bump:.*/ do |t|
|
17
|
+
sh "git status | grep 'nothing to commit'" # ensure we are not dirty
|
18
|
+
index = ['major', 'minor','patch'].index(t.name.split(':').last)
|
19
|
+
file = 'lib/ok_hbase/version.rb'
|
20
|
+
|
21
|
+
version_file = File.read(file)
|
22
|
+
old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
|
23
|
+
version_parts[index] = version_parts[index].to_i + 1
|
24
|
+
version_parts[2] = 0 if index < 2
|
25
|
+
version_parts[1] = 0 if index < 1
|
26
|
+
new_version = version_parts * '.'
|
27
|
+
File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
|
28
|
+
|
29
|
+
sh "bundle && git add #{file} && git commit -m 'bump version to #{new_version}'"
|
30
|
+
end
|