hbase-driver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require 'rake/gempackagetask'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec') do |t|
6
+ t.pattern = "spec/**/[^_]*_spec.rb"
7
+ end
8
+
9
+ task :default => :spec
10
+
11
+ spec = Gem::Specification.new do |s|
12
+ s.name = "hbase-driver"
13
+ s.version = '0.0.1'
14
+ s.summary = "HBase Ruby Driver"
15
+ s.homepage = "http://github.com/railsware/hbase-driver"
16
+ s.email = ["Alexey.Petrushin@railsware.com", "dmitry.larkin@railsware.com"]
17
+ s.authors = ["Alexey Petrushin", "Dmitry Larkin"]
18
+ s.files = FileList["{lib}/**/*"].to_a + %w(readme.md Rakefile)
19
+ s.require_paths = ["lib"]
20
+ s.has_rdoc = false
21
+ end
22
+
23
+ Rake::GemPackageTask.new(spec) do |p|
24
+ p.gem_spec = spec
25
+ end
data/lib/hbase.rb ADDED
@@ -0,0 +1,9 @@
1
+ %w(
2
+ support
3
+ cell
4
+ record
5
+ table
6
+ hbase
7
+ ).each{|f| require "hbase/#{f}"}
8
+
9
+ require 'stargate'
data/lib/hbase/cell.rb ADDED
@@ -0,0 +1,9 @@
1
+ class HBase
2
+ class Cell
3
+ attr_accessor :value, :timestamp
4
+
5
+ def initialize value, timestamp
6
+ @value, @timestamp = value, timestamp
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ class HBase
2
+ attr_reader :stargate
3
+
4
+ def initialize *args
5
+ @stargate = Stargate::Client.new *args
6
+ end
7
+
8
+ def get table_name
9
+ HBase::Table.new stargate, table_name
10
+ end
11
+ alias_method :[], :get
12
+
13
+ def delete table_name
14
+ stargate.delete_table table_name
15
+ end
16
+
17
+ def truncate table_name
18
+ stargate.truncate table_name
19
+ end
20
+
21
+ def all
22
+ stargate.list_tables
23
+ end
24
+ alias_method :list, :all
25
+
26
+ def include? table_name
27
+ all.any?{|meta| meta.name == table_name}
28
+ end
29
+ alias_method :exist?, :include?
30
+
31
+ def create *args
32
+ stargate.create_table *args
33
+ end
34
+
35
+ def disable *args
36
+ stargate.disable_table *args
37
+ end
38
+
39
+ def enable *args
40
+ stargate.enable_table *args
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ class HBase
2
+ class Record
3
+ attr_reader :cells
4
+
5
+ def initialize
6
+ @cells = {}
7
+ end
8
+
9
+ def get(key)
10
+ if cell = cells[key]
11
+ cell.value
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ alias_method :[], :get
17
+
18
+ def size
19
+ cells.size
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ unless {}.respond_to? :symbolize_keys
2
+ class Hash
3
+ def symbolize_keys
4
+ h = {}
5
+ each{|k, v| h[k.to_sym] = v}
6
+ h
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,75 @@
1
+ class HBase
2
+ class Table
3
+ attr_reader :stargate, :table_name
4
+
5
+ def initialize(stargate, table_name)
6
+ @stargate, @table_name = stargate, table_name
7
+ end
8
+
9
+ def get(key, with_timestamps = false)
10
+ begin
11
+ record = Record.new
12
+ old_row = stargate.show_row(table_name, key)
13
+ old_row.columns.each do |old_cell|
14
+ record.cells[old_cell.name] = Cell.new old_cell.value, old_cell.timestamp
15
+ end
16
+ record
17
+ rescue Stargate::RowNotFoundError
18
+ nil
19
+ end
20
+ end
21
+ alias_method :[], :get
22
+
23
+ def update(key, attributes)
24
+ raise "ivalid usage, attributes should be a hash!" unless attributes.is_a? Hash
25
+ timestamp = attributes.delete(:timestamp) || attributes.delete('timestamp') # || Time.now.to_i
26
+ attr_in_driver_format = attributes.to_a.collect do |attr_name, attr_value|
27
+ {:name => attr_name, :value => attr_value}
28
+ end
29
+ stargate.create_row(table_name, key, timestamp, attr_in_driver_format)
30
+ end
31
+ alias_method :[]=, :update
32
+
33
+ def delete(key)
34
+ stargate.delete_row table_name, key
35
+ end
36
+
37
+ def include?(key)
38
+ !!get(key)
39
+ end
40
+ alias_method :exist?, :include?
41
+
42
+ def metadata
43
+ stargate.show_table table_name
44
+ end
45
+
46
+ TRANSLATION = {
47
+ :start => :start_row,
48
+ :end => :end_row
49
+ }
50
+ def scan(options = {})
51
+ options.symbolize_keys
52
+
53
+ stargate_options = {}
54
+ options.each do |k, v|
55
+ stargate_options[TRANSLATION[k] || k] = v
56
+ end
57
+
58
+ scanner = stargate.open_scanner(table_name, stargate_options)
59
+ begin
60
+ old_rows = stargate.get_rows(scanner)
61
+ records = []
62
+ old_rows.each do |old_row|
63
+ old_row.columns.each do |old_cell|
64
+ record = Record.new
65
+ record.cells[old_cell.name] = Cell.new old_cell.value, old_cell.timestamp
66
+ records << record
67
+ end
68
+ end
69
+ records
70
+ ensure
71
+ stargate.close_scanner(scanner) if scanner
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/stargate.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Stargate end
2
+
3
+ require File.join(File.dirname(__FILE__), "stargate", "client")
4
+ require File.join(File.dirname(__FILE__), "stargate", "exception")
5
+ require File.join(File.dirname(__FILE__), "stargate", "model")
6
+ require File.join(File.dirname(__FILE__), "stargate", "request")
7
+ require File.join(File.dirname(__FILE__), "stargate", "response")
@@ -0,0 +1,93 @@
1
+ require 'net/http'
2
+ require File.dirname(__FILE__) + '/operation/meta_operation'
3
+ require File.dirname(__FILE__) + '/operation/table_operation'
4
+ require File.dirname(__FILE__) + '/operation/row_operation'
5
+ require File.dirname(__FILE__) + '/operation/scanner_operation'
6
+
7
+ module Stargate
8
+ class Client
9
+ VERSION = 1 #File.read(File.join(File.dirname(__FILE__), "..", "..", "VERSION")).chomp.freeze
10
+
11
+ include Operation::MetaOperation
12
+ include Operation::TableOperation
13
+ include Operation::RowOperation
14
+ include Operation::ScannerOperation
15
+
16
+ attr_reader :url, :connection
17
+
18
+ def initialize(url = "http://localhost:8080", opts = {})
19
+ @url = URI.parse(url)
20
+ unless @url.kind_of? URI::HTTP
21
+ raise "invalid http url: #{url}"
22
+ end
23
+
24
+ # Not actually opening the connection yet, just setting up the persistent connection.
25
+ if opts[:proxy]
26
+ proxy_address, proxy_port = opts[:proxy].split(':')
27
+ @connection = Net::HTTP.Proxy(proxy_address, proxy_port).new(@url.host, @url.port)
28
+ else
29
+ @connection = Net::HTTP.new(@url.host, @url.port)
30
+ end
31
+ @connection.read_timeout = opts[:timeout] if opts[:timeout]
32
+ end
33
+
34
+ def get(path, options = {})
35
+ safe_request { @connection.get(@url.path + path, {'Content-Type' => 'text/xml', "Accept" => "application/json"}.merge(options)) }
36
+ end
37
+
38
+ def get_response(path, options = {})
39
+ safe_response { @connection.get(@url.path + path, {"Accept" => "application/json"}.merge(options)) }
40
+ end
41
+
42
+ def post(path, data = nil, options = {})
43
+ safe_request { @connection.post(@url.path + path, data, {'Content-Type' => 'text/xml', "Accept" => "application/json"}.merge(options)) }
44
+ end
45
+
46
+ def post_response(path, data = nil, options = {})
47
+ safe_response { @connection.post(@url.path + path, data, {'Content-Type' => 'text/xml', "Accept" => "application/json"}.merge(options)) }
48
+ end
49
+
50
+ def delete(path, options = {})
51
+ safe_request { @connection.delete(@url.path + path, options) }
52
+ end
53
+
54
+ def delete_response(path, options = {})
55
+ safe_response { @connection.delete(@url.path + path, options) }
56
+ end
57
+
58
+ def put(path, data = nil, options = {})
59
+ safe_request { @connection.put(@url.path + path, data, {'Content-Type' => 'text/xml', "Accept" => "application/json"}.merge(options)) }
60
+ end
61
+
62
+ def put_response(path, data = nil, options = {})
63
+ safe_response { @connection.put(@url.path + path, data, {'Content-Type' => 'text/xml', "Accept" => "application/json"}.merge(options)) }
64
+ end
65
+
66
+ private
67
+
68
+ # Part of safe_request was broken up into safe_response because when working with scanners
69
+ # in Stargate, you need to have access to the response itself, and not just the body.
70
+ def safe_response(&block)
71
+ begin
72
+ yield
73
+ rescue Errno::ECONNREFUSED
74
+ raise ConnectionNotEstablishedError, "can't connect to #{@url}"
75
+ rescue Timeout::Error => e
76
+ puts e.backtrace.join("\n")
77
+ raise ConnectionTimeoutError, "execution expired. Maybe query disabled tables"
78
+ end
79
+ end
80
+
81
+ def safe_request(&block)
82
+ response = safe_response{ yield block }
83
+
84
+ case response
85
+ when Net::HTTPSuccess
86
+ response.body
87
+ else
88
+ response.error!
89
+ end
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,21 @@
1
+ class Stargate::Exception < StandardError; end
2
+
3
+ class Stargate::ConnectionNotEstablishedError < Stargate::Exception; end
4
+
5
+ class Stargate::ConnectionTimeoutError < Stargate::Exception; end
6
+
7
+ class Stargate::TableNotFoundError < Stargate::Exception; end
8
+
9
+ class Stargate::TableExistsError < Stargate::Exception; end
10
+
11
+ class Stargate::TableFailCreateError < Stargate::Exception; end
12
+
13
+ class Stargate::TableNotDisabledError < Stargate::Exception; end
14
+
15
+ class Stargate::TableFailDisableError < Stargate::Exception; end
16
+
17
+ class Stargate::TableFailEnableError < Stargate::Exception; end
18
+
19
+ class Stargate::RowNotFoundError < Stargate::Exception; end
20
+
21
+ class Stargate::ScannerError < Stargate::Exception; end
@@ -0,0 +1,19 @@
1
+ module Stargate
2
+ module Model
3
+ class Record
4
+ def initialize (params)
5
+ params.each do |key, value|
6
+ name = key.to_s
7
+ instance_variable_set("@#{name}", value) if respond_to?(name)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ require File.dirname(__FILE__) + '/model/column'
15
+ require File.dirname(__FILE__) + '/model/column_descriptor'
16
+ require File.dirname(__FILE__) + '/model/region_descriptor'
17
+ require File.dirname(__FILE__) + '/model/row'
18
+ require File.dirname(__FILE__) + '/model/table_descriptor'
19
+ require File.dirname(__FILE__) + '/model/scanner'
@@ -0,0 +1,9 @@
1
+ module Stargate
2
+ module Model
3
+ class Column < Record
4
+ attr_accessor :name
5
+ attr_accessor :value
6
+ attr_accessor :timestamp
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ module Stargate
2
+ module Model
3
+ module CompressionType
4
+ NONE = "NONE"
5
+ RECORD = "RECORD"
6
+ BLOCK = "BLOCK"
7
+
8
+ CTYPES = [NONE, RECORD, BLOCK]
9
+
10
+ def to_compression_type(type_string)
11
+ CTYPES.include?(type_string) ? type_string : NONE
12
+ end
13
+
14
+ module_function :to_compression_type
15
+ end
16
+
17
+ class ColumnDescriptor < Record
18
+ AVAILABLE_OPTS = { :name => "name", :max_versions => "VERSIONS", :versions => "VERSIONS",
19
+ :compression => "COMPRESSION", :in_memory => "IN_MEMORY",
20
+ :block_cache => "BLOCKCACHE", :blockcache => "BLOCKCACHE",
21
+ :blocksize => "BLOCKSIZE", :length => "LENGTH", :ttl => "TTL",
22
+ :bloomfilter => "BLOOMFILTER"}
23
+ attr_accessor :name
24
+ attr_accessor :compression
25
+ attr_accessor :bloomfilter
26
+ attr_accessor :maximum_cell_size
27
+ attr_accessor :max_versions
28
+
29
+ attr_accessor :versions
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Stargate
2
+ module Model
3
+ class Region < Record
4
+ end
5
+
6
+ class RegionDescriptor < Record
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Stargate
2
+ module Model
3
+ class Row < Record
4
+ attr_accessor :table_name
5
+ attr_accessor :name
6
+ attr_accessor :total_count
7
+ attr_accessor :columns
8
+
9
+ def timestamp
10
+ warn "[DEPRECATION] timestamp attribute will be removed from the Row model. "
11
+ end
12
+
13
+ def timestamp=
14
+ timestamp
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module Stargate
2
+ module Model
3
+ class Scanner < Record
4
+ AVAILABLE_OPTS = { :start_row => "startRow", :end_row => "endRow",
5
+ :start_time => "startTime", :end_time => "endTime",
6
+ :batch => "batch" }
7
+
8
+ attr_accessor :table_name
9
+ attr_accessor :scanner_url
10
+ attr_accessor :batch_size
11
+
12
+ # Deprecation: scanner_url is used instead of just the ID
13
+ attr_accessor :scanner_id
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ module Stargate
2
+ module Model
3
+ class TableDescriptor < Record
4
+ attr_accessor :name
5
+ attr_accessor :column_families
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ module Stargate
2
+ module Operation
3
+ module MetaOperation
4
+ def list_tables
5
+ request = Request::MetaRequest.new
6
+ Response::MetaResponse.new(get(request.list_tables), :list_tables).parse
7
+ end
8
+
9
+ def version
10
+ request = Request::MetaRequest.new
11
+ get(request.version, {"Accept" => "text/plain"}).strip
12
+ end
13
+
14
+ def cluster_version
15
+ request = Request::MetaRequest.new
16
+ get(request.cluster_version, {"Accept" => "text/plain"}).strip
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,86 @@
1
+ module Stargate
2
+ module Operation
3
+ module RowOperation
4
+ Converter = {
5
+ '&' => '&amp;',
6
+ '<' => '&lt;',
7
+ '>' => '&gt;',
8
+ "'" => '&apos;',
9
+ '"' => '&quot;'
10
+ }
11
+
12
+ def row_timestamps(table_name, name)
13
+ raise NotImplementedError, "Currently not supported in Stargate client"
14
+ end
15
+
16
+ def show_row(table_name, name, timestamp = nil, columns = nil, options = { })
17
+ begin
18
+ options[:version] ||= 1
19
+
20
+ request = Request::RowRequest.new(table_name, name, timestamp)
21
+ row = Response::RowResponse.new(get(request.show(columns, options)), :show_row).parse.first
22
+ row.table_name = table_name
23
+ row
24
+ rescue Net::ProtocolError => e
25
+ # TODO: Use better handling instead of this.
26
+ if e.to_s.include?("Table")
27
+ raise TableNotFoundError, "Table '#{table_name}' Not Found"
28
+ elsif e.to_s.include?("404")
29
+ raise RowNotFoundError, "Row '#{name}' Not Found"
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_row(table_name, name, timestamp = nil, columns = nil)
35
+ begin
36
+ request = Request::RowRequest.new(table_name, name, timestamp)
37
+ data = []
38
+ if columns
39
+ if columns.instance_of? Array
40
+ data = columns
41
+ elsif columns.instance_of? Hash
42
+ data = [columns]
43
+ else
44
+ raise StandardError, "Only Array or Hash data accepted"
45
+ end
46
+ end
47
+
48
+ xml_data = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?><CellSet>"
49
+ xml_data << "<Row key='#{[name].pack('m') rescue ''}'>"
50
+ data.each do |d|
51
+ escape_name = d[:name].gsub(/[&<>'"]/) { |match| Converter[match] }
52
+ xml_data << "<Cell "
53
+ xml_data << "timestamp='#{timestamp}' " if timestamp
54
+ xml_data << "column='#{[escape_name].pack('m') rescue ''}'>"
55
+ xml_data << "#{[d[:value]].pack("m") rescue ''}"
56
+ xml_data << "</Cell>"
57
+ end
58
+ xml_data << "</Row></CellSet>"
59
+
60
+ Response::RowResponse.new(post_response(request.create(data.map{|col| col[:name]}), xml_data), :create_row).parse
61
+ rescue Net::ProtocolError => e
62
+ if e.to_s.include?("Table")
63
+ raise TableNotFoundError, "Table '#{table_name}' Not Found"
64
+ elsif e.to_s.include?("Row")
65
+ raise RowNotFoundError, "Row '#{name}' Not Found"
66
+ else
67
+ raise StandardError, "Error encountered while trying to create row: #{e.message}"
68
+ end
69
+ end
70
+ end
71
+
72
+ def delete_row(table_name, name, timestamp = nil, columns = nil)
73
+ begin
74
+ request = Request::RowRequest.new(table_name, name, timestamp)
75
+ Response::RowResponse.new(delete_response(request.delete(columns)), :delete_row).parse
76
+ rescue Net::ProtocolError => e
77
+ if e.to_s.include?("Table")
78
+ raise TableNotFoundError, "Table '#{table_name}' Not Found"
79
+ elsif e.to_s.include?("Row")
80
+ raise RowNotFoundError, "Row '#{name}' Not Found"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,90 @@
1
+ module Stargate
2
+ module Operation
3
+ module ScannerOperation
4
+ # Trying to maintain some API stability for now
5
+ def open_scanner(table_name, columns, start_row, stop_row = nil, timestamp = nil)
6
+ warn "[DEPRECATION] This method is deprecated. Use #open_scanner(table_name, options = {}) instead."
7
+
8
+ open_scanner(table_name, {:columns => columns, :start_row => start_row, :stop_row => stop_row, :timestamp => timestamp})
9
+ end
10
+
11
+ def open_scanner(table_name, options = {})
12
+ raise ArgumentError, "options should be given as a Hash" unless options.instance_of? Hash
13
+ columns = options.delete(:columns)
14
+ batch = options.delete(:batch) || "10"
15
+
16
+ begin
17
+ request = Request::ScannerRequest.new(table_name)
18
+
19
+ xml_data = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?><Scanner batch='#{batch}' "
20
+ options.each do |key,value|
21
+ if Model::Scanner::AVAILABLE_OPTS.include? key
22
+ xml_data << "#{Model::Scanner::AVAILABLE_OPTS[key]}='#{[value.to_s].flatten.pack('m')}' "
23
+ else
24
+ warn "[open_scanner] Received invalid option key :#{key}"
25
+ end
26
+ end
27
+ if columns
28
+ xml_data << ">"
29
+ [columns].flatten.each do |col|
30
+ xml_data << "<column>#{[col].flatten.pack('m')}</column>"
31
+ end
32
+ xml_data << "</Scanner>"
33
+ else
34
+ xml_data << "/>"
35
+ end
36
+
37
+ scanner = Response::ScannerResponse.new(post_response(request.open, xml_data), :open_scanner).parse
38
+ scanner.table_name = table_name
39
+ scanner.batch_size = batch
40
+ scanner
41
+ rescue Net::ProtocolError => e
42
+ raise StandardError, e.to_s
43
+ end
44
+ end
45
+
46
+ def get_rows(scanner, limit = nil)
47
+ begin
48
+ request = Request::ScannerRequest.new(scanner.table_name)
49
+ request_url = request.get_rows(scanner) # The url to the scanner is the same for each batch
50
+
51
+ rows = []
52
+ begin
53
+ # Loop until we've reached the limit, or the scanner was exhausted (HTTP 204 returned)
54
+ until (limit && rows.size >= limit) || (response = get_response(request_url)).code == "204"
55
+ rows.concat Response::ScannerResponse.new(response.body, :get_rows).parse
56
+
57
+ rows.each do |row|
58
+ row.table_name = scanner.table_name
59
+ end
60
+ end
61
+ rescue Exception => e
62
+ raise Stargate::ScannerError, "Scanner failed while getting rows. #{e.message}"
63
+ end
64
+
65
+ # Prune the last few rows if the limit was passed.
66
+ (limit) ? rows.slice(0, limit) : rows
67
+ rescue StandardError => e
68
+ if e.to_s.include?("TableNotFoundException")
69
+ raise TableNotFoundError, "Table #{table_name} Not Found!"
70
+ else
71
+ raise StandardError, e.to_s
72
+ end
73
+ end
74
+ end
75
+
76
+ def close_scanner(scanner)
77
+ begin
78
+ request = Request::ScannerRequest.new(scanner.table_name)
79
+ Response::ScannerResponse.new(delete_response(request.close(scanner)), :close_scanner).parse
80
+ rescue StandardError => e
81
+ if e.to_s.include?("TableNotFoundException")
82
+ raise TableNotFoundError, "Table #{table_name} Not Found!"
83
+ else
84
+ raise StandardError, e.to_s
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,95 @@
1
+ module Stargate
2
+ module Operation
3
+ module TableOperation
4
+ def show_table(name)
5
+ begin
6
+ request = Request::TableRequest.new(name)
7
+ Response::TableResponse.new(get(request.show)).parse
8
+ rescue Net::ProtocolError
9
+ raise TableNotFoundError, "Table '#{name}' Not found"
10
+ end
11
+ end
12
+
13
+ def create_table(name, *args)
14
+ request = Request::TableRequest.new(name)
15
+
16
+ raise StandardError, "Table name must be of type String" unless name.instance_of? String
17
+
18
+ begin
19
+ xml_data = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?><TableSchema name='#{name}' IS_META='false' IS_ROOT='false'>"
20
+ for arg in args
21
+ if arg.instance_of? String
22
+ xml_data << "<ColumnSchema name='#{arg}' />"
23
+ elsif arg.instance_of? Hash
24
+ xml_data << "<ColumnSchema "
25
+
26
+ arg.each do |k,v|
27
+ if Model::ColumnDescriptor::AVAILABLE_OPTS.include? k
28
+ xml_data << "#{Model::ColumnDescriptor::AVAILABLE_OPTS[k]}='#{v}' "
29
+ end
30
+ end
31
+
32
+ xml_data << "/>"
33
+ else
34
+ raise StandardError, "#{arg.class.to_s} of #{arg.to_s} is not of Hash Type"
35
+ end
36
+ end
37
+ xml_data << "</TableSchema>"
38
+ Response::TableResponse.new(post(request.create, xml_data))
39
+ rescue Net::ProtocolError => e
40
+ if e.to_s.include?("TableExistsException")
41
+ raise TableExistsError, "Table '#{name}' already exists"
42
+ else
43
+ raise TableFailCreateError, e.message
44
+ end
45
+ end
46
+ end
47
+
48
+ def alter_table(name, *args)
49
+ raise StandardError, "Table name must be of type String" unless name.instance_of? String
50
+
51
+ request = Request::TableRequest.new(name)
52
+
53
+ begin
54
+ xml_data = construct_xml_stream(name, *args)
55
+ Response::TableResponse.new(put(request.update, xml_data))
56
+ rescue Net::ProtocolError => e
57
+ if e.to_s.include?("TableNotFoundException")
58
+ raise TableNotFoundError, "Table '#{name}' not exists"
59
+ else
60
+ raise TableFailCreateError, e.message
61
+ end
62
+ end
63
+ end
64
+
65
+ def delete_table(name, columns = nil)
66
+ begin
67
+ request = Request::TableRequest.new(name)
68
+ Response::TableResponse.new(delete(request.delete(columns)))
69
+ rescue Net::ProtocolError => e
70
+ if e.to_s.include?("TableNotFoundException")
71
+ raise TableNotFoundError, "Table '#{name}' not exists"
72
+ elsif e.to_s.include?("TableNotDisabledException")
73
+ raise TableNotDisabledError, "Table '#{name}' not disabled"
74
+ end
75
+ end
76
+ end
77
+
78
+ def destroy_table(name, columns = nil)
79
+ delete_table(name, columns)
80
+ end
81
+
82
+ def enable_table(name)
83
+ warn "[DEPRECATION] Explicitly enabling tables isn't required anymore. HBase Stargate will enable/disable as needed."
84
+ end
85
+
86
+ def disable_table(name)
87
+ warn "[DEPRECATION] Explicitly disabling tables isn't required anymore. HBase Stargate will enable/disable as needed."
88
+ end
89
+
90
+ def table_regions(name, start_row = nil, end_row = nil)
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,7 @@
1
+ module Stargate module Request; end; end
2
+
3
+ require File.dirname(__FILE__) + '/request/basic_request'
4
+ require File.dirname(__FILE__) + '/request/meta_request'
5
+ require File.dirname(__FILE__) + '/request/table_request'
6
+ require File.dirname(__FILE__) + '/request/row_request'
7
+ require File.dirname(__FILE__) + '/request/scanner_request'
@@ -0,0 +1,27 @@
1
+ require 'cgi'
2
+
3
+ module Stargate
4
+ module Request
5
+ class BasicRequest
6
+ attr_reader :path
7
+
8
+ def initialize(path)
9
+ @path = path
10
+ end
11
+
12
+
13
+ protected
14
+
15
+ def pack_params columns
16
+ if columns.is_a? String
17
+ columns = [columns]
18
+ elsif columns.is_a? Array
19
+ else
20
+ raise StandardError, "Only String or Array type allows for columns"
21
+ end
22
+
23
+ columns.join(',')
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module Stargate
2
+ module Request
3
+ class MetaRequest < BasicRequest
4
+ def initialize
5
+ super("")
6
+ end
7
+
8
+ def list_tables
9
+ @path << "/"
10
+ end
11
+
12
+ def create_table
13
+ @path << "/tables"
14
+ end
15
+
16
+ def version
17
+ @path << "/version"
18
+ end
19
+
20
+ def cluster_version
21
+ @path << "/version/cluster"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module Stargate
2
+ module Request
3
+ class RowRequest < BasicRequest
4
+ attr_reader :table_name
5
+ attr_reader :name
6
+ attr_reader :timestamp
7
+
8
+ def initialize(table_name, name, timestamp=nil)
9
+ @table_name, @name, @timestamp = CGI.escape(table_name), CGI.escape(name), timestamp
10
+ path = "/#{@table_name}/#{@name}"
11
+ super(path)
12
+ end
13
+
14
+ def show(columns = nil, options = { })
15
+ @path << (columns ? "/#{pack_params(columns)}" : "/")
16
+ @path << "/#{@timestamp}" if @timestamp
17
+ @path << "?v=#{options[:version]}" if options[:version]
18
+ @path
19
+ end
20
+
21
+ def create(columns = nil)
22
+ @path << (columns ? "/#{pack_params(columns)}" : "/")
23
+ @path << "/#{@timestamp}" if @timestamp
24
+ @path
25
+ end
26
+
27
+ def delete(columns = nil)
28
+ @path << (columns ? "/#{pack_params(columns)}" : "/")
29
+ @path << "/#{@timestamp}" if @timestamp
30
+ @path
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ module Stargate
2
+ module Request
3
+ class ScannerRequest < BasicRequest
4
+ attr_reader :table_name
5
+
6
+ def initialize(table_name)
7
+ @table_name = CGI.escape(table_name)
8
+ path = "/#{@table_name}/scanner"
9
+ super(path)
10
+ end
11
+
12
+ def open
13
+ @path
14
+ end
15
+
16
+ def get_rows(scanner)
17
+ @path = URI.parse(scanner.scanner_url).path
18
+ end
19
+
20
+ def close(scanner)
21
+ @path = URI.parse(scanner.scanner_url).path
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ module Stargate
2
+ module Request
3
+ class TableRequest < BasicRequest
4
+ attr_reader :name
5
+ attr_reader :body
6
+
7
+ def initialize(name)
8
+ super("")
9
+ @name = CGI.escape(name) if name
10
+ end
11
+
12
+ def show
13
+ @path << "/#{name}/schema"
14
+ end
15
+
16
+ def regions(start_row = nil, end_row = nil)
17
+ #TODO no handle the args!
18
+ @path << "/#{name}/regions"
19
+ end
20
+
21
+ def create
22
+ @path << "/#{name}/schema"
23
+ end
24
+
25
+ def update
26
+ @path << "/#{name}"
27
+ end
28
+
29
+ def enable
30
+ @path << "/#{name}/enable"
31
+ end
32
+
33
+ def disable
34
+ @path << "/#{name}/disable"
35
+ end
36
+
37
+ def delete(columns = nil)
38
+ warn "[DEPRECATION] the use of the 'columns' argument is deprecated. Please use the delete method without any arguments." if columns
39
+ @path << "/#{name}/schema"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module Stargate module Response; end; end
2
+
3
+ require File.dirname(__FILE__) + '/response/basic_response'
4
+ require File.dirname(__FILE__) + '/response/meta_response'
5
+ require File.dirname(__FILE__) + '/response/table_response'
6
+ require File.dirname(__FILE__) + '/response/row_response'
7
+ require File.dirname(__FILE__) + '/response/scanner_response'
@@ -0,0 +1,30 @@
1
+ begin
2
+ require 'json'
3
+ rescue LoadError => e
4
+ puts "[hbase-stargate] json is required. Install it with 'gem install json' (or json-jruby for JRuby)"
5
+ raise e
6
+ end
7
+
8
+ module Stargate
9
+ module Response
10
+ class BasicResponse
11
+
12
+ def initialize(raw_data)
13
+ @raw_data = raw_data
14
+ end
15
+
16
+ def parse
17
+ parse_content @raw_data
18
+ end
19
+
20
+ def verify_success(response)
21
+ case response
22
+ when Net::HTTPSuccess
23
+ true
24
+ else
25
+ false
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module Stargate
2
+ module Response
3
+ class MetaResponse < BasicResponse
4
+ attr_reader :method
5
+
6
+ def initialize(raw_data, method)
7
+ @method = method
8
+ super(raw_data)
9
+ end
10
+
11
+ def parse_content(raw_data)
12
+ case @method
13
+ when :list_tables
14
+ if raw_data.to_s.empty? || raw_data == "null" # "null" from json
15
+ puts "There are no available tables in the HBase"
16
+ return []
17
+ end
18
+
19
+ tables = []
20
+ doc = JSON.parse(raw_data)
21
+
22
+ doc["table"].each do |table|
23
+ name = table["name"].strip rescue nil
24
+ t = Model::TableDescriptor.new(:name => name)
25
+ tables << t
26
+ end
27
+
28
+ tables
29
+ else
30
+ puts "method '#{@method}' not supported yet"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ module Stargate
2
+ module Response
3
+ class RowResponse < BasicResponse
4
+ attr_reader :method
5
+
6
+ def initialize(raw_data, method)
7
+ @method = method
8
+ super(raw_data)
9
+ end
10
+
11
+ def parse_content(raw_data)
12
+ case @method
13
+ when :show_row
14
+ doc = JSON.parse(raw_data)
15
+ rows = doc["Row"]
16
+
17
+ model_rows = []
18
+ rows.each do |row|
19
+ rname = row["key"].strip.unpack("m").first
20
+ count = row["Cell"].size
21
+ columns = []
22
+
23
+ row["Cell"].each do |col|
24
+ name = col["column"].strip.unpack('m').first
25
+ value = col["$"].strip.unpack('m').first rescue nil
26
+ timestamp = col["timestamp"].to_i
27
+
28
+ columns << Stargate::Model::Column.new( :name => name,
29
+ :value => value,
30
+ :timestamp => timestamp)
31
+ end
32
+
33
+ model_rows << Stargate::Model::Row.new(:name => rname, :total_count => count, :columns => columns)
34
+ end
35
+
36
+ model_rows
37
+ when :create_row
38
+ verify_success(raw_data)
39
+ when :delete_row
40
+ verify_success(raw_data)
41
+ else
42
+ puts "method '#{@method}' not supported yet"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ module Stargate
2
+ module Response
3
+ class ScannerResponse < BasicResponse
4
+ attr_reader :method
5
+
6
+ def initialize(raw_data, method)
7
+ @method = method
8
+ super(raw_data)
9
+ end
10
+
11
+ def parse_content(raw_data)
12
+ case @method
13
+ when :open_scanner
14
+ case raw_data
15
+ when Net::HTTPCreated
16
+ Stargate::Model::Scanner.new(:scanner_url => raw_data["Location"])
17
+ else
18
+ if raw_data.message.include?("TableNotFoundException")
19
+ raise TableNotFoundError, "Table #{table_name} Not Found!"
20
+ else
21
+ raise StandardError, "Unable to open scanner. Received the following message: #{raw_data.message}"
22
+ end
23
+ end
24
+ when :get_rows
25
+ # Dispatch it to RowResponse, since that method is made
26
+ # to deal with rows already.
27
+ RowResponse.new(raw_data, :show_row).parse
28
+ when :close_scanner
29
+ case raw_data
30
+ when Net::HTTPOK
31
+ return true
32
+ else
33
+ raise StandardError, "Unable to close scanner. Received the following message: #{raw_data.message}"
34
+ end
35
+ else
36
+ puts "method '#{@method}' not supported yet"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ module Stargate
2
+ module Response
3
+ class TableResponse < BasicResponse
4
+ def parse_content(raw_data)
5
+ table = JSON.parse(raw_data)
6
+ name = table["name"].strip
7
+
8
+ column_families = []
9
+ table["ColumnSchema"].each do |columnfamily|
10
+ colname = columnfamily["name"].strip
11
+ compression = columnfamily["COMPRESSION"].strip
12
+ bloomfilter = (columnfamily["BLOOMFILTER"].strip =~ /^true$/i ? true : false)
13
+ max_versions = columnfamily["VERSIONS"].strip.to_i
14
+
15
+ column_descriptor = Model::ColumnDescriptor.new(:name => colname,
16
+ :compression => Model::CompressionType.to_compression_type(compression),
17
+ :bloomfilter => bloomfilter,
18
+ :max_versions => max_versions)
19
+ column_families << column_descriptor
20
+ end
21
+
22
+ Model::TableDescriptor.new(:name => name, :column_families => column_families)
23
+ end
24
+ end
25
+ end
26
+ end
data/readme.md ADDED
@@ -0,0 +1,47 @@
1
+ # Ruby driver for HBase
2
+
3
+ Small and handy Ruby driver for HBase (via Stargate RESTfull interface).
4
+
5
+ # Usage
6
+
7
+ **Warning:** HBase and Stargate should be installed and running.
8
+
9
+ require 'hbase'
10
+
11
+ hbase = HBase.new('http://localhost:8080')
12
+
13
+ # creating table
14
+ hbase.create 'users'
15
+
16
+ # listing all tables
17
+ p hbase.all # => ['users']
18
+
19
+ # create record
20
+ users.update 'john', 'attr:email' => 'john@mail.com'
21
+
22
+ # get record
23
+ john = users['john']
24
+ p john['attr:email'] # => "john@mail.com"
25
+
26
+ # update record
27
+ users.update 'john', 'attr:email' => "another@mail.com"
28
+ p john['attr:email'] # => "another@mail.com"
29
+
30
+ # deleting record
31
+ users.delete 'john'
32
+
33
+ # scanning
34
+ users.update 'john', 'attr:email' => 'john@mail.com'
35
+ users.update 'mario', 'attr:email' => 'mario@mail.com'
36
+ users.update 'stanley', 'attr:email' => 'stanley@mail.com'
37
+
38
+ list = users.scan :start => 'john', :batch => 5, :columns => ['attr:']
39
+ p list.collect{|user| user['attr:email']} # => ['john@mail.com', 'mario@mail.com', 'stanley@mail.com']
40
+
41
+ # Installation
42
+
43
+ $ gem install ruby-driver
44
+
45
+ For installation of HBase and Stargate please see HBase docs (google it).
46
+
47
+ **Warning:** currently HBase distributed with RESTful interface called 'rest' and it's deprecated and broken. And because it looks very similar to Stargate it's easy to mix it up. So, be aware, you need to install and use the Stargate, not default and broken HBase REST client.
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hbase-driver
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Alexey Petrushin
14
+ - Dmitry Larkin
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-10 00:00:00 +03:00
20
+ default_executable:
21
+ dependencies: []
22
+
23
+ description:
24
+ email:
25
+ - Alexey.Petrushin@railsware.com
26
+ - dmitry.larkin@railsware.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/hbase/cell.rb
35
+ - lib/hbase/hbase.rb
36
+ - lib/hbase/record.rb
37
+ - lib/hbase/support.rb
38
+ - lib/hbase/table.rb
39
+ - lib/hbase.rb
40
+ - lib/stargate/client.rb
41
+ - lib/stargate/exception.rb
42
+ - lib/stargate/model/column.rb
43
+ - lib/stargate/model/column_descriptor.rb
44
+ - lib/stargate/model/region_descriptor.rb
45
+ - lib/stargate/model/row.rb
46
+ - lib/stargate/model/scanner.rb
47
+ - lib/stargate/model/table_descriptor.rb
48
+ - lib/stargate/model.rb
49
+ - lib/stargate/operation/meta_operation.rb
50
+ - lib/stargate/operation/row_operation.rb
51
+ - lib/stargate/operation/scanner_operation.rb
52
+ - lib/stargate/operation/table_operation.rb
53
+ - lib/stargate/request/basic_request.rb
54
+ - lib/stargate/request/meta_request.rb
55
+ - lib/stargate/request/row_request.rb
56
+ - lib/stargate/request/scanner_request.rb
57
+ - lib/stargate/request/table_request.rb
58
+ - lib/stargate/request.rb
59
+ - lib/stargate/response/basic_response.rb
60
+ - lib/stargate/response/meta_response.rb
61
+ - lib/stargate/response/row_response.rb
62
+ - lib/stargate/response/scanner_response.rb
63
+ - lib/stargate/response/table_response.rb
64
+ - lib/stargate/response.rb
65
+ - lib/stargate.rb
66
+ - readme.md
67
+ - Rakefile
68
+ has_rdoc: true
69
+ homepage: http://github.com/railsware/hbase-driver
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options: []
74
+
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.3.7
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: HBase Ruby Driver
102
+ test_files: []
103
+