cql-rb 1.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # Ruby CQL 3 driver
2
+
3
+ This is a work in progress, no usable version exists yet.
4
+
5
+ ## Copyright
6
+
7
+ Copyright 2013 Theo Hultberg
8
+
9
+ _Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License You may obtain a copy of the License at_
10
+
11
+ [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
12
+
13
+ _Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License._
data/bin/cqlexec ADDED
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ $: << File.expand_path('../../lib', __FILE__)
5
+
6
+ require 'optparse'
7
+ require 'set'
8
+ require 'cql'
9
+
10
+
11
+ class CqlExecutor
12
+ def initialize(args)
13
+ @options = parse_options!(args)
14
+ end
15
+
16
+ def run(io)
17
+ @client = Cql::Client.new(host: @options[:host], port: @options[:port]).start!
18
+
19
+ # TODO register for events
20
+
21
+ begin
22
+ buffer = ''
23
+ while (line = io.gets)
24
+ buffer << line
25
+ if semi_index = buffer.index(';')
26
+ query = buffer.slice!(0, semi_index + 1)
27
+ prepare_and_execute_request(query)
28
+ end
29
+ end
30
+ prepare_and_execute_request(buffer)
31
+ rescue Cql::CqlError => e
32
+ abort("Error: #{e.message} (#{e.class})")
33
+ rescue Interrupt
34
+ exit
35
+ ensure
36
+ @client.shutdown!
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def parse_options!(args)
43
+ options = {}
44
+
45
+ option_parser = OptionParser.new do |opts|
46
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
47
+ opts.separator('')
48
+ opts.on('--help', 'Show this message') do
49
+ $stderr.puts(opts)
50
+ exit!
51
+ end
52
+ opts.on('-h', '--host [HOST]', 'Connect to HOST, defaults to localhost') do |host|
53
+ options[:host] = host
54
+ end
55
+ opts.on('-p', '--port [PORT]', Integer, 'Connect to PORT, defaults to 9042') do |port|
56
+ options[:port] = port
57
+ end
58
+ opts.on('-v', '--verbose', 'Print requests to STDERR') do |port|
59
+ options[:verbose] = true
60
+ end
61
+ opts.separator('')
62
+ opts.separator('Pass CQL commands on STDIN, prints results on STDOUT')
63
+ opts.separator('')
64
+ end
65
+
66
+ option_parser.parse!
67
+
68
+ options
69
+ end
70
+
71
+ def prepare_and_execute_request(query)
72
+ query.chomp!(';')
73
+ query.strip!
74
+ unless query.empty?
75
+ format_table_output(@client.execute(query, :one))
76
+ end
77
+ end
78
+
79
+ def format_table_output(rows)
80
+ table_names = []
81
+ header = ''
82
+ row_format = ''
83
+ divider = ''
84
+ column_widths = {}
85
+ if rows && rows.any?
86
+ rows.metadata.each do |ks, table, column, type|
87
+ table_names << [ks, table].join('.')
88
+ column_width = [format_value(rows.first[column]).length, column.length].max
89
+ column_widths[column] = column_width
90
+ format = "%-#{column_width}.#{column_width}s"
91
+ header << "#{format} | " % [column]
92
+ row_format << "#{format} | "
93
+ divider << ('-' * column_width) << '-+-'
94
+ end
95
+ row_format.sub!(/ \| $/, '')
96
+ divider = divider[0..-4]
97
+ table_name = table_names.uniq.join(', ')
98
+
99
+ $stdout.puts(table_name)
100
+ $stdout.puts('=' * table_name.length)
101
+ $stdout.puts(header[0, divider.length])
102
+ $stdout.puts(divider)
103
+
104
+ rows.each do |row|
105
+ values = rows.metadata.map do |_, _, column, _|
106
+ limit_width(format_value(row[column]), column_widths[column])
107
+ end
108
+ $stdout.puts(row_format % values)
109
+ end
110
+ else
111
+ $stdout.puts('(empty result set)')
112
+ end
113
+ end
114
+
115
+ def format_value(value)
116
+ case value
117
+ when Set
118
+ value.to_a.to_s
119
+ when nil
120
+ '(nil)'
121
+ else
122
+ value.to_s
123
+ end
124
+ end
125
+
126
+ def limit_width(value, width)
127
+ if value.length > width
128
+ value[0, width - 1] << '…'
129
+ else
130
+ value
131
+ end
132
+ end
133
+ end
134
+
135
+ CqlExecutor.new(ARGV).run(STDIN)
data/lib/cql.rb ADDED
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ CqlError = Class.new(StandardError)
5
+ end
6
+
7
+ require 'cql/uuid'
8
+ require 'cql/future'
9
+ require 'cql/io'
10
+ require 'cql/protocol'
11
+ require 'cql/client'
data/lib/cql/client.rb ADDED
@@ -0,0 +1,196 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ NotConnectedError = Class.new(CqlError)
5
+ InvalidKeyspaceNameError = Class.new(CqlError)
6
+
7
+ class QueryError < CqlError
8
+ attr_reader :code
9
+
10
+ def initialize(code, message)
11
+ super(message)
12
+ @code = code
13
+ end
14
+ end
15
+
16
+ class Client
17
+ def initialize(options={})
18
+ connection_timeout = options[:connection_timeout]
19
+ @host = options[:host] || 'localhost'
20
+ @port = options[:port] || 9042
21
+ @io_reactor = options[:io_reactor] || Io::IoReactor.new(connection_timeout: connection_timeout)
22
+ @lock = Mutex.new
23
+ @started = false
24
+ @shut_down = false
25
+ @initial_keyspace = options[:keyspace]
26
+ @connection_keyspaces = {}
27
+ end
28
+
29
+ def start!
30
+ @lock.synchronize do
31
+ return if @started
32
+ @started = true
33
+ end
34
+ @io_reactor.start
35
+ hosts = @host.split(',')
36
+ start_request = Protocol::StartupRequest.new
37
+ connection_futures = hosts.map do |host|
38
+ @io_reactor.add_connection(host, @port).flat_map do |connection_id|
39
+ execute_request(start_request, connection_id).map { connection_id }
40
+ end
41
+ end
42
+ @connection_ids = Future.combine(*connection_futures).get
43
+ use(@initial_keyspace) if @initial_keyspace
44
+ self
45
+ end
46
+
47
+ def shutdown!
48
+ @lock.synchronize do
49
+ return if @shut_down
50
+ @shut_down = true
51
+ @started = false
52
+ end
53
+ @io_reactor.stop.get
54
+ self
55
+ end
56
+
57
+ def keyspace
58
+ @lock.synchronize do
59
+ return @connection_ids.map { |id| @connection_keyspaces[id] }.first
60
+ end
61
+ end
62
+
63
+ def use(keyspace, connection_ids=@connection_ids)
64
+ raise NotConnectedError unless @started
65
+ if check_keyspace_name!(keyspace)
66
+ @lock.synchronize do
67
+ connection_ids = connection_ids.select { |id| @connection_keyspaces[id] != keyspace }
68
+ end
69
+ if connection_ids.any?
70
+ futures = connection_ids.map do |connection_id|
71
+ execute_request(Protocol::QueryRequest.new("USE #{keyspace}", :one), connection_id)
72
+ end
73
+ futures.compact!
74
+ Future.combine(*futures).get
75
+ end
76
+ nil
77
+ end
78
+ end
79
+
80
+ def execute(cql, consistency=:quorum)
81
+ result = execute_request(Protocol::QueryRequest.new(cql, consistency)).value
82
+ ensure_keyspace!
83
+ result
84
+ end
85
+
86
+ def execute_statement(connection_id, statement_id, metadata, values, consistency)
87
+ execute_request(Protocol::ExecuteRequest.new(statement_id, metadata, values, consistency), connection_id).value
88
+ end
89
+
90
+ def prepare(cql)
91
+ execute_request(Protocol::PrepareRequest.new(cql)).value
92
+ end
93
+
94
+ private
95
+
96
+ KEYSPACE_NAME_PATTERN = /^\w[\w\d_]*$/
97
+
98
+ def check_keyspace_name!(name)
99
+ if name !~ KEYSPACE_NAME_PATTERN
100
+ raise InvalidKeyspaceNameError, %("#{name}" is not a valid keyspace name)
101
+ end
102
+ true
103
+ end
104
+
105
+ def execute_request(request, connection_id=nil)
106
+ raise NotConnectedError unless @started
107
+ @io_reactor.queue_request(request, connection_id).map do |response, connection_id|
108
+ interpret_response!(response, connection_id)
109
+ end
110
+ end
111
+
112
+ def interpret_response!(response, connection_id)
113
+ case response
114
+ when Protocol::ErrorResponse
115
+ raise QueryError.new(response.code, response.message)
116
+ when Protocol::RowsResultResponse
117
+ QueryResult.new(response.metadata, response.rows)
118
+ when Protocol::PreparedResultResponse
119
+ PreparedStatement.new(self, connection_id, response.id, response.metadata)
120
+ when Protocol::SetKeyspaceResultResponse
121
+ @lock.synchronize do
122
+ @last_keyspace_change = @connection_keyspaces[connection_id] = response.keyspace
123
+ end
124
+ nil
125
+ else
126
+ nil
127
+ end
128
+ end
129
+
130
+ def ensure_keyspace!
131
+ ks = nil
132
+ @lock.synchronize do
133
+ ks = @last_keyspace_change
134
+ return unless @last_keyspace_change
135
+ end
136
+ use(ks, @connection_ids) if ks
137
+ end
138
+
139
+ class PreparedStatement
140
+ def initialize(*args)
141
+ @client, @connection_id, @statement_id, @metadata = args
142
+ end
143
+
144
+ def execute(*args)
145
+ @client.execute_statement(@connection_id, @statement_id, @metadata, args, :quorum)
146
+ end
147
+ end
148
+
149
+ class QueryResult
150
+ include Enumerable
151
+
152
+ attr_reader :metadata
153
+
154
+ def initialize(metadata, rows)
155
+ @metadata = ResultMetadata.new(metadata)
156
+ @rows = rows
157
+ end
158
+
159
+ def empty?
160
+ @rows.empty?
161
+ end
162
+
163
+ def each(&block)
164
+ @rows.each(&block)
165
+ end
166
+ end
167
+
168
+ class ResultMetadata
169
+ include Enumerable
170
+
171
+ def initialize(metadata)
172
+ @metadata = Hash[metadata.map { |m| mm = ColumnMetadata.new(*m); [mm.column_name, mm] }]
173
+ end
174
+
175
+ def [](column_name)
176
+ @metadata[column_name]
177
+ end
178
+
179
+ def each(&block)
180
+ @metadata.each_value(&block)
181
+ end
182
+ end
183
+
184
+ class ColumnMetadata
185
+ attr_reader :keyspace, :table, :table, :column_name, :type
186
+
187
+ def initialize(*args)
188
+ @keyspace, @table, @column_name, @type = args
189
+ end
190
+
191
+ def to_ary
192
+ [@keyspace, @table, @column_name, @type]
193
+ end
194
+ end
195
+ end
196
+ end
data/lib/cql/future.rb ADDED
@@ -0,0 +1,176 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ FutureError = Class.new(CqlError)
5
+
6
+ class Future
7
+ def initialize
8
+ @complete_listeners = []
9
+ @failure_listeners = []
10
+ @value_barrier = Queue.new
11
+ @state_lock = Mutex.new
12
+ end
13
+
14
+ def self.combine(*futures)
15
+ CombinedFuture.new(*futures)
16
+ end
17
+
18
+ def self.completed(value=nil)
19
+ CompletedFuture.new(value)
20
+ end
21
+
22
+ def self.failed(error)
23
+ FailedFuture.new(error)
24
+ end
25
+
26
+ def complete!(v=nil)
27
+ @state_lock.synchronize do
28
+ raise FutureError, 'Future already completed' if complete? || failed?
29
+ @value = v
30
+ @complete_listeners.each do |listener|
31
+ listener.call(@value)
32
+ end
33
+ end
34
+ ensure
35
+ @state_lock.synchronize do
36
+ @value_barrier << :ping
37
+ end
38
+ end
39
+
40
+ def complete?
41
+ defined? @value
42
+ end
43
+
44
+ def on_complete(&listener)
45
+ @state_lock.synchronize do
46
+ if complete?
47
+ listener.call(value)
48
+ else
49
+ @complete_listeners << listener
50
+ end
51
+ end
52
+ end
53
+
54
+ def value
55
+ raise @error if @error
56
+ return @value if defined? @value
57
+ @value_barrier.pop
58
+ raise @error if @error
59
+ return @value
60
+ end
61
+ alias_method :get, :value
62
+
63
+ def fail!(error)
64
+ @state_lock.synchronize do
65
+ raise FutureError, 'Future already completed' if failed? || complete?
66
+ @error = error
67
+ @failure_listeners.each do |listener|
68
+ listener.call(error)
69
+ end
70
+ end
71
+ ensure
72
+ @state_lock.synchronize do
73
+ @value_barrier << :ping
74
+ end
75
+ end
76
+
77
+ def failed?
78
+ !!@error
79
+ end
80
+
81
+ def on_failure(&listener)
82
+ @state_lock.synchronize do
83
+ if failed?
84
+ listener.call(@error)
85
+ else
86
+ @failure_listeners << listener
87
+ end
88
+ end
89
+ end
90
+
91
+ def map(&block)
92
+ fp = Future.new
93
+ on_failure { |e| fp.fail!(e) }
94
+ on_complete do |v|
95
+ begin
96
+ vv = block.call(v)
97
+ fp.complete!(vv)
98
+ rescue => e
99
+ fp.fail!(e)
100
+ end
101
+ end
102
+ fp
103
+ end
104
+
105
+ def flat_map(&block)
106
+ fp = Future.new
107
+ on_failure { |e| fp.fail!(e) }
108
+ on_complete do |v|
109
+ begin
110
+ fpp = block.call(v)
111
+ fpp.on_failure { |e| fp.fail!(e) }
112
+ fpp.on_complete do |vv|
113
+ fp.complete!(vv)
114
+ end
115
+ rescue => e
116
+ fp.fail!(e)
117
+ end
118
+ end
119
+ fp
120
+ end
121
+ end
122
+
123
+ class CompletedFuture < Future
124
+ def initialize(value=nil)
125
+ super()
126
+ complete!(value)
127
+ end
128
+ end
129
+
130
+ class FailedFuture < Future
131
+ def initialize(error)
132
+ super()
133
+ fail!(error)
134
+ end
135
+ end
136
+
137
+ class CombinedFuture < Future
138
+ def initialize(*futures)
139
+ super()
140
+ values = [nil] * futures.size
141
+ completed = [false] * futures.size
142
+ futures.each_with_index do |f, i|
143
+ f.on_complete do |v|
144
+ all_done = false
145
+ @state_lock.synchronize do
146
+ values[i] = v
147
+ completed[i] = true
148
+ all_done = completed.all?
149
+ end
150
+ if all_done
151
+ combined_complete!(values)
152
+ end
153
+ end
154
+ f.on_failure do |e|
155
+ unless failed?
156
+ combined_fail!(e)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ alias_method :combined_complete!, :complete!
163
+ private :combined_complete!
164
+
165
+ alias_method :combined_fail!, :fail!
166
+ private :combined_fail!
167
+
168
+ def complete!(v=nil)
169
+ raise FutureError, 'Cannot complete a combined future'
170
+ end
171
+
172
+ def fail!(e)
173
+ raise FutureError, 'Cannot fail a combined future'
174
+ end
175
+ end
176
+ end