cql-rb 2.0.0.pre0 → 2.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -2
  3. data/lib/cql.rb +8 -3
  4. data/lib/cql/client.rb +21 -356
  5. data/lib/cql/client/authenticators.rb +70 -0
  6. data/lib/cql/client/batch.rb +54 -0
  7. data/lib/cql/client/{asynchronous_client.rb → client.rb} +241 -6
  8. data/lib/cql/client/connector.rb +3 -2
  9. data/lib/cql/client/{asynchronous_prepared_statement.rb → prepared_statement.rb} +103 -0
  10. data/lib/cql/protocol.rb +1 -2
  11. data/lib/cql/protocol/cql_byte_buffer.rb +285 -0
  12. data/lib/cql/protocol/cql_protocol_handler.rb +3 -3
  13. data/lib/cql/protocol/frame_decoder.rb +3 -3
  14. data/lib/cql/protocol/frame_encoder.rb +2 -2
  15. data/lib/cql/protocol/request.rb +0 -2
  16. data/lib/cql/protocol/requests/auth_response_request.rb +2 -2
  17. data/lib/cql/protocol/requests/batch_request.rb +10 -10
  18. data/lib/cql/protocol/requests/credentials_request.rb +2 -2
  19. data/lib/cql/protocol/requests/execute_request.rb +13 -13
  20. data/lib/cql/protocol/requests/options_request.rb +2 -2
  21. data/lib/cql/protocol/requests/prepare_request.rb +2 -2
  22. data/lib/cql/protocol/requests/query_request.rb +13 -13
  23. data/lib/cql/protocol/requests/register_request.rb +2 -2
  24. data/lib/cql/protocol/requests/startup_request.rb +2 -2
  25. data/lib/cql/protocol/response.rb +2 -4
  26. data/lib/cql/protocol/responses/auth_challenge_response.rb +2 -2
  27. data/lib/cql/protocol/responses/auth_success_response.rb +2 -2
  28. data/lib/cql/protocol/responses/authenticate_response.rb +2 -2
  29. data/lib/cql/protocol/responses/detailed_error_response.rb +15 -15
  30. data/lib/cql/protocol/responses/error_response.rb +4 -4
  31. data/lib/cql/protocol/responses/event_response.rb +3 -3
  32. data/lib/cql/protocol/responses/prepared_result_response.rb +4 -4
  33. data/lib/cql/protocol/responses/raw_rows_result_response.rb +1 -1
  34. data/lib/cql/protocol/responses/ready_response.rb +1 -1
  35. data/lib/cql/protocol/responses/result_response.rb +3 -3
  36. data/lib/cql/protocol/responses/rows_result_response.rb +22 -22
  37. data/lib/cql/protocol/responses/schema_change_event_response.rb +2 -2
  38. data/lib/cql/protocol/responses/schema_change_result_response.rb +2 -2
  39. data/lib/cql/protocol/responses/set_keyspace_result_response.rb +2 -2
  40. data/lib/cql/protocol/responses/status_change_event_response.rb +2 -2
  41. data/lib/cql/protocol/responses/supported_response.rb +2 -2
  42. data/lib/cql/protocol/responses/void_result_response.rb +1 -1
  43. data/lib/cql/protocol/type_converter.rb +78 -81
  44. data/lib/cql/time_uuid.rb +6 -0
  45. data/lib/cql/uuid.rb +2 -1
  46. data/lib/cql/version.rb +1 -1
  47. data/spec/cql/client/batch_spec.rb +8 -8
  48. data/spec/cql/client/{asynchronous_client_spec.rb → client_spec.rb} +162 -0
  49. data/spec/cql/client/connector_spec.rb +13 -3
  50. data/spec/cql/client/{asynchronous_prepared_statement_spec.rb → prepared_statement_spec.rb} +148 -1
  51. data/spec/cql/client/request_runner_spec.rb +2 -2
  52. data/spec/cql/protocol/cql_byte_buffer_spec.rb +895 -0
  53. data/spec/cql/protocol/cql_protocol_handler_spec.rb +1 -1
  54. data/spec/cql/protocol/frame_decoder_spec.rb +14 -14
  55. data/spec/cql/protocol/frame_encoder_spec.rb +7 -7
  56. data/spec/cql/protocol/requests/auth_response_request_spec.rb +4 -4
  57. data/spec/cql/protocol/requests/batch_request_spec.rb +21 -21
  58. data/spec/cql/protocol/requests/credentials_request_spec.rb +2 -2
  59. data/spec/cql/protocol/requests/execute_request_spec.rb +13 -13
  60. data/spec/cql/protocol/requests/options_request_spec.rb +1 -1
  61. data/spec/cql/protocol/requests/prepare_request_spec.rb +2 -2
  62. data/spec/cql/protocol/requests/query_request_spec.rb +13 -13
  63. data/spec/cql/protocol/requests/register_request_spec.rb +2 -2
  64. data/spec/cql/protocol/requests/startup_request_spec.rb +4 -4
  65. data/spec/cql/protocol/responses/auth_challenge_response_spec.rb +5 -5
  66. data/spec/cql/protocol/responses/auth_success_response_spec.rb +5 -5
  67. data/spec/cql/protocol/responses/authenticate_response_spec.rb +3 -3
  68. data/spec/cql/protocol/responses/detailed_error_response_spec.rb +15 -15
  69. data/spec/cql/protocol/responses/error_response_spec.rb +5 -5
  70. data/spec/cql/protocol/responses/event_response_spec.rb +8 -8
  71. data/spec/cql/protocol/responses/prepared_result_response_spec.rb +7 -7
  72. data/spec/cql/protocol/responses/raw_rows_result_response_spec.rb +1 -1
  73. data/spec/cql/protocol/responses/ready_response_spec.rb +2 -2
  74. data/spec/cql/protocol/responses/result_response_spec.rb +16 -16
  75. data/spec/cql/protocol/responses/rows_result_response_spec.rb +21 -21
  76. data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +3 -3
  77. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +3 -3
  78. data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +2 -2
  79. data/spec/cql/protocol/responses/status_change_event_response_spec.rb +3 -3
  80. data/spec/cql/protocol/responses/supported_response_spec.rb +3 -3
  81. data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +3 -3
  82. data/spec/cql/protocol/responses/void_result_response_spec.rb +2 -2
  83. data/spec/cql/protocol/type_converter_spec.rb +25 -13
  84. data/spec/cql/time_uuid_spec.rb +17 -4
  85. data/spec/cql/uuid_spec.rb +5 -1
  86. data/spec/integration/protocol_spec.rb +48 -42
  87. data/spec/spec_helper.rb +0 -1
  88. metadata +27 -39
  89. data/lib/cql/byte_buffer.rb +0 -177
  90. data/lib/cql/client/synchronous_client.rb +0 -79
  91. data/lib/cql/client/synchronous_prepared_statement.rb +0 -63
  92. data/lib/cql/future.rb +0 -515
  93. data/lib/cql/io.rb +0 -15
  94. data/lib/cql/io/connection.rb +0 -220
  95. data/lib/cql/io/io_reactor.rb +0 -349
  96. data/lib/cql/protocol/decoding.rb +0 -187
  97. data/lib/cql/protocol/encoding.rb +0 -114
  98. data/spec/cql/byte_buffer_spec.rb +0 -337
  99. data/spec/cql/client/synchronous_client_spec.rb +0 -170
  100. data/spec/cql/client/synchronous_prepared_statement_spec.rb +0 -155
  101. data/spec/cql/future_spec.rb +0 -737
  102. data/spec/cql/io/connection_spec.rb +0 -484
  103. data/spec/cql/io/io_reactor_spec.rb +0 -402
  104. data/spec/cql/protocol/decoding_spec.rb +0 -547
  105. data/spec/cql/protocol/encoding_spec.rb +0 -386
  106. data/spec/integration/io_spec.rb +0 -283
  107. data/spec/support/fake_server.rb +0 -106
@@ -2,6 +2,76 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # An auth provider is a factory for {Cql::Client::Authenticator} instances
6
+ # (or objects matching that interface). Its {#create_authenticator} will be
7
+ # called once for each connection that requires authentication.
8
+ #
9
+ # If the authentication requires keeping state, keep that in the
10
+ # authenticator instances, not in the auth provider.
11
+ #
12
+ # @note Creating an authenticator must absolutely not block, or the whole
13
+ # connection process will block.
14
+ #
15
+ # @note Auth providers given to {Cql::Client.connect} as the `:auth_provider`
16
+ # option don't need to be subclasses of this class, but need to
17
+ # implement the same methods. This class exists only for documentation
18
+ # purposes.
19
+ class AuthProvider
20
+ # @!method create_authenticator(authentication_class, protocol_version)
21
+ #
22
+ # Create a new authenticator object. This method will be called once per
23
+ # connection that requires authentication. The auth provider can create
24
+ # different authenticators for different authentication classes, or return
25
+ # nil if it does not support the authentication class.
26
+ #
27
+ # @note This method must absolutely not block.
28
+ #
29
+ # @param authentication_class [String] the authentication class used by
30
+ # the server.
31
+ # @return [Cql::Client::Authenticator, nil] an object with an interface
32
+ # matching {Cql::Client::Authenticator} or nil if the authentication
33
+ # class is not supported.
34
+ end
35
+
36
+ # An authenticator handles the authentication challenge/response cycles of
37
+ # a single connection. It can be stateful, but it must not for any reason
38
+ # block. If any of the method calls block, the whole connection process
39
+ # will be blocked.
40
+ #
41
+ # @note Authenticators created by auth providers don't need to be subclasses
42
+ # of this class, but need to implement the same methods. This class exists
43
+ # only for documentation purposes.
44
+ class Authenticator
45
+ # @!method initial_response
46
+ #
47
+ # This method must return the initial authentication token to be sent to
48
+ # the server.
49
+ #
50
+ # @note This method must absolutely not block.
51
+ #
52
+ # @return [String] the initial authentication token
53
+
54
+ # @!method challenge_response(token)
55
+ #
56
+ # If the authentication requires multiple challenge/response cycles this
57
+ # method will be called when a challenge is returned by the server. A
58
+ # response token must be created and will be sent back to the server.
59
+ #
60
+ # @note This method must absolutely not block.
61
+ #
62
+ # @param token [String] a challenge token sent by the server
63
+ # @return [String] the authentication token to send back to the server
64
+
65
+ # @!method authentication_successful(token)
66
+ #
67
+ # Called when the authentication is successful.
68
+ #
69
+ # @note This method must absolutely not block.
70
+ #
71
+ # @param token [String] a token sent by the server
72
+ # @return [nil]
73
+ end
74
+
5
75
  # Auth provider used for Cassandra's built in authentication.
6
76
  #
7
77
  # There is no need to create instances of this class to pass as `:auth_provider`
@@ -2,6 +2,60 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ class Batch
6
+ # @!method add(cql_or_prepared_statement, *bound_values)
7
+ #
8
+ # Add a query or a prepared statement to the batch.
9
+ #
10
+ # @example Adding a mix of statements to a batch
11
+ # batch.add(%(UPDATE people SET name = 'Miriam' WHERE id = 3435))
12
+ # batch.add(%(UPDATE people SET name = ? WHERE id = ?), 'Miriam', 3435)
13
+ # batch.add(prepared_statement, 'Miriam', 3435)
14
+ #
15
+ # @param [String, Cql::Client::PreparedStatement] cql_or_prepared_statement
16
+ # a CQL string or a prepared statement object (obtained through
17
+ # {Cql::Client::Client#prepare})
18
+ # @param [Array] bound_values a list of bound values -- only applies when
19
+ # adding prepared statements and when there are binding markers in the
20
+ # given CQL. If the last argument is a hash and it has the key
21
+ # `:type_hints` this will be passed as type hints to the request encoder
22
+ # (if the last argument is any other hash it will be assumed to be a
23
+ # bound value of type MAP). See {Cql::Client::Client#execute} for more
24
+ # info on type hints.
25
+ # @return [nil]
26
+
27
+ # @!method execute(options={})
28
+ #
29
+ # Execute the batch and return the result.
30
+ #
31
+ # @param options [Hash] an options hash or a symbol (as a shortcut for
32
+ # specifying the consistency), see {Cql::Client::Client#execute} for
33
+ # full details about how this value is interpreted.
34
+ # @raise [Cql::QueryError] raised when there is an error on the server side
35
+ # @raise [Cql::NotPreparedError] raised in the unlikely event that a
36
+ # prepared statement was not prepared on the chosen connection
37
+ # @return [Cql::Client::VoidResult] a batch always returns a void result
38
+ end
39
+
40
+ class PreparedStatementBatch
41
+ # @!method add(*bound_values)
42
+ #
43
+ # Add the statement to the batch with the specified bound values.
44
+ #
45
+ # @param [Array] bound_values the values to bind to the added statement,
46
+ # see {Cql::Client::PreparedStatement#execute}.
47
+ # @return [nil]
48
+
49
+ # @!method execute(options={})
50
+ #
51
+ # Execute the batch and return the result.
52
+ #
53
+ # @raise [Cql::QueryError] raised when there is an error on the server side
54
+ # @raise [Cql::NotPreparedError] raised in the unlikely event that a
55
+ # prepared statement was not prepared on the chosen connection
56
+ # @return [Cql::Client::VoidResult] a batch always returns a void result
57
+ end
58
+
5
59
  # @private
6
60
  class AsynchronousBatch < Batch
7
61
  def initialize(type, execute_options_decoder, connection_manager, options=nil)
@@ -2,6 +2,188 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ class Client
6
+ # @!method connect
7
+ #
8
+ # Connect to all nodes. See {Cql::Client.connect} for the full
9
+ # documentation.
10
+ #
11
+ # This method needs to be called before any other. Calling it again will
12
+ # have no effect.
13
+ #
14
+ # @see Cql::Client.connect
15
+ # @return [Cql::Client]
16
+
17
+ # @!method close
18
+ #
19
+ # Disconnect from all nodes.
20
+ #
21
+ # @return [Cql::Client]
22
+
23
+ # @!method connected?
24
+ #
25
+ # Returns whether or not the client is connected.
26
+ #
27
+ # @return [true, false]
28
+
29
+ # @!method keyspace
30
+ #
31
+ # Returns the name of the current keyspace, or `nil` if no keyspace has been
32
+ # set yet.
33
+ #
34
+ # @return [String]
35
+
36
+ # @!method use(keyspace)
37
+ #
38
+ # Changes keyspace by sending a `USE` statement to all connections.
39
+ #
40
+ # The the second parameter is meant for internal use only.
41
+ #
42
+ # @param [String] keyspace
43
+ # @raise [Cql::NotConnectedError] raised when the client is not connected
44
+ # @return [nil]
45
+
46
+ # @!method execute(cql, *values, options_or_consistency={})
47
+ #
48
+ # Execute a CQL statement, optionally passing bound values.
49
+ #
50
+ # When passing bound values the request encoder will have to guess what
51
+ # types to encode the values as. For most types this will be no problem,
52
+ # but for integers and floating point numbers the larger size will be
53
+ # chosen (e.g. `BIGINT` and `DOUBLE` and not `INT` and `FLOAT`). You can
54
+ # override the guessing with the `:type_hint` option. Don't use on-the-fly
55
+ # bound values when you will issue the request multiple times, prepared
56
+ # statements are almost always a better choice.
57
+ #
58
+ # @note On-the-fly bound values are not supported in Cassandra 1.2
59
+ #
60
+ # @example A simple CQL query
61
+ # result = client.execute("SELECT * FROM users WHERE user_name = 'sue'")
62
+ # result.each do |row|
63
+ # p row
64
+ # end
65
+ #
66
+ # @example Using on-the-fly bound values
67
+ # client.execute('INSERT INTO users (user_name, full_name) VALUES (?, ?)', 'sue', 'Sue Smith')
68
+ #
69
+ # @example Using on-the-fly bound values with type hints
70
+ # client.execute('INSERT INTO users (user_name, age) VALUES (?, ?)', 'sue', 33, type_hints: [nil, :int])
71
+ #
72
+ # @example Specifying the consistency as a symbol
73
+ # client.execute("UPDATE users SET full_name = 'Sue S. Smith' WHERE user_name = 'sue'", consistency: :one)
74
+ #
75
+ # @example Specifying the consistency and other options
76
+ # client.execute("SELECT * FROM users", consistency: :all, timeout: 1.5)
77
+ #
78
+ # @example Activating tracing for a query
79
+ # result = client.execute("SELECT * FROM users", tracing: true)
80
+ # p result.trace_id
81
+ #
82
+ # @param [String] cql
83
+ # @param [Array] values Values to bind to any binding markers in the
84
+ # query (i.e. "?" placeholders) -- using this feature is similar to
85
+ # using a prepared statement, but without the type checking. The client
86
+ # needs to guess which data types to encode the values as, and will err
87
+ # on the side of caution, using types like BIGINT instead of INT for
88
+ # integers, and DOUBLE instead of FLOAT for floating point numbers. It
89
+ # is not recommended to use this feature for anything but convenience,
90
+ # and the algorithm used to guess types is to be considered experimental.
91
+ # @param [Hash] options_or_consistency Either a consistency as a symbol
92
+ # (e.g. `:quorum`), or a options hash (see below). Passing a symbol is
93
+ # equivalent to passing the options `consistency: <symbol>`.
94
+ # @option options_or_consistency [Symbol] :consistency (:quorum) The
95
+ # consistency to use for this query.
96
+ # @option options_or_consistency [Symbol] :serial_consistency (nil) The
97
+ # consistency to use for conditional updates (`:serial` or
98
+ # `:local_serial`), see the CQL documentation for the semantics of
99
+ # serial consistencies and conditional updates. The default is assumed
100
+ # to be `:serial` by the server if none is specified. Ignored for non-
101
+ # conditional queries.
102
+ # @option options_or_consistency [Integer] :timeout (nil) How long to wait
103
+ # for a response. If this timeout expires a {Cql::TimeoutError} will
104
+ # be raised.
105
+ # @option options_or_consistency [Boolean] :trace (false) Request tracing
106
+ # for this request. See {Cql::Client::QueryResult} and
107
+ # {Cql::Client::VoidResult} for how to retrieve the tracing data.
108
+ # @option options_or_consistency [Array] :type_hints (nil) When passing
109
+ # on-the-fly bound values the request encoder will have to guess what
110
+ # types to encode the values as. Using this option you can give it hints
111
+ # and avoid it guessing wrong. The hints must be an array that has the
112
+ # same number of arguments as the number of bound values, and each
113
+ # element should be the type of the corresponding value, or nil if you
114
+ # prefer the encoder to guess. The types should be provided as lower
115
+ # case symbols, e.g. `:int`, `:time_uuid`, etc.
116
+ # @raise [Cql::NotConnectedError] raised when the client is not connected
117
+ # @raise [Cql::TimeoutError] raised when a timeout was specified and no
118
+ # response was received within the timeout.
119
+ # @raise [Cql::QueryError] raised when the CQL has syntax errors or for
120
+ # other situations when the server complains.
121
+ # @return [nil, Cql::Client::QueryResult, Cql::Client::VoidResult] Some
122
+ # queries have no result and return `nil`, but `SELECT` statements
123
+ # return an `Enumerable` of rows (see {Cql::Client::QueryResult}), and
124
+ # `INSERT` and `UPDATE` return a similar type
125
+ # (see {Cql::Client::VoidResult}).
126
+
127
+ # @!method prepare(cql)
128
+ #
129
+ # Returns a prepared statement that can be run over and over again with
130
+ # different values.
131
+ #
132
+ # @see Cql::Client::PreparedStatement
133
+ # @param [String] cql The CQL to prepare
134
+ # @raise [Cql::NotConnectedError] raised when the client is not connected
135
+ # @raise [Cql::Io::IoError] raised when there is an IO error, for example
136
+ # if the server suddenly closes the connection
137
+ # @raise [Cql::QueryError] raised when there is an error on the server
138
+ # side, for example when you specify a malformed CQL query
139
+ # @return [Cql::Client::PreparedStatement] an object encapsulating the
140
+ # prepared statement
141
+
142
+ # @!method batch(type=:logged, options={})
143
+ #
144
+ # Yields a batch when called with a block. The batch is automatically
145
+ # executed at the end of the block and the result is returned.
146
+ #
147
+ # Returns a batch when called wihtout a block. The batch will remember
148
+ # the options given and merge these with any additional options given
149
+ # when {Cql::Client::Batch#execute} is called.
150
+ #
151
+ # Please note that the batch object returned by this method _is not thread
152
+ # safe_.
153
+ #
154
+ # The type parameter can be ommitted and the options can then be given
155
+ # as first parameter.
156
+ #
157
+ # @example Executing queries in a batch
158
+ # client.batch do |batch|
159
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (1234, NOW(), 23423)))
160
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2346, NOW(), 13)))
161
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2342, NOW(), 2367)))
162
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (4562, NOW(), 1231)))
163
+ # end
164
+ #
165
+ # @example Using the returned batch object
166
+ # batch = client.batch(:counter, trace: true)
167
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, 87654)
168
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 3, 6572)
169
+ # result = batch.execute(timeout: 10)
170
+ # puts result.trace_id
171
+ #
172
+ # @example Providing type hints for on-the-fly bound values
173
+ # batch = client.batch
174
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, type_hints: [:int])
175
+ # batch.execute
176
+ #
177
+ # @see Cql::Client::Batch
178
+ # @param [Symbol] type the type of batch, must be one of `:logged`,
179
+ # `:unlogged` and `:counter`. The precise meaning of these is defined
180
+ # in the CQL specification.
181
+ # @yieldparam [Cql::Client::Batch] batch the batch
182
+ # @return [Cql::Client::VoidResult, Cql::Client::Batch] when no block is
183
+ # given the batch is returned, when a block is given the result of
184
+ # executing the batch is returned (see {Cql::Client::Batch#execute}).
185
+ end
186
+
5
187
  # @private
6
188
  class AsynchronousClient < Client
7
189
  def initialize(options={})
@@ -9,7 +191,7 @@ module Cql
9
191
  @cql_version = options[:cql_version]
10
192
  @logger = options[:logger] || NullLogger.new
11
193
  @protocol_version = options[:protocol_version] || 2
12
- @io_reactor = options[:io_reactor] || Io::IoReactor.new(protocol_handler_factory)
194
+ @io_reactor = options[:io_reactor] || Io::IoReactor.new
13
195
  @hosts = extract_hosts(options)
14
196
  @initial_keyspace = options[:keyspace]
15
197
  @connections_per_node = options[:connections_per_node] || 1
@@ -131,10 +313,6 @@ module Cql
131
313
  DEFAULT_CONNECTION_TIMEOUT = 10
132
314
  MAX_RECONNECTION_ATTEMPTS = 5
133
315
 
134
- def protocol_handler_factory
135
- lambda { |connection, timeout| Protocol::CqlProtocolHandler.new(connection, timeout, @protocol_version, @compressor) }
136
- end
137
-
138
316
  def extract_hosts(options)
139
317
  if options[:hosts]
140
318
  options[:hosts].uniq
@@ -148,9 +326,10 @@ module Cql
148
326
  def create_cluster_connector
149
327
  cql_version = @cql_version || DEFAULT_CQL_VERSIONS[@protocol_version]
150
328
  authentication_step = @protocol_version == 1 ? CredentialsAuthenticationStep.new(@credentials) : SaslAuthenticationStep.new(@auth_provider)
329
+ protocol_handler_factory = lambda { |connection| Protocol::CqlProtocolHandler.new(connection, @io_reactor, @protocol_version, @compressor) }
151
330
  ClusterConnector.new(
152
331
  Connector.new([
153
- ConnectStep.new(@io_reactor, @port, @connection_timeout, @logger),
332
+ ConnectStep.new(@io_reactor, protocol_handler_factory, @port, @connection_timeout, @logger),
154
333
  CacheOptionsStep.new,
155
334
  InitializeStep.new(cql_version, @compressor, @logger),
156
335
  authentication_step,
@@ -308,5 +487,61 @@ module Cql
308
487
  end
309
488
  end
310
489
  end
490
+
491
+ # @private
492
+ class SynchronousClient < Client
493
+ include SynchronousBacktrace
494
+
495
+ def initialize(async_client)
496
+ @async_client = async_client
497
+ end
498
+
499
+ def connect
500
+ synchronous_backtrace { @async_client.connect.value }
501
+ self
502
+ end
503
+
504
+ def close
505
+ synchronous_backtrace { @async_client.close.value }
506
+ self
507
+ end
508
+
509
+ def connected?
510
+ @async_client.connected?
511
+ end
512
+
513
+ def keyspace
514
+ @async_client.keyspace
515
+ end
516
+
517
+ def use(keyspace)
518
+ synchronous_backtrace { @async_client.use(keyspace).value }
519
+ end
520
+
521
+ def execute(cql, *args)
522
+ synchronous_backtrace do
523
+ result = @async_client.execute(cql, *args).value
524
+ result = SynchronousPagedQueryResult.new(result) if result.is_a?(PagedQueryResult)
525
+ result
526
+ end
527
+ end
528
+
529
+ def prepare(cql)
530
+ async_statement = synchronous_backtrace { @async_client.prepare(cql).value }
531
+ SynchronousPreparedStatement.new(async_statement)
532
+ end
533
+
534
+ def batch(type=:logged, options={}, &block)
535
+ if block_given?
536
+ synchronous_backtrace { @async_client.batch(type, options, &block).value }
537
+ else
538
+ SynchronousBatch.new(@async_client.batch(type, options))
539
+ end
540
+ end
541
+
542
+ def async
543
+ @async_client
544
+ end
545
+ end
311
546
  end
312
547
  end
@@ -72,8 +72,9 @@ module Cql
72
72
 
73
73
  # @private
74
74
  class ConnectStep
75
- def initialize(io_reactor, port, connection_timeout, logger)
75
+ def initialize(io_reactor, protocol_handler_factory, port, connection_timeout, logger)
76
76
  @io_reactor = io_reactor
77
+ @protocol_handler_factory = protocol_handler_factory
77
78
  @port = port
78
79
  @connection_timeout = connection_timeout
79
80
  @logger = logger
@@ -81,7 +82,7 @@ module Cql
81
82
 
82
83
  def run(pending_connection)
83
84
  @logger.debug('Connecting to node at %s:%d' % [pending_connection.host, @port])
84
- @io_reactor.connect(pending_connection.host, @port, @connection_timeout).map do |connection|
85
+ @io_reactor.connect(pending_connection.host, @port, @connection_timeout, &@protocol_handler_factory).map do |connection|
85
86
  pending_connection.with_connection(connection)
86
87
  end
87
88
  end
@@ -2,6 +2,51 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ class PreparedStatement
6
+ # Metadata describing the bound values
7
+ #
8
+ # @return [ResultMetadata]
9
+ attr_reader :metadata
10
+
11
+ # Metadata about the result (i.e. rows) that is returned when executing
12
+ # this prepared statement.
13
+ #
14
+ # @return [ResultMetadata]
15
+ attr_reader :result_metadata
16
+
17
+ # Execute the prepared statement with a list of values to be bound to the
18
+ # statements parameters.
19
+ #
20
+ # The number of arguments must equal the number of bound parameters. You
21
+ # can also specify options as the last argument, or a symbol as a shortcut
22
+ # for just specifying the consistency.
23
+ #
24
+ # Because you can specify options, or not, there is an edge case where if
25
+ # the last parameter of your prepared statement is a map, and you forget
26
+ # to specify a value for your map, the options will end up being sent to
27
+ # Cassandra. Most other cases when you specify the wrong number of
28
+ # arguments should result in an `ArgumentError` or `TypeError` being
29
+ # raised.
30
+ #
31
+ # @param args [Array] the values for the bound parameters. The last
32
+ # argument can also be an options hash or a symbol (as a shortcut for
33
+ # specifying the consistency), see {Cql::Client::Client#execute} for
34
+ # full details.
35
+ # @raise [ArgumentError] raised when number of argument does not match
36
+ # the number of parameters needed to be bound to the statement.
37
+ # @raise [Cql::NotConnectedError] raised when the client is not connected
38
+ # @raise [Cql::Io::IoError] raised when there is an IO error, for example
39
+ # if the server suddenly closes the connection
40
+ # @raise [Cql::QueryError] raised when there is an error on the server side
41
+ # @return [nil, Cql::Client::QueryResult, Cql::Client::VoidResult] Some
42
+ # queries have no result and return `nil`, but `SELECT` statements
43
+ # return an `Enumerable` of rows (see {Cql::Client::QueryResult}), and
44
+ # `INSERT` and `UPDATE` return a similar type
45
+ # (see {Cql::Client::VoidResult}).
46
+ def execute(*args)
47
+ end
48
+ end
49
+
5
50
  # @private
6
51
  class AsynchronousPreparedStatement < PreparedStatement
7
52
  # @private
@@ -102,5 +147,63 @@ module Cql
102
147
  f
103
148
  end
104
149
  end
150
+
151
+ # @private
152
+ class SynchronousPreparedStatement < PreparedStatement
153
+ include SynchronousBacktrace
154
+
155
+ def initialize(async_statement)
156
+ @async_statement = async_statement
157
+ @metadata = async_statement.metadata
158
+ @result_metadata = async_statement.result_metadata
159
+ end
160
+
161
+ def execute(*args)
162
+ synchronous_backtrace do
163
+ result = @async_statement.execute(*args).value
164
+ result = SynchronousPagedQueryResult.new(result) if result.is_a?(PagedQueryResult)
165
+ result
166
+ end
167
+ end
168
+
169
+ def batch(type=:logged, options=nil, &block)
170
+ if block_given?
171
+ synchronous_backtrace { @async_statement.batch(type, options, &block).value }
172
+ else
173
+ SynchronousPreparedStatementBatch.new(@async_statement.batch(type, options))
174
+ end
175
+ end
176
+
177
+ def pipeline
178
+ pl = Pipeline.new(@async_statement)
179
+ yield pl
180
+ synchronous_backtrace { pl.value }
181
+ end
182
+
183
+ def async
184
+ @async_statement
185
+ end
186
+
187
+ # @private
188
+ def add_to_batch(batch, connection, bound_arguments)
189
+ @async_statement.add_to_batch(batch, connection, bound_arguments)
190
+ end
191
+ end
192
+
193
+ # @private
194
+ class Pipeline
195
+ def initialize(async_statement)
196
+ @async_statement = async_statement
197
+ @futures = []
198
+ end
199
+
200
+ def execute(*args)
201
+ @futures << @async_statement.execute(*args)
202
+ end
203
+
204
+ def value
205
+ Future.all(*@futures).value
206
+ end
207
+ end
105
208
  end
106
209
  end