cql-rb 2.0.0.pre0 → 2.0.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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