ilios 1.1.2 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c20c800bf59b7673cdb60ce4dd5086412e3461f4fa75f8f729eef7b2bba3ade
4
- data.tar.gz: 1edb52689033a9047388c4be27f81ac3c1078fefcd845bc6f03c8d0080983670
3
+ metadata.gz: dc846b97514ac6b86eccf9a06eee87d35396f26746974e6927d7d99f0bcf33ed
4
+ data.tar.gz: cd91086e384b4af2fc3a39cb0c5234722947dbb8dc097782a4508df9bf73615f
5
5
  SHA512:
6
- metadata.gz: 0724d14f13175a62b25aeda0926ea4f9bb6593f64c75cf871bf55426594ec2cf1b4d76fcd61a86a85c686cec49902e37b0f9302bce9933467c688d55dad9c78d
7
- data.tar.gz: 7c3cd165e571bbdccbf627a2bb50aadfa84a02188fed7ede80a6bb41d7bfb7b75d43c0b31ab92dcf9de882d3dbfd7a5170663708e72023c9c640da9b3bedf14d
6
+ metadata.gz: bf94440639db92113db0a8c06a05f2409139644268988aa7c2c94f202bab494d035fd6107596010f07222feecbe92cad8c3f14ed169061d16d91fe69db8f05f9
7
+ data.tar.gz: 2b514895a3a71380e5fab10198a92e1dc1a9cdea7d84d10dad344658f14efbc7884c8fa49e3c80bcd9a8cb7942f0cb1d846a7e92cfca9ae02ff8b785989c6db5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.2.0
4
+
5
+ - Fix cluster setters silently ignoring invalid values in `Cluster#port`, `#protocol_version`, `#connect_timeout`, `#request_timeout` and `#resolve_timeout` (#36)
6
+ - Fix segfault when a `Cluster` created by `allocate` or `dup` is used without running `initialize` (#37)
7
+ - Yield the failure reason as an `Ilios::Cassandra::ExecutionError` to `Future#on_failure` blocks that accept an argument, including variadic blocks (e.g. `{ |*args| }`); zero-arity blocks are unchanged (#40)
8
+ - The synchronous API (`Session#prepare`, `Session#execute`, `Result#next_page`, `Cluster#connect`) now raises errors carrying the server-reported message and a `#code` Integer, same as `Future#on_failure` (#40)
9
+ - `Ilios::Cassandra::StatementError` also carries a `#code` Integer, so `#code` is answered by every error class the driver raises (#40)
10
+ - Fix `Future#on_success` RBS signature to allow the `Statement` yielded by `prepare_async` (#42)
11
+
3
12
  ## 1.1.2
4
13
 
5
14
  - Support username/password authentication with `Cluster#credentials` (#33)
data/README.md CHANGED
@@ -226,8 +226,12 @@ prepare_future.on_success { |statement|
226
226
  p result
227
227
  p "success"
228
228
  }
229
- result_future.on_failure {
230
- p "fail"
229
+ # `error` is an `Ilios::Cassandra::ExecutionError` (a StandardError with
230
+ # a #code Integer attribute); the block is optional-arity, so `{ p "fail" }`
231
+ # still works without it.
232
+ result_future.on_failure { |error|
233
+ p error.message
234
+ p error.code
231
235
  }
232
236
 
233
237
  futures << result_future
data/ext/ilios/cluster.c CHANGED
@@ -23,6 +23,36 @@ static VALUE cluster_allocator(VALUE klass)
23
23
  return CREATE_CLUSTER(cassandra_cluster);
24
24
  }
25
25
 
26
+ static void cluster_check_error(CassError error, const char *name)
27
+ {
28
+ if (error != CASS_OK) {
29
+ rb_raise(rb_eArgError, "Invalid %s: %s", name, cass_error_desc(error));
30
+ }
31
+ }
32
+
33
+ // Casting a negative value to unsigned wraps it into a huge number, so check
34
+ // the range on the signed value NUM2LONG/NUM2LL already produced. Converting a
35
+ // second time would run to_int again, which may return a different value.
36
+ static unsigned cluster_value_to_uint(VALUE value, const char *name)
37
+ {
38
+ long v = NUM2LONG(value);
39
+
40
+ if (v < 0 || v > UINT_MAX) {
41
+ rb_raise(rb_eRangeError, "Invalid %s: %ld", name, v);
42
+ }
43
+ return (unsigned)v;
44
+ }
45
+
46
+ static cass_uint64_t cluster_value_to_uint64(VALUE value, const char *name)
47
+ {
48
+ long long v = NUM2LL(value);
49
+
50
+ if (v < 0) {
51
+ rb_raise(rb_eRangeError, "Invalid %s: %lld", name, v);
52
+ }
53
+ return (cass_uint64_t)v;
54
+ }
55
+
26
56
  /**
27
57
  * Creates a new cluster.
28
58
  *
@@ -32,7 +62,7 @@ static VALUE cluster_initialize(VALUE self)
32
62
  {
33
63
  CassandraCluster *cassandra_cluster;
34
64
 
35
- GET_CLUSTER(self, cassandra_cluster);
65
+ GET_UNINITIALIZED_CLUSTER(self, cassandra_cluster);
36
66
  cassandra_cluster->cluster = cass_cluster_new();
37
67
 
38
68
  return self;
@@ -65,12 +95,10 @@ static VALUE cluster_connect(VALUE self)
65
95
  nogvl_future_wait(connect_future);
66
96
 
67
97
  if (cass_future_error_code(connect_future) != CASS_OK) {
68
- char error[4096] = { 0 };
98
+ VALUE error = ilios_future_error_new(eConnectError, "Unable to connect", connect_future);
69
99
 
70
- strncpy(error, cass_error_desc(cass_future_error_code(connect_future)), sizeof(error) - 1);
71
100
  cass_future_free(connect_future);
72
- rb_raise(eConnectError, "Unable to connect: %s", error);
73
- return Qnil;
101
+ rb_exc_raise(error);
74
102
  }
75
103
  cass_future_free(connect_future);
76
104
 
@@ -110,13 +138,14 @@ static VALUE cluster_hosts(VALUE self, VALUE hosts)
110
138
  *
111
139
  * @param port [Integer] A port number.
112
140
  * @return [Cassandra::Cluster] self.
141
+ * @raise [ArgumentError] If the port is zero or negative.
113
142
  */
114
143
  static VALUE cluster_port(VALUE self, VALUE port)
115
144
  {
116
145
  CassandraCluster *cassandra_cluster;
117
146
 
118
147
  GET_CLUSTER(self, cassandra_cluster);
119
- cass_cluster_set_port(cassandra_cluster->cluster, NUM2INT(port));
148
+ cluster_check_error(cass_cluster_set_port(cassandra_cluster->cluster, NUM2INT(port)), "port");
120
149
 
121
150
  return self;
122
151
  }
@@ -141,17 +170,30 @@ static VALUE cluster_keyspace(VALUE self, VALUE keyspace)
141
170
 
142
171
  /**
143
172
  * Sets the protocol version. The driver will automatically downgrade to the lowest supported protocol version.
144
- * Default is +PROTOCOL_VERSION_V4+.
173
+ * Default is +PROTOCOL_VERSION_V4+, or +PROTOCOL_VERSION_DSEV1+ against DSE.
174
+ *
175
+ * The driver accepts +PROTOCOL_VERSION_V3+, +PROTOCOL_VERSION_V4+,
176
+ * +PROTOCOL_VERSION_DSEV1+ and +PROTOCOL_VERSION_DSEV2+ only. It ignores
177
+ * +PROTOCOL_VERSION_V1+ and +PROTOCOL_VERSION_V2+, which are below the lowest
178
+ * version it supports, and +PROTOCOL_VERSION_V5+, which is a beta version it
179
+ * enables through a separate setting instead. Each of those leaves the version
180
+ * unchanged and is reported on the driver's error log.
145
181
  *
146
182
  * @param version [Integer] A protocol version.
147
183
  * @return [Cassandra::Cluster] self.
184
+ * @raise [RangeError] If a negative value was given.
148
185
  */
149
186
  static VALUE cluster_protocol_version(VALUE self, VALUE version)
150
187
  {
151
188
  CassandraCluster *cassandra_cluster;
189
+ int v = NUM2INT(version);
190
+
191
+ if (v < 0) {
192
+ rb_raise(rb_eRangeError, "Invalid protocol_version: %d", v);
193
+ }
152
194
 
153
195
  GET_CLUSTER(self, cassandra_cluster);
154
- cass_cluster_set_protocol_version(cassandra_cluster->cluster, NUM2INT(version));
196
+ cass_cluster_set_protocol_version(cassandra_cluster->cluster, v);
155
197
 
156
198
  return self;
157
199
  }
@@ -162,13 +204,14 @@ static VALUE cluster_protocol_version(VALUE self, VALUE version)
162
204
  *
163
205
  * @param timeout_ms [Integer] A connect timeout in milliseconds.
164
206
  * @return [Cassandra::Cluster] self.
207
+ * @raise [RangeError] If a negative value was given.
165
208
  */
166
209
  static VALUE cluster_connect_timeout(VALUE self, VALUE timeout_ms)
167
210
  {
168
211
  CassandraCluster *cassandra_cluster;
169
212
 
170
213
  GET_CLUSTER(self, cassandra_cluster);
171
- cass_cluster_set_connect_timeout(cassandra_cluster->cluster, NUM2UINT(timeout_ms));
214
+ cass_cluster_set_connect_timeout(cassandra_cluster->cluster, cluster_value_to_uint(timeout_ms, "connect_timeout"));
172
215
 
173
216
  return self;
174
217
  }
@@ -179,13 +222,14 @@ static VALUE cluster_connect_timeout(VALUE self, VALUE timeout_ms)
179
222
  *
180
223
  * @param timeout_ms [Integer] A request timeout in milliseconds.
181
224
  * @return [Cassandra::Cluster] self.
225
+ * @raise [RangeError] If a negative value was given.
182
226
  */
183
227
  static VALUE cluster_request_timeout(VALUE self, VALUE timeout_ms)
184
228
  {
185
229
  CassandraCluster *cassandra_cluster;
186
230
 
187
231
  GET_CLUSTER(self, cassandra_cluster);
188
- cass_cluster_set_request_timeout(cassandra_cluster->cluster, NUM2UINT(timeout_ms));
232
+ cass_cluster_set_request_timeout(cassandra_cluster->cluster, cluster_value_to_uint(timeout_ms, "request_timeout"));
189
233
 
190
234
  return self;
191
235
  }
@@ -196,13 +240,14 @@ static VALUE cluster_request_timeout(VALUE self, VALUE timeout_ms)
196
240
  *
197
241
  * @param timeout_ms [Integer] A request timeout in milliseconds.
198
242
  * @return [Cassandra::Cluster] self.
243
+ * @raise [RangeError] If a negative value was given.
199
244
  */
200
245
  static VALUE cluster_resolve_timeout(VALUE self, VALUE timeout_ms)
201
246
  {
202
247
  CassandraCluster *cassandra_cluster;
203
248
 
204
249
  GET_CLUSTER(self, cassandra_cluster);
205
- cass_cluster_set_resolve_timeout(cassandra_cluster->cluster, NUM2UINT(timeout_ms));
250
+ cass_cluster_set_resolve_timeout(cassandra_cluster->cluster, cluster_value_to_uint(timeout_ms, "resolve_timeout"));
206
251
 
207
252
  return self;
208
253
  }
@@ -217,13 +262,15 @@ static VALUE cluster_resolve_timeout(VALUE self, VALUE timeout_ms)
217
262
  static VALUE cluster_constant_speculative_execution_policy(VALUE self, VALUE constant_delay_ms, VALUE max_speculative_executions)
218
263
  {
219
264
  CassandraCluster *cassandra_cluster;
265
+ long delay = NUM2LONG(constant_delay_ms);
266
+ int max_executions = NUM2INT(max_speculative_executions);
220
267
 
221
- if (NUM2LONG(constant_delay_ms) < 0 || NUM2INT(max_speculative_executions) < 0) {
268
+ if (delay < 0 || max_executions < 0) {
222
269
  rb_raise(rb_eArgError, "Bad parameters.");
223
270
  }
224
271
 
225
272
  GET_CLUSTER(self, cassandra_cluster);
226
- cass_cluster_set_constant_speculative_execution_policy(cassandra_cluster->cluster, NUM2LONG(constant_delay_ms), NUM2INT(max_speculative_executions));
273
+ cluster_check_error(cass_cluster_set_constant_speculative_execution_policy(cassandra_cluster->cluster, delay, max_executions), "constant_speculative_execution_policy");
227
274
 
228
275
  return self;
229
276
  }
@@ -248,39 +295,10 @@ static VALUE cluster_credentials(VALUE self, VALUE username, VALUE password)
248
295
  return self;
249
296
  }
250
297
 
251
- static void cluster_check_error(CassError error, const char *name)
252
- {
253
- if (error != CASS_OK) {
254
- rb_raise(rb_eArgError, "Invalid %s: %s", name, cass_error_desc(error));
255
- }
256
- }
257
-
258
- // Casting a negative value to unsigned wraps it into a huge number, so check
259
- // the range on the signed value NUM2LONG/NUM2LL already produced. Converting a
260
- // second time would run to_int again, which may return a different value.
261
- static unsigned cluster_value_to_uint(VALUE value, const char *name)
262
- {
263
- long v = NUM2LONG(value);
264
-
265
- if (v < 0 || v > UINT_MAX) {
266
- rb_raise(rb_eRangeError, "Invalid %s: %ld", name, v);
267
- }
268
- return (unsigned)v;
269
- }
270
-
271
- static cass_uint64_t cluster_value_to_uint64(VALUE value, const char *name)
272
- {
273
- long long v = NUM2LL(value);
274
-
275
- if (v < 0) {
276
- rb_raise(rb_eRangeError, "Invalid %s: %lld", name, v);
277
- }
278
- return (cass_uint64_t)v;
279
- }
280
-
281
- // The driver only rejects CASS_CONSISTENCY_UNKNOWN at set time; any other
282
- // out-of-range value is accepted and later fails every query with an
283
- // opaque protocol error, so validate the range here.
298
+ // The driver only rejects CASS_CONSISTENCY_UNKNOWN (0xFFFF) at set time; any
299
+ // other out-of-range value is accepted and later fails every query with an
300
+ // opaque protocol error, so validate the range here. This also keeps UNKNOWN
301
+ // away from the driver, which is why the setters ignore its return value.
284
302
  static void cluster_check_consistency(long value, const char *name)
285
303
  {
286
304
  if (value < CASS_CONSISTENCY_ANY || value > CASS_CONSISTENCY_LOCAL_ONE) {
@@ -347,7 +365,7 @@ static VALUE cluster_consistency(VALUE self, VALUE consistency)
347
365
  CassConsistency consistency_value = cluster_value_to_consistency(consistency, "consistency");
348
366
 
349
367
  GET_CLUSTER(self, cassandra_cluster);
350
- cluster_check_error(cass_cluster_set_consistency(cassandra_cluster->cluster, consistency_value), "consistency");
368
+ cass_cluster_set_consistency(cassandra_cluster->cluster, consistency_value);
351
369
 
352
370
  return self;
353
371
  }
@@ -378,7 +396,7 @@ static VALUE cluster_serial_consistency(VALUE self, VALUE consistency)
378
396
  }
379
397
 
380
398
  GET_CLUSTER(self, cassandra_cluster);
381
- cluster_check_error(cass_cluster_set_serial_consistency(cassandra_cluster->cluster, consistency_value), "serial_consistency");
399
+ cass_cluster_set_serial_consistency(cassandra_cluster->cluster, consistency_value);
382
400
 
383
401
  return self;
384
402
  }
data/ext/ilios/future.c CHANGED
@@ -136,7 +136,13 @@ static void future_result_success_yield(CassandraFuture *cassandra_future)
136
136
  static void future_result_failure_yield(CassandraFuture *cassandra_future)
137
137
  {
138
138
  if (cassandra_future->on_failure_block) {
139
- rb_proc_call_with_block(cassandra_future->on_failure_block, 0, NULL, Qnil);
139
+ if (rb_proc_arity(cassandra_future->on_failure_block)) {
140
+ VALUE error = ilios_future_error_new(eExecutionError, NULL, cassandra_future->future);
141
+
142
+ rb_proc_call_with_block(cassandra_future->on_failure_block, 1, &error, Qnil);
143
+ } else {
144
+ rb_proc_call_with_block(cassandra_future->on_failure_block, 0, NULL, Qnil);
145
+ }
140
146
  }
141
147
  }
142
148
 
@@ -317,6 +323,9 @@ static VALUE future_on_failure_synchronize(VALUE future)
317
323
  /**
318
324
  * Run block when future resolves to error.
319
325
  *
326
+ * @yieldparam error [Cassandra::ExecutionError] The failure reason. Only
327
+ * yielded when the block accepts an argument; a zero-arity block is still
328
+ * called with no arguments.
320
329
  * @return [Cassandra::Future] self.
321
330
  * @raise [Cassandra::ExecutionError] If this method will be called twice.
322
331
  * @raise [ArgumentError] If no block was given.
data/ext/ilios/ilios.c CHANGED
@@ -1,5 +1,7 @@
1
1
  #include "ilios.h"
2
2
 
3
+ #include <string.h>
4
+
3
5
  VALUE mIlios;
4
6
  VALUE mCassandra;
5
7
  VALUE cCluster;
@@ -21,6 +23,7 @@ VALUE id_push;
21
23
  VALUE id_pop;
22
24
  VALUE id_alive;
23
25
  VALUE id_report_on_exception;
26
+ VALUE id_code;
24
27
  VALUE sym_unsupported_column_type;
25
28
 
26
29
  #if defined(HAVE_MALLOC_USABLE_SIZE)
@@ -74,6 +77,45 @@ static VALUE cassandra_set_log_level(VALUE self, VALUE log_level)
74
77
  return self;
75
78
  }
76
79
 
80
+ VALUE ilios_error_new(VALUE exception_class, VALUE message, CassError error_code)
81
+ {
82
+ VALUE error = rb_exc_new_str(exception_class, message);
83
+
84
+ rb_ivar_set(error, id_code, INT2NUM(error_code));
85
+
86
+ return error;
87
+ }
88
+
89
+ VALUE ilios_future_error_new(VALUE exception_class, const char *prefix, CassFuture *future)
90
+ {
91
+ CassError error_code;
92
+ const char *message;
93
+ size_t message_length;
94
+ VALUE body;
95
+ VALUE full_message;
96
+
97
+ error_code = cass_future_error_code(future);
98
+ cass_future_error_message(future, &message, &message_length);
99
+ if (message_length == 0) {
100
+ message = cass_error_desc(error_code);
101
+ message_length = strlen(message);
102
+ }
103
+
104
+ // The driver hands back the server's raw bytes, which rb_str_new would
105
+ // tag ASCII-8BIT; CQL identifiers and literals are UTF-8, so tag it
106
+ // UTF-8 and keep the prefixed message in that encoding too. The message
107
+ // can also carry driver-local text (strerror output, host names), which
108
+ // is not guaranteed UTF-8, so replace invalid bytes rather than handing
109
+ // back a string that raises ArgumentError on the first regexp match.
110
+ body = rb_utf8_str_new(message, message_length);
111
+ if (rb_enc_str_coderange(body) == ENC_CODERANGE_BROKEN) {
112
+ body = rb_str_scrub(body, Qnil);
113
+ }
114
+ full_message = prefix ? rb_enc_sprintf(rb_utf8_encoding(), "%s: %"PRIsVALUE, prefix, body) : body;
115
+
116
+ return ilios_error_new(exception_class, full_message, error_code);
117
+ }
118
+
77
119
  void Init_ilios(void)
78
120
  {
79
121
  rb_ext_ractor_safe(true);
@@ -88,6 +130,11 @@ void Init_ilios(void)
88
130
  eConnectError = rb_define_class_under(mCassandra, "ConnectError", rb_eStandardError);
89
131
  eExecutionError = rb_define_class_under(mCassandra, "ExecutionError", rb_eStandardError);
90
132
  eStatementError = rb_define_class_under(mCassandra, "StatementError", rb_eStandardError);
133
+ // @code is only set on errors built by ilios_error_new; the ones raised
134
+ // elsewhere via plain rb_raise leave #code nil.
135
+ rb_define_attr(eExecutionError, "code", 1, 0);
136
+ rb_define_attr(eConnectError, "code", 1, 0);
137
+ rb_define_attr(eStatementError, "code", 1, 0);
91
138
 
92
139
  cSizedQueue = rb_const_get(rb_cThread, rb_intern("SizedQueue"));
93
140
  rb_require("set");
@@ -101,6 +148,7 @@ void Init_ilios(void)
101
148
  id_pop = rb_intern("pop");
102
149
  id_alive = rb_intern("alive?");
103
150
  id_report_on_exception = rb_intern("report_on_exception=");
151
+ id_code = rb_intern("@code");
104
152
  sym_unsupported_column_type = ID2SYM(rb_intern("unsupported_column_type"));
105
153
 
106
154
  rb_define_module_function(mCassandra, "log_level", cassandra_set_log_level, 1);
data/ext/ilios/ilios.h CHANGED
@@ -11,7 +11,16 @@
11
11
 
12
12
  #define DEFAULT_PAGE_SIZE 10000
13
13
 
14
- #define GET_CLUSTER(obj, var) TypedData_Get_Struct(obj, CassandraCluster, &cassandra_cluster_data_type, var)
14
+ // Cluster exposes an allocator, so allocate and dup both produce an instance
15
+ // that never ran initialize. Reject it here instead of handing NULL to the
16
+ // driver, which segfaults.
17
+ #define GET_CLUSTER(obj, var) do { \
18
+ GET_UNINITIALIZED_CLUSTER(obj, var); \
19
+ if ((var)->cluster == NULL) { \
20
+ rb_raise(rb_eRuntimeError, "uninitialized Ilios::Cassandra::Cluster"); \
21
+ } \
22
+ } while (0)
23
+ #define GET_UNINITIALIZED_CLUSTER(obj, var) TypedData_Get_Struct(obj, CassandraCluster, &cassandra_cluster_data_type, var)
15
24
  #define GET_SESSION(obj, var) TypedData_Get_Struct(obj, CassandraSession, &cassandra_session_data_type, var)
16
25
  #define GET_STATEMENT(obj, var) TypedData_Get_Struct(obj, CassandraStatement, &cassandra_statement_data_type, var)
17
26
  #define GET_RESULT(obj, var) TypedData_Get_Struct(obj, CassandraResult, &cassandra_result_data_type, var)
@@ -118,6 +127,7 @@ extern VALUE id_push;
118
127
  extern VALUE id_pop;
119
128
  extern VALUE id_alive;
120
129
  extern VALUE id_report_on_exception;
130
+ extern VALUE id_code;
121
131
  extern VALUE sym_unsupported_column_type;
122
132
 
123
133
  extern void Init_cluster(void);
@@ -136,5 +146,17 @@ extern void statement_default_config(CassandraStatement *cassandra_statement);
136
146
  extern CassStatement *statement_build_for_execution(CassandraStatement *cassandra_statement);
137
147
  extern void result_await(CassandraResult *cassandra_result);
138
148
 
149
+ // Builds an exception of `exception_class` carrying `message` and `@code`
150
+ // (readable via #code) set to the CassError value.
151
+ extern VALUE ilios_error_new(VALUE exception_class, VALUE message, CassError error_code);
152
+
153
+ // Builds an exception of `exception_class` from `future`'s error: the
154
+ // driver's server-reported message (falling back to the generic
155
+ // cass_error_desc when the driver returns none), formatted as
156
+ // "<prefix>: <message>" when `prefix` is non-NULL, or the bare message when
157
+ // `prefix` is NULL. `future` must still be alive (not yet cass_future_free'd)
158
+ // when this is called.
159
+ extern VALUE ilios_future_error_new(VALUE exception_class, const char *prefix, CassFuture *future);
160
+
139
161
 
140
162
  #endif // ILIOS_H
data/ext/ilios/result.c CHANGED
@@ -22,10 +22,9 @@ void result_await(CassandraResult *cassandra_result)
22
22
  nogvl_future_wait(cassandra_result->future);
23
23
 
24
24
  if (cass_future_error_code(cassandra_result->future) != CASS_OK) {
25
- char error[4096] = { 0 };
25
+ VALUE error = ilios_future_error_new(eExecutionError, "Unable to wait executing", cassandra_result->future);
26
26
 
27
- strncpy(error, cass_error_desc(cass_future_error_code(cassandra_result->future)), sizeof(error) - 1);
28
- rb_raise(eExecutionError, "Unable to wait executing: %s", error);
27
+ rb_exc_raise(error);
29
28
  }
30
29
 
31
30
  if (cassandra_result->result == NULL) {
@@ -66,8 +65,10 @@ static VALUE result_next_page(VALUE self)
66
65
 
67
66
  error_code = cass_future_error_code(result_future);
68
67
  if (error_code != CASS_OK) {
68
+ VALUE error = ilios_future_error_new(eExecutionError, "Unable to wait executing", result_future);
69
+
69
70
  cass_future_free(result_future);
70
- rb_raise(eExecutionError, "Unable to wait executing: %s", cass_error_desc(error_code));
71
+ rb_exc_raise(error);
71
72
  }
72
73
 
73
74
  cass_result_free(cassandra_result->result);
@@ -80,10 +81,20 @@ static VALUE result_next_page(VALUE self)
80
81
  return self;
81
82
  }
82
83
 
84
+ // No CassFuture is involved here: this fires while decoding an already
85
+ // fetched row's column value.
86
+ static VALUE result_check_value_error_new(CassError error_code, VALUE key)
87
+ {
88
+ VALUE message = rb_enc_sprintf(rb_utf8_encoding(), "Unable to get value of %"PRIsVALUE" column: %s",
89
+ key, cass_error_desc(error_code));
90
+
91
+ return ilios_error_new(eExecutionError, message, error_code);
92
+ }
93
+
83
94
  static void result_check_value(CassError error_code, VALUE key)
84
95
  {
85
96
  if (error_code != CASS_OK) {
86
- rb_raise(eExecutionError, "Unable to get value of %"PRIsVALUE" column: %s", key, cass_error_desc(error_code));
97
+ rb_exc_raise(result_check_value_error_new(error_code, key));
87
98
  }
88
99
  }
89
100
 
data/ext/ilios/session.c CHANGED
@@ -56,11 +56,10 @@ static VALUE session_prepare(VALUE self, VALUE query)
56
56
  nogvl_future_wait(prepare_future);
57
57
 
58
58
  if (cass_future_error_code(prepare_future) != CASS_OK) {
59
- char error[4096] = { 0 };
59
+ VALUE error = ilios_future_error_new(eExecutionError, "Unable to prepare query", prepare_future);
60
60
 
61
- strncpy(error, cass_error_desc(cass_future_error_code(prepare_future)), sizeof(error) - 1);
62
61
  cass_future_free(prepare_future);
63
- rb_raise(eExecutionError, "Unable to prepare query: %s", error);
62
+ rb_exc_raise(error);
64
63
  }
65
64
 
66
65
  cassandra_statement_obj = CREATE_STATEMENT(cassandra_statement);
@@ -141,7 +141,10 @@ static void statement_bind_collection(statement_bind_target *target, const CassD
141
141
  static void statement_bind_check(CassError result, VALUE key)
142
142
  {
143
143
  if (result != CASS_OK) {
144
- rb_raise(eStatementError, "Failed to bind value of %"PRIsVALUE" column: %s", key, cass_error_desc(result));
144
+ VALUE message = rb_enc_sprintf(rb_utf8_encoding(), "Failed to bind value of %"PRIsVALUE" column: %s",
145
+ key, cass_error_desc(result));
146
+
147
+ rb_exc_raise(ilios_error_new(eStatementError, message, result));
145
148
  }
146
149
  }
147
150
 
data/lib/ilios/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ilios
4
- VERSION = '1.1.2'
4
+ VERSION = '1.2.0'
5
5
  public_constant :VERSION
6
6
 
7
7
  CASSANDRA_CPP_DRIVER_VERSION = '2.17.1'
data/sig/ilios.rbs CHANGED
@@ -75,9 +75,21 @@ module Ilios
75
75
  def idempotent=: (bool) -> self
76
76
  end
77
77
 
78
+ class ExecutionError < StandardError
79
+ def code: () -> Integer?
80
+ end
81
+
82
+ class ConnectError < StandardError
83
+ def code: () -> Integer?
84
+ end
85
+
86
+ class StatementError < StandardError
87
+ def code: () -> Integer?
88
+ end
89
+
78
90
  class Future
79
- def on_success: () { (Ilios::Cassandra::Result) -> void } -> self
80
- def on_failure: () { () -> void } -> self
91
+ def on_success: () { (Ilios::Cassandra::Result | Ilios::Cassandra::Statement) -> void } -> self
92
+ def on_failure: () { (Ilios::Cassandra::ExecutionError) -> void } -> self
81
93
  def await: () -> self
82
94
  end
83
95
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ilios
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Watson
@@ -58,7 +58,7 @@ metadata:
58
58
  homepage_uri: https://github.com/Watson1978/ilios
59
59
  source_code_uri: https://github.com/Watson1978/ilios
60
60
  bug_tracker_uri: https://github.com/Watson1978/ilios/issues
61
- documentation_uri: https://www.rubydoc.info/gems/ilios/1.1.2
61
+ documentation_uri: https://www.rubydoc.info/gems/ilios/1.2.0
62
62
  rubygems_mfa_required: 'true'
63
63
  rdoc_options: []
64
64
  require_paths: