voltrb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,8 @@
1
+ = Change log
2
+
3
+ == 0.0.1
4
+ * Initial release.
5
+
6
+ == 0.0.2
7
+ * Updated the README about the Unicode string issue. The trunk version now has this fixed.
8
+ * More robust handling of different date & time types.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Junjun Olympia
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,160 @@
1
+ = VoltRb: A gem client for VoltDB
2
+
3
+ VoltRB is a gem client for VoltDB that uses the JSON interface.
4
+
5
+ = Installing VoltRb
6
+
7
+ Get from RubyForge.
8
+
9
+ % gem install voltrb
10
+
11
+ = Example
12
+
13
+ This is the Ruby version of VoltDB's hello world example.
14
+
15
+ require 'voltrb'
16
+
17
+ # Create a client and connect to the VoltDB instance running in the same machine.
18
+ client = VoltRb::Client.new
19
+
20
+ # Insert "Hello, World" for different languages.
21
+ client.call_procedure("Insert", "Hello", "World", "English")
22
+ client.call_procedure("Insert", "Bonjour", "Monde", "French")
23
+ client.call_procedure("Insert", "Hola", "Mundo", "Spanish")
24
+ client.call_procedure("Insert", "Hej", "Verden", "Danish")
25
+ client.call_procedure("Insert", "Ciao", "Mondo", "Italian")
26
+
27
+ # Say it in Spanish!
28
+ response = client.call_procedure("Select", "Spanish")
29
+ puts "#{response.results[0].first[:HELLO]}, #{response.results[0].first[:WORLD]}!"
30
+
31
+ = Usage
32
+
33
+ == Initialization
34
+
35
+ Initializing a client without passing any arguments will have it connect to the VoltDB instance running in localhost.
36
+
37
+ client = VoltRb::Client.new
38
+
39
+ Or we can specify options as a hash.
40
+
41
+ client = VoltRb::Client.new({:host => "voltdbhost", :port => 8888})
42
+
43
+ Right now, VoltRb only knows two:
44
+
45
+ * host - should point to the name or IP address of the VoltDB instance.
46
+ * port - in case the instance isn't listening on the default port (8080).
47
+
48
+ There are no options yet for authentication since the JSON interface doesn't implement any. See Notes for more.
49
+
50
+ == Stored Procedure Invocation
51
+
52
+ We call VoltDB stored procedures using Client#call_procedure. The first argument is the name of the procedure followed by its parameters if any.
53
+
54
+ client.call_procedure(:Insert, "Hello", "World", "English")
55
+
56
+ Ruby types are automatically mapped to VoltDB types as below:
57
+
58
+ Ruby:: VoltDB
59
+ Fixnum, Bignum:: Tiny, Small, Integer, Long
60
+ Float:: Float, Double
61
+ BigDecimal:: Decimal
62
+ Time, Date, DateTime:: Timestamp
63
+ String:: String
64
+
65
+ Nils become Nulls.
66
+
67
+ == Stored Procedure Response
68
+
69
+ VoltDB stored procedures can return multiple rowsets (VoltTable objects). This array of rowsets is returned by ProcedureResponse#results. In the hello world example, the Select procedure returns only one so:
70
+
71
+ response = client.call_procedure("Select", "Spanish")
72
+ response.results[0]
73
+ => #<VoltRb::VoltTable:0x17952c4 @raw={"status"=>-128, "schema"=>[{"name"=>"HELLO", "type"=>9},
74
+ {"name"=>"WORLD", "type"=>9}], "data"=>[["Hola", "Mundo"]]}>
75
+
76
+ Each VoltTable / rowset implements Enumerable so we can access the rows using the usual methods #each, #all, #first, #last.
77
+
78
+ response.results[0].each { |row| puts row }
79
+
80
+ Each row is a hash with keys that are symbolized column names.
81
+
82
+ response.results[0].each { |row| puts "#{row[:HELLO]}, #{row[:WORLD]}" }
83
+ Hola, Mundo
84
+ => [["Hola", "Mundo"]]
85
+
86
+ You can get the list of columns by looking at the array returned by VoltTable#schema.
87
+
88
+ response.results[0].schema
89
+ => [#<struct VoltRb::VoltColumn name=:HELLO, type=9>, #<struct VoltRb::VoltColumn name=:WORLD, type=9>]
90
+
91
+ == Catching Errors
92
+
93
+ Standard Ruby errors will be raised in case of network connection problems. VoltDB-specific errors like invalid procedure arguments and constraint violations will raise VoltRb::VoltError. We can find out more by inspecting #status_string or #app_status_string.
94
+
95
+ begin
96
+ client.call_procedure("Insert", "Hello", "World", "English")
97
+ # This second call will cause a unique constraint violation.
98
+ client.call_procedure("Insert", "Hello", "World", "English")
99
+ rescue VoltRb::VoltError => bang
100
+ puts "Error: #{bang.status_string}"
101
+ end
102
+
103
+ Error:
104
+ ========================================================
105
+ VOLTDB ERROR: CONSTRAINT VIOLATION
106
+ Attempted violation of constraint
107
+ Constraint Type UNIQUE, Table CatalogId HELLOWORLD
108
+ header size: 35
109
+ status code: -128 column count: 3
110
+ cols (HELLO:STRING), (WORLD:STRING), (DIALECT:STRING),
111
+ rows -
112
+ Hello, World, English,
113
+
114
+ at Insert.run(Insert.java:19)
115
+ ========================================================
116
+
117
+ = Notes
118
+
119
+ == User Authentication
120
+
121
+ As of VoltDB v1.1, the JSON interface does not implement any form of authentication.
122
+
123
+ The following text was taken from VoltDB's JSON documentation:
124
+
125
+ <em>Note that there is currently no way to authenticate the client interface using JSON. In other words, the JSON interface operates as an "anonymous" client. If security is enabled for the database, use of the JSON interface is essentially disabled because any attempt to execute a stored procedure will result in a protection violation.</em>
126
+
127
+ This forum thread says they expect to have the feature in v1.2
128
+
129
+ http://community.voltdb.com/node/241
130
+
131
+ == Unicode Strings
132
+
133
+ As of v1.1 VoltDB's JSON interface does not properly return UTF-8 strings so non-Latin characters don't display correctly.
134
+
135
+ http://community.voltdb.com/node/263
136
+
137
+ The fix is now in their trunk version. It'll be included in the next official release of VoltDB (v1.2). Or you can build directly from trunk.
138
+
139
+ svn checkout http://svnmirror.voltdb.com/eng/trunk/
140
+
141
+ = License
142
+
143
+ This gem has been released under the MIT License[link:files/MIT-LICENSE.html].
144
+
145
+ = Warranty
146
+
147
+ This software is provided "as is" and without any express or
148
+ implied warranties, including, without limitation, the implied
149
+ warranties of merchantibility and fitness for a particular
150
+ purpose.
151
+
152
+ == Contributing
153
+
154
+ The source code is hosted on http://github.com/beljun/voltrb.
155
+
156
+ == Credits
157
+
158
+ Author: Junjun Olympia (@beljun) / http://rubyredtomatoes.com
159
+
160
+
@@ -0,0 +1,109 @@
1
+ require 'json'
2
+ require 'rest_client'
3
+ require 'date'
4
+ require 'time'
5
+ require 'bigdecimal'
6
+
7
+ module VoltRb
8
+ # This is the main class we use to interact with the VoltDB instance.
9
+ #
10
+ # == Initialization
11
+ # Create a client and connect to the instance in localhost:
12
+ # client = VoltRb::Client.new
13
+ #
14
+ # Or pass it a hash of options like:
15
+ # client = VoltRb::Client.new({:host => "voltdb_server", :port => 8888})
16
+ #
17
+ # See the new method for more details.
18
+ #
19
+ # == Calling stored procedures
20
+ # Call a stored procedure:
21
+ # client.call_procedure(:MyStoredProc, "This is the first argument", 2, Date.today)
22
+ #
23
+ # The call above invokes the VoltDB stored procedure named "MyStoredProc".
24
+ # The procedure expects 3 parameters (a String, a small integer, and a Date/Timestamp) so we pass these in order.
25
+ # See call_procedure method for more.
26
+ #
27
+ # == Getting the stored procedure response
28
+ # call_procedure returns a ProcedureResponse object which will contain the rows returned if any:
29
+ # response = client.call_procedure(:AnotherProc, 1)
30
+ # puts response.results[0].first[:TITLE]
31
+ #
32
+ # In the above example, we invoke the procedure "AnotherProc" and expect one rowset and
33
+ # one row with a column named "TITLE".
34
+ # See ProcedureResponse for more.
35
+ #
36
+ # == Handling errors
37
+ # In case of typical errors like network connection problems, standard Ruby exceptions will be raised.
38
+ # In case of VoltDB-specific errors like invalid stored procedure arguments or constraint violations,
39
+ # a VoltDB::VoltError exception object will be raised.
40
+ # begin
41
+ # client.call_procedure(:MyStoredProc, 2)
42
+ # rescue VoltDB::VoltError => bang
43
+ # puts bang.status_string
44
+ # raise
45
+ # end
46
+ #
47
+ # The above call will raise an error because it expects 3 parameters but was only given one.
48
+ # We can find the details of the error by inspecting status_string or app_status_string.
49
+ # See VoltError for more.
50
+ class Client
51
+ attr_reader :host, :port
52
+ attr_accessor :api_root
53
+
54
+ # Calling new without any arguments will create a client and have it connect to the VoltDB instance running
55
+ # in localhost:8080.
56
+ # Pass an options hash to modify these defaults.
57
+ # client = VoltRb::Client.new({:host => "voltdb_server", :port => 8888})
58
+ #
59
+ # Right now, only two options are recognized:
60
+ # * host - the machine name or IP address
61
+ # * port - other than the default
62
+ #
63
+ # <em>Note on user authentication:</em>
64
+ # As of v1.1, the VoltDB JSON interface does not implement any form of authentication.
65
+ # See the README[link:files/README.html] for more.
66
+ def initialize(options = {})
67
+ @host = options[:host] || "localhost"
68
+ @port = options[:port] || 8080
69
+ @api_root = "http://#{@host}:#{@port}/api/1.0/"
70
+ end
71
+
72
+ # Invoke a VoltDB stored procedure by calling this method with the first argument being the stored procedure name.
73
+ # Follow that with any number of parameters needed by the stored procedure itself.
74
+ # client.call_procedure(:MyStoredProc, "This is the first argument for :MyStoredProc", 2, Date.today)
75
+ #
76
+ # Ruby types map to the stored procedure data types as below:
77
+ # Ruby:: VoltDB
78
+ # Fixnum, Bignum:: Tiny, Small, Integer, Long
79
+ # Float:: Float, Double
80
+ # BigDecimal:: Decimal
81
+ # Time, Date, DateTime:: Timestamp
82
+ # String:: String
83
+ #
84
+ # Nils become Nulls.
85
+ # If a stored procedure returns one or more rowsets that we're interested in, see the ProcedureResponse object
86
+ # that's returned by this method.
87
+ # In case of errors, handle the usual Ruby errors and the VoltDB-specfic VoltError (this inherits from StandardError).
88
+ def call_procedure(procedure_name, *args)
89
+ params = args.inject([]) { |o,e| o << prep_param(e); o }
90
+ response = RestClient.post(@api_root, :Procedure => procedure_name.to_s, :Parameters => params.to_json, :content_type => "text/plain; charset=UTF-8", :accept => :json)
91
+ proc_resp = ProcedureResponse.new(response)
92
+ raise(VoltError.new(proc_resp.status, proc_resp.status_string, proc_resp.app_status, proc_resp.app_status_string), "A VoltDB procedure error occurred. See the exception object for details.") if proc_resp.raw["status"] != 1 or proc_resp.raw["appstatus"] != -128
93
+ proc_resp
94
+ end
95
+
96
+ private
97
+ def prep_param(val)
98
+ case val
99
+ when BigDecimal then val.to_s('F')
100
+ when Time then val.to_i * 1000
101
+ # Note: case DateTime MUST come before Date because Ruby seems to think DateTime is a Date. But Date is not a DateTime. Ruby bug?
102
+ when DateTime then Time.parse(val.strftime("%a %b %d %H:%M:%S %z %Y")).to_i * 1000
103
+ when Date then Time.parse(val.strftime("%b %d %Y")).to_i * 1000
104
+ else val
105
+ end
106
+ end
107
+ end
108
+ end
109
+
@@ -0,0 +1,11 @@
1
+ module VoltRb
2
+ # Raised in case of VoltDB-specific errors such as stored procedure invalid arguments or constraint violations.
3
+ # Inspect status, status_string, app_status, and app_status_string for details on the error returned by VoltDB.
4
+ class VoltError < StandardError
5
+ attr_reader :status, :status_string, :app_status, :app_status_string
6
+
7
+ def initialize(status, status_string, app_status, app_status_string)
8
+ @status, @status_string, @app_status, @app_status_string = status, status_string, app_status, app_status_string
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ module VoltRb
2
+ # An object of this class is returned by a call to Client#call_procedure.
3
+ # response = client.call_procedure("Select", "Spanish")
4
+ # Some VoltDB stored procedures return multiple rowsets. (Think of one rowset as the rows returned by a SQL select.)
5
+ # You get access to these array of rowsets (VoltTable objects) thru results.
6
+ class ProcedureResponse
7
+ # This is a plain Ruby hash derived from the JSON response from VoltDB.
8
+ # We can use this hash to access the entire response entity as an alternative to the object methods below.
9
+ # May save us a few CPU cycles. (Benchmark to be sure.)
10
+ # No conversions are done for values of type Decimal and Timestamp so we'll have to handle those ourselves.
11
+ # We can also use the conversion functions in Utils
12
+ attr_reader :raw
13
+
14
+ def initialize(response)
15
+ @raw = JSON.parse(response)
16
+ end
17
+
18
+ def status
19
+ @status ||= @raw["status"]
20
+ end
21
+
22
+ def status_string
23
+ @status_string ||= @raw["statusstring"]
24
+ end
25
+
26
+ def app_status
27
+ @app_status ||= @raw["appstatus"]
28
+ end
29
+
30
+ def app_status_string
31
+ @app_status_string ||= @raw["appstatusstring"]
32
+ end
33
+
34
+ # Returns an array of VoltTable.
35
+ def results
36
+ @results ||= hydrate_results
37
+ end
38
+
39
+ private
40
+ def hydrate_results
41
+ @raw["results"].inject([]) { |o,e| o << VoltTable.new(e); o }
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,15 @@
1
+ module VoltRb
2
+ # VoltDB constants to indicate data types.
3
+ module Types
4
+ ARRAY = -99
5
+ NULL = 1
6
+ TINYINT = 3
7
+ SMALLINT = 4
8
+ INTEGER = 5
9
+ BIGINT = 6
10
+ FLOAT = 8
11
+ STRING = 9
12
+ TIMESTAMP = 11
13
+ DECIMAL = 22
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ require 'bigdecimal'
2
+
3
+ module VoltRb
4
+ # Set of utility functions.
5
+ module Utils
6
+ # Converts a string to a Big Decimal.
7
+ def dec_from_str(str)
8
+ BigDecimal.new(str)
9
+ end
10
+
11
+ # Converts an integer (millisecond value since epoch) to a Time object.
12
+ def time_from_milli_epoch(num)
13
+ Time.at(num/1000)
14
+ end
15
+
16
+ # Converts a val of type into its Ruby equivalent.
17
+ def ruby_from_volt(val, type)
18
+ return nil if val.nil?
19
+
20
+ case type
21
+ when Types::DECIMAL then dec_from_str(val)
22
+ when Types::TIMESTAMP then time_from_milli_epoch(val)
23
+ else val
24
+ end
25
+ end
26
+
27
+ module_function :dec_from_str, :time_from_milli_epoch, :ruby_from_volt
28
+ end
29
+ end
30
+
@@ -0,0 +1,5 @@
1
+ module VoltRb
2
+ # This gem's version.
3
+ VERSION = "0.0.2"
4
+ end
5
+
@@ -0,0 +1,62 @@
1
+ module VoltRb
2
+ VoltColumn = Struct.new :name, :type
3
+
4
+ # This object contains one or more rows (a rowset).
5
+ # Enumerable is implemented so we can access the rows via the usual each, all, first, last.
6
+ # first_row = response.results[0].first
7
+ # Each row is a hash with the symbolized column names for keys.
8
+ # puts first_row[:MY_COLUMN]
9
+ # We can get a list of columns via schema.
10
+ class VoltTable
11
+ include Enumerable
12
+
13
+ def initialize(hash_data)
14
+ @raw = hash_data
15
+ end
16
+
17
+ def status
18
+ @status ||= @raw["status"]
19
+ end
20
+
21
+ def schema
22
+ @schema ||= hydrate_schema
23
+ end
24
+
25
+ def each
26
+ # ToDo: Benchmark, benchmark, benchmark.
27
+ @raw["data"].each { |r| yield hydrate_row(r) }
28
+ end
29
+
30
+ def all
31
+ self.inject([]) { |o,e| o << e; o }
32
+ end
33
+
34
+ def first
35
+ hydrate_row(@raw["data"].first)
36
+ end
37
+
38
+ def last
39
+ hydrate_row(@raw["data"].last)
40
+ end
41
+
42
+ def empty?
43
+ @raw["data"].empty?
44
+ end
45
+
46
+ def count
47
+ @raw["data"].size
48
+ end
49
+
50
+ private
51
+ def hydrate_schema
52
+ @raw["schema"].inject([]) { |o, e| o << VoltColumn.new(e["name"].to_sym, e["type"]); o }
53
+ end
54
+
55
+ def hydrate_row(data)
56
+ row = {}
57
+ self.schema.each_with_index { |col, i| row[col.name.to_sym] = Utils.ruby_from_volt(data[i], col.type) }
58
+ row
59
+ end
60
+ end
61
+ end
62
+
data/lib/voltrb.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'json'
2
+ require 'rest_client'
3
+ require 'date'
4
+ require 'time'
5
+ require 'bigdecimal'
6
+ require 'voltrb/types'
7
+ require 'voltrb/utils'
8
+ require 'voltrb/exceptions'
9
+ require 'voltrb/volt_table'
10
+ require 'voltrb/procedure_response'
11
+ require 'voltrb/client'
12
+
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: voltrb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Junjun Olympia
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-07 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rest-client
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: VoltRb is a gem client for VoltDB. This early release uses the JSON interface and works with VoltDB v1.1 onwards.
64
+ email: romeo.olympia@gmail.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files: []
70
+
71
+ files:
72
+ - lib/voltrb/client.rb
73
+ - lib/voltrb/exceptions.rb
74
+ - lib/voltrb/procedure_response.rb
75
+ - lib/voltrb/types.rb
76
+ - lib/voltrb/utils.rb
77
+ - lib/voltrb/version.rb
78
+ - lib/voltrb/volt_table.rb
79
+ - lib/voltrb.rb
80
+ - README.rdoc
81
+ - MIT-LICENSE
82
+ - CHANGELOG
83
+ has_rdoc: true
84
+ homepage: http://github.com/beljun/voltrb
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options: []
89
+
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project: voltrb
113
+ rubygems_version: 1.3.7
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: VoltRb is a gem client for VoltDB.
117
+ test_files: []
118
+