ilios 1.0.4 → 1.0.6

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: a4f2c920b53b519c07299b86d8c31c290d858609dfda93dcdec8b1e0b1dcd687
4
- data.tar.gz: c6070d007f1bdef3ba96dd5a2b4620e174f42c5cce6d662452be21fb5d54693d
3
+ metadata.gz: ae1db76d702cb217eb8145f498dd86044183e441cbd896ac6acfd0d118823d13
4
+ data.tar.gz: 5e5e141d2578bdfd5c2b39b8b7e5b72e5a65de824a4e5868c3388e3308973d8c
5
5
  SHA512:
6
- metadata.gz: d2a5d977c1ed2c756534697590f8affe5a93ded29a53225ff1f2b0ae41bce7ce9901a1310f5540fef037de53e3b3aff9fa5c073635d983f528558200bf38e6a7
7
- data.tar.gz: 8456a5c214c9a61bc0fcb5fd0558ea3e24a1345c3e77e8c3f94e4d85f2f7497b74bb1f1a5e755181521005d0102c52db92a3af0aa610be72b87296a32edb224f
6
+ metadata.gz: 2c78135d42da8f1c532c80531d0a493fed7f4bcf96ddf2d8aabdafac649740cb819a6f038640031993fe33fdc82c245e9725868f71149751aaaadf9ca3710d0b
7
+ data.tar.gz: 26771b46cf175596fdd14a19873783c151f3a1b02798ca43fa39b7828b8519a3316bbcafbc93a291d9c44cadb95d562c1e7fc6d30b19e48e2efb28592c72c674
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.0.6
4
+
5
+ - Fix uninitialized memory being returned for an undecodable column value (#27)
6
+ - Fix segmentation fault when a Result is used after Result#next_page fails (#26)
7
+ - Fix uninitialized memory being bound for a malformed UUID string (#25)
8
+
9
+ ## 1.0.5
10
+
11
+ - Fix use-after-free when re-binding a statement with in-flight async executions (#24)
12
+
3
13
  ## 1.0.4
4
14
 
5
15
  - Fix macOS build failure with Apple Clang on macOS 26+ (#23)
data/ext/ilios/future.c CHANGED
@@ -116,6 +116,10 @@ static void future_result_success_yield(CassandraFuture *cassandra_future)
116
116
  cassandra_result_obj = CREATE_RESULT(cassandra_result);
117
117
  cassandra_result->result = cass_future_get_result(cassandra_future->future);
118
118
  cassandra_result->statement_obj = cassandra_future->statement_obj;
119
+ // Hand over the executed CassStatement so Result#next_page
120
+ // can reuse it and it gets freed exactly once.
121
+ cassandra_result->executed_statement = cassandra_future->executed_statement;
122
+ cassandra_future->executed_statement = NULL;
119
123
 
120
124
  obj = cassandra_result_obj;
121
125
  }
@@ -202,6 +206,7 @@ VALUE future_create(CassFuture *future, VALUE session, VALUE statement, future_k
202
206
  cassandra_future_obj = CREATE_FUTURE(cassandra_future);
203
207
  cassandra_future->kind = kind;
204
208
  cassandra_future->future = future;
209
+ cassandra_future->executed_statement = NULL;
205
210
  cassandra_future->session_obj = session;
206
211
  cassandra_future->statement_obj = statement;
207
212
  cassandra_future->proc_mutex = rb_mutex_new();
@@ -366,6 +371,11 @@ static void future_destroy(void *ptr)
366
371
  if (cassandra_future->future) {
367
372
  cass_future_free(cassandra_future->future);
368
373
  }
374
+ if (cassandra_future->executed_statement) {
375
+ // Safe even if the request is still in flight: the driver's request
376
+ // holds its own reference to the statement internals.
377
+ cass_statement_free(cassandra_future->executed_statement);
378
+ }
369
379
  uv_sem_destroy(&cassandra_future->sem);
370
380
  xfree(cassandra_future);
371
381
  }
data/ext/ilios/ilios.h CHANGED
@@ -27,6 +27,12 @@ typedef enum {
27
27
  execute_async
28
28
  } future_kind;
29
29
 
30
+ typedef enum {
31
+ idempotency_unset,
32
+ idempotency_false,
33
+ idempotency_true
34
+ } statement_idempotency;
35
+
30
36
  typedef struct
31
37
  {
32
38
  CassCluster* cluster;
@@ -40,21 +46,36 @@ typedef struct
40
46
 
41
47
  typedef struct
42
48
  {
49
+ // Scratch statement used for validating values in Statement#bind.
50
+ // It is never handed to the driver: each execution gets its own
51
+ // CassStatement built from `prepared` and `bound_values`, because the
52
+ // driver encodes values asynchronously on its IO thread and re-binding
53
+ // an in-flight statement is a use-after-free (issue #12).
43
54
  CassStatement* statement;
44
55
  const CassPrepared* prepared;
45
56
  VALUE session_obj;
57
+ VALUE bound_values;
58
+ int page_size;
59
+ statement_idempotency idempotent;
46
60
  } CassandraStatement;
47
61
 
48
62
  typedef struct
49
63
  {
50
64
  const CassResult *result;
51
65
  CassFuture *future;
66
+ // The CassStatement this result was executed with (owned, freed on destroy).
67
+ // Not to be confused with statement_obj, the Ruby Statement object.
68
+ CassStatement *executed_statement;
52
69
  VALUE statement_obj;
53
70
  } CassandraResult;
54
71
 
55
72
  typedef struct
56
73
  {
57
74
  CassFuture *future;
75
+ // The CassStatement this future's execution was submitted with (owned,
76
+ // freed on destroy unless handed over to the result). Not to be confused
77
+ // with statement_obj, the Ruby Statement object.
78
+ CassStatement *executed_statement;
58
79
  future_kind kind;
59
80
 
60
81
  VALUE session_obj;
@@ -108,6 +129,7 @@ extern CassFuture *nogvl_session_execute(CassSession* session, CassStatement* st
108
129
  extern void nogvl_sem_wait(uv_sem_t *sem);
109
130
 
110
131
  extern void statement_default_config(CassandraStatement *cassandra_statement);
132
+ extern CassStatement *statement_build_for_execution(CassandraStatement *cassandra_statement);
111
133
  extern void result_await(CassandraResult *cassandra_result);
112
134
 
113
135
 
data/ext/ilios/result.c CHANGED
@@ -45,6 +45,7 @@ static VALUE result_next_page(VALUE self)
45
45
  CassandraStatement *cassandra_statement;
46
46
  CassandraSession *cassandra_session;
47
47
  CassFuture *result_future;
48
+ CassError error_code;
48
49
 
49
50
  GET_RESULT(self, cassandra_result);
50
51
 
@@ -55,19 +56,37 @@ static VALUE result_next_page(VALUE self)
55
56
  GET_STATEMENT(cassandra_result->statement_obj, cassandra_statement);
56
57
  GET_SESSION(cassandra_statement->session_obj, cassandra_session);
57
58
 
58
- cass_statement_set_paging_state(cassandra_statement->statement, cassandra_result->result);
59
+ // Reuse this result's own executed statement: it is not shared with other
60
+ // executions and its previous request has already completed, so setting
61
+ // the paging state cannot race with the driver's IO thread.
62
+ cass_statement_set_paging_state(cassandra_result->executed_statement, cassandra_result->result);
59
63
 
60
- result_future = nogvl_session_execute(cassandra_session->session, cassandra_statement->statement);
64
+ result_future = nogvl_session_execute(cassandra_session->session, cassandra_result->executed_statement);
65
+ nogvl_future_wait(result_future);
66
+
67
+ error_code = cass_future_error_code(result_future);
68
+ if (error_code != CASS_OK) {
69
+ cass_future_free(result_future);
70
+ rb_raise(eExecutionError, "Unable to wait executing: %s", cass_error_desc(error_code));
71
+ }
61
72
 
62
73
  cass_result_free(cassandra_result->result);
63
- cass_future_free(cassandra_result->future);
64
- cassandra_result->result = NULL;
74
+ if (cassandra_result->future) {
75
+ cass_future_free(cassandra_result->future);
76
+ }
77
+ cassandra_result->result = cass_future_get_result(result_future);
65
78
  cassandra_result->future = result_future;
66
79
 
67
- result_await(cassandra_result);
68
80
  return self;
69
81
  }
70
82
 
83
+ static void result_check_value(CassError error_code, VALUE key)
84
+ {
85
+ if (error_code != CASS_OK) {
86
+ rb_raise(eExecutionError, "Unable to get value of %"PRIsVALUE" column: %s", key, cass_error_desc(error_code));
87
+ }
88
+ }
89
+
71
90
  static VALUE result_convert_row(const CassResult *result, const CassRow *row, size_t column_count)
72
91
  {
73
92
  VALUE key;
@@ -90,56 +109,56 @@ static VALUE result_convert_row(const CassResult *result, const CassRow *row, si
90
109
  switch (type) {
91
110
  case CASS_VALUE_TYPE_TINY_INT:
92
111
  {
93
- cass_int8_t output;
94
- cass_value_get_int8(value, &output);
112
+ cass_int8_t output = 0;
113
+ result_check_value(cass_value_get_int8(value, &output), key);
95
114
  rb_hash_aset(hash, key, INT2NUM(output));
96
115
  }
97
116
  break;
98
117
 
99
118
  case CASS_VALUE_TYPE_SMALL_INT:
100
119
  {
101
- cass_int16_t output;
102
- cass_value_get_int16(value, &output);
120
+ cass_int16_t output = 0;
121
+ result_check_value(cass_value_get_int16(value, &output), key);
103
122
  rb_hash_aset(hash, key, INT2NUM(output));
104
123
  }
105
124
  break;
106
125
 
107
126
  case CASS_VALUE_TYPE_INT:
108
127
  {
109
- cass_int32_t output;
110
- cass_value_get_int32(value, &output);
128
+ cass_int32_t output = 0;
129
+ result_check_value(cass_value_get_int32(value, &output), key);
111
130
  rb_hash_aset(hash, key, INT2NUM(output));
112
131
  }
113
132
  break;
114
133
 
115
134
  case CASS_VALUE_TYPE_BIGINT:
116
135
  {
117
- cass_int64_t output;
118
- cass_value_get_int64(value, &output);
136
+ cass_int64_t output = 0;
137
+ result_check_value(cass_value_get_int64(value, &output), key);
119
138
  rb_hash_aset(hash, key, LL2NUM(output));
120
139
  }
121
140
  break;
122
141
 
123
142
  case CASS_VALUE_TYPE_FLOAT:
124
143
  {
125
- cass_float_t output;
126
- cass_value_get_float(value, &output);
144
+ cass_float_t output = 0;
145
+ result_check_value(cass_value_get_float(value, &output), key);
127
146
  rb_hash_aset(hash, key, DBL2NUM(output));
128
147
  }
129
148
  break;
130
149
 
131
150
  case CASS_VALUE_TYPE_DOUBLE:
132
151
  {
133
- cass_double_t output;
134
- cass_value_get_double(value, &output);
152
+ cass_double_t output = 0;
153
+ result_check_value(cass_value_get_double(value, &output), key);
135
154
  rb_hash_aset(hash, key, DBL2NUM(output));
136
155
  }
137
156
  break;
138
157
 
139
158
  case CASS_VALUE_TYPE_BOOLEAN:
140
159
  {
141
- cass_bool_t output;
142
- cass_value_get_bool(value, &output);
160
+ cass_bool_t output = cass_false;
161
+ result_check_value(cass_value_get_bool(value, &output), key);
143
162
  rb_hash_aset(hash, key, output == cass_true ? Qtrue : Qfalse);
144
163
  }
145
164
  break;
@@ -148,26 +167,26 @@ static VALUE result_convert_row(const CassResult *result, const CassRow *row, si
148
167
  case CASS_VALUE_TYPE_ASCII:
149
168
  case CASS_VALUE_TYPE_VARCHAR:
150
169
  {
151
- const char* s;
152
- size_t s_length;
153
- cass_value_get_string(value, &s, &s_length);
170
+ const char* s = NULL;
171
+ size_t s_length = 0;
172
+ result_check_value(cass_value_get_string(value, &s, &s_length), key);
154
173
  rb_hash_aset(hash, key, rb_str_new(s, s_length));
155
174
  }
156
175
  break;
157
176
 
158
177
  case CASS_VALUE_TYPE_TIMESTAMP:
159
178
  {
160
- cass_int64_t output;
161
- cass_value_get_int64(value, &output);
179
+ cass_int64_t output = 0;
180
+ result_check_value(cass_value_get_int64(value, &output), key);
162
181
  rb_hash_aset(hash, key, rb_time_new(output / 1000, output % 1000 * 1000));
163
182
  }
164
183
  break;
165
184
 
166
185
  case CASS_VALUE_TYPE_UUID:
167
186
  {
168
- CassUuid output;
187
+ CassUuid output = { 0, 0 };
169
188
  char uuid[40];
170
- cass_value_get_uuid(value, &output);
189
+ result_check_value(cass_value_get_uuid(value, &output), key);
171
190
  cass_uuid_string(output, uuid);
172
191
  rb_hash_aset(hash, key, rb_str_new2(uuid));
173
192
  }
@@ -245,6 +264,9 @@ static void result_destroy(void *ptr)
245
264
  if (cassandra_result->future) {
246
265
  cass_future_free(cassandra_result->future);
247
266
  }
267
+ if (cassandra_result->executed_statement) {
268
+ cass_statement_free(cassandra_result->executed_statement);
269
+ }
248
270
  xfree(cassandra_result);
249
271
  }
250
272
 
data/ext/ilios/session.c CHANGED
@@ -85,13 +85,24 @@ static VALUE session_execute_async(VALUE self, VALUE statement)
85
85
  {
86
86
  CassandraSession *cassandra_session;
87
87
  CassandraStatement *cassandra_statement;
88
+ CassandraFuture *cassandra_future;
89
+ CassStatement *executed_statement;
88
90
  CassFuture *result_future;
91
+ VALUE future;
89
92
 
90
93
  GET_SESSION(self, cassandra_session);
91
94
  GET_STATEMENT(statement, cassandra_statement);
92
95
 
93
- result_future = nogvl_session_execute(cassandra_session->session, cassandra_statement->statement);
94
- return future_create(result_future, self, statement, execute_async);
96
+ // Execute a dedicated statement so that later re-binds of `statement`
97
+ // cannot race with the driver's asynchronous encoding (issue #12).
98
+ executed_statement = statement_build_for_execution(cassandra_statement);
99
+ result_future = nogvl_session_execute(cassandra_session->session, executed_statement);
100
+
101
+ future = future_create(result_future, self, statement, execute_async);
102
+ GET_FUTURE(future, cassandra_future);
103
+ // The future owns the executed statement and frees it on destroy.
104
+ cassandra_future->executed_statement = executed_statement;
105
+ return future;
95
106
  }
96
107
 
97
108
  /**
@@ -107,15 +118,18 @@ static VALUE session_execute(VALUE self, VALUE statement)
107
118
  CassandraSession *cassandra_session;
108
119
  CassandraStatement *cassandra_statement;
109
120
  CassandraResult *cassandra_result;
121
+ CassStatement *executed_statement;
110
122
  CassFuture *result_future;
111
123
  VALUE cassandra_result_obj;
112
124
 
113
125
  GET_SESSION(self, cassandra_session);
114
126
  GET_STATEMENT(statement, cassandra_statement);
115
127
 
116
- result_future = nogvl_session_execute(cassandra_session->session, cassandra_statement->statement);
128
+ executed_statement = statement_build_for_execution(cassandra_statement);
129
+ result_future = nogvl_session_execute(cassandra_session->session, executed_statement);
117
130
 
118
131
  cassandra_result_obj = CREATE_RESULT(cassandra_result);
132
+ cassandra_result->executed_statement = executed_statement;
119
133
  cassandra_result->future = result_future;
120
134
  cassandra_result->statement_obj = statement;
121
135
 
@@ -17,14 +17,24 @@ const rb_data_type_t cassandra_statement_data_type = {
17
17
  RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
18
18
  };
19
19
 
20
+ typedef struct
21
+ {
22
+ const CassPrepared *prepared;
23
+ CassStatement *statement;
24
+ VALUE bound_values;
25
+ } statement_bind_context;
26
+
20
27
  void statement_default_config(CassandraStatement *cassandra_statement)
21
28
  {
29
+ cassandra_statement->bound_values = Qnil;
30
+ cassandra_statement->page_size = DEFAULT_PAGE_SIZE;
31
+ cassandra_statement->idempotent = idempotency_unset;
22
32
  cass_statement_set_paging_size(cassandra_statement->statement, DEFAULT_PAGE_SIZE);
23
33
  }
24
34
 
25
- static int hash_cb(VALUE key, VALUE value, VALUE statement)
35
+ static int hash_cb(VALUE key, VALUE value, VALUE arg)
26
36
  {
27
- CassandraStatement *cassandra_statement = (CassandraStatement *)statement;
37
+ statement_bind_context *ctx = (statement_bind_context *)arg;
28
38
  const CassDataType* data_type;
29
39
  CassValueType value_type;
30
40
  CassError result;
@@ -35,14 +45,14 @@ static int hash_cb(VALUE key, VALUE value, VALUE statement)
35
45
  }
36
46
  name = StringValueCStr(key);
37
47
 
38
- data_type = cass_prepared_parameter_data_type_by_name(cassandra_statement->prepared, name);
48
+ data_type = cass_prepared_parameter_data_type_by_name(ctx->prepared, name);
39
49
  if (data_type == NULL) {
40
50
  rb_raise(eStatementError, "Invalid name %s was given.", name);
41
51
  }
42
52
  value_type = cass_data_type_type(data_type);
43
53
 
44
54
  if (NIL_P(value)) {
45
- result = cass_statement_bind_null_by_name(cassandra_statement->statement, name);
55
+ result = cass_statement_bind_null_by_name(ctx->statement, name);
46
56
  goto result_check;
47
57
  }
48
58
 
@@ -54,7 +64,7 @@ static int hash_cb(VALUE key, VALUE value, VALUE statement)
54
64
  if (v < INT8_MIN || v > INT8_MAX) {
55
65
  rb_raise(rb_eRangeError, "Invalid value: %ld", v);
56
66
  }
57
- result = cass_statement_bind_int8_by_name(cassandra_statement->statement, name, (cass_int8_t)v);
67
+ result = cass_statement_bind_int8_by_name(ctx->statement, name, (cass_int8_t)v);
58
68
  }
59
69
  break;
60
70
 
@@ -66,7 +76,7 @@ static int hash_cb(VALUE key, VALUE value, VALUE statement)
66
76
  rb_raise(rb_eRangeError, "Invalid value: %ld", v);
67
77
  }
68
78
 
69
- result = cass_statement_bind_int16_by_name(cassandra_statement->statement, name, (cass_int16_t)v);
79
+ result = cass_statement_bind_int16_by_name(ctx->statement, name, (cass_int16_t)v);
70
80
  }
71
81
  break;
72
82
 
@@ -78,12 +88,12 @@ static int hash_cb(VALUE key, VALUE value, VALUE statement)
78
88
  rb_raise(rb_eRangeError, "Invalid value: %ld", v);
79
89
  }
80
90
 
81
- result = cass_statement_bind_int32_by_name(cassandra_statement->statement, name, (cass_int32_t)v);
91
+ result = cass_statement_bind_int32_by_name(ctx->statement, name, (cass_int32_t)v);
82
92
  }
83
93
  break;
84
94
 
85
95
  case CASS_VALUE_TYPE_BIGINT:
86
- result = cass_statement_bind_int64_by_name(cassandra_statement->statement, name, NUM2LONG(value));
96
+ result = cass_statement_bind_int64_by_name(ctx->statement, name, NUM2LONG(value));
87
97
  break;
88
98
 
89
99
  case CASS_VALUE_TYPE_FLOAT:
@@ -94,25 +104,25 @@ static int hash_cb(VALUE key, VALUE value, VALUE statement)
94
104
  rb_raise(rb_eRangeError, "Invalid value: %lf", v);
95
105
  }
96
106
 
97
- result = cass_statement_bind_float_by_name(cassandra_statement->statement, name, v);
107
+ result = cass_statement_bind_float_by_name(ctx->statement, name, v);
98
108
  }
99
109
  break;
100
110
 
101
111
  case CASS_VALUE_TYPE_DOUBLE:
102
- result = cass_statement_bind_double_by_name(cassandra_statement->statement, name, NUM2DBL(value));
112
+ result = cass_statement_bind_double_by_name(ctx->statement, name, NUM2DBL(value));
103
113
  break;
104
114
 
105
115
  case CASS_VALUE_TYPE_BOOLEAN:
106
116
  {
107
117
  cass_bool_t v = RTEST(value) ? cass_true : cass_false;
108
- result = cass_statement_bind_bool_by_name(cassandra_statement->statement, name, v);
118
+ result = cass_statement_bind_bool_by_name(ctx->statement, name, v);
109
119
  }
110
120
  break;
111
121
 
112
122
  case CASS_VALUE_TYPE_TEXT:
113
123
  case CASS_VALUE_TYPE_ASCII:
114
124
  case CASS_VALUE_TYPE_VARCHAR:
115
- result = cass_statement_bind_string_by_name(cassandra_statement->statement, name, StringValueCStr(value));
125
+ result = cass_statement_bind_string_by_name(ctx->statement, name, StringValueCStr(value));
116
126
  break;
117
127
 
118
128
  case CASS_VALUE_TYPE_TIMESTAMP:
@@ -123,16 +133,20 @@ static int hash_cb(VALUE key, VALUE value, VALUE statement)
123
133
  rb_raise(rb_eTypeError, "no implicit conversion of %"PRIsVALUE" to Time", rb_obj_class(value));
124
134
  }
125
135
  }
126
- result = cass_statement_bind_int64_by_name(cassandra_statement->statement, name, (cass_int64_t)(NUM2DBL(rb_Float(value)) * 1000));
136
+ result = cass_statement_bind_int64_by_name(ctx->statement, name, (cass_int64_t)(NUM2DBL(rb_Float(value)) * 1000));
127
137
  break;
128
138
 
129
139
  case CASS_VALUE_TYPE_UUID:
130
140
  {
131
- CassUuid uuid;
141
+ CassUuid uuid = { 0, 0 };
132
142
  const char *uuid_string = StringValueCStr(value);
133
143
 
134
- cass_uuid_from_string(uuid_string, &uuid);
135
- result = cass_statement_bind_uuid_by_name(cassandra_statement->statement, name, uuid);
144
+ result = cass_uuid_from_string(uuid_string, &uuid);
145
+ if (result != CASS_OK) {
146
+ rb_raise(eStatementError, "Invalid UUID was given: %s=%"PRIsVALUE"", name, value);
147
+ }
148
+
149
+ result = cass_statement_bind_uuid_by_name(ctx->statement, name, uuid);
136
150
  }
137
151
  break;
138
152
 
@@ -145,9 +159,65 @@ result_check:
145
159
  rb_raise(eStatementError, "Failed to bind value: %s", cass_error_desc(result));
146
160
  }
147
161
 
162
+ if (!NIL_P(ctx->bound_values)) {
163
+ if (RB_TYPE_P(value, T_STRING)) {
164
+ // Snapshot the value so a later in-place mutation by the caller
165
+ // doesn't change what gets bound at execution time.
166
+ value = rb_str_new_frozen(value);
167
+ }
168
+ rb_hash_aset(ctx->bound_values, key, value);
169
+ }
170
+
148
171
  return ST_CONTINUE;
149
172
  }
150
173
 
174
+ typedef struct
175
+ {
176
+ statement_bind_context ctx;
177
+ VALUE hash;
178
+ } statement_rebind_args;
179
+
180
+ static VALUE statement_rebind_body(VALUE arg)
181
+ {
182
+ statement_rebind_args *args = (statement_rebind_args *)arg;
183
+
184
+ rb_hash_foreach(args->hash, hash_cb, (VALUE)&args->ctx);
185
+ return Qnil;
186
+ }
187
+
188
+ /*
189
+ * Builds a fresh CassStatement carrying the current configuration and bound
190
+ * values for a single execution. The returned statement must not be mutated
191
+ * once handed to the driver, and the caller owns it: it must stay alive until
192
+ * the execution's future resolves and be freed exactly once afterwards.
193
+ */
194
+ CassStatement *statement_build_for_execution(CassandraStatement *cassandra_statement)
195
+ {
196
+ CassStatement *statement = cass_prepared_bind(cassandra_statement->prepared);
197
+
198
+ cass_statement_set_paging_size(statement, cassandra_statement->page_size);
199
+ if (cassandra_statement->idempotent != idempotency_unset) {
200
+ cass_statement_set_is_idempotent(statement, cassandra_statement->idempotent == idempotency_true ? cass_true : cass_false);
201
+ }
202
+
203
+ if (!NIL_P(cassandra_statement->bound_values)) {
204
+ statement_rebind_args args;
205
+ int state = 0;
206
+
207
+ args.ctx.prepared = cassandra_statement->prepared;
208
+ args.ctx.statement = statement;
209
+ args.ctx.bound_values = Qnil;
210
+ args.hash = cassandra_statement->bound_values;
211
+
212
+ rb_protect(statement_rebind_body, (VALUE)&args, &state);
213
+ if (state) {
214
+ cass_statement_free(statement);
215
+ rb_jump_tag(state);
216
+ }
217
+ }
218
+ return statement;
219
+ }
220
+
151
221
  /**
152
222
  * Binds a specified column value to a query.
153
223
  * A hash object should be given with column name as key.
@@ -161,11 +231,27 @@ result_check:
161
231
  static VALUE statement_bind(VALUE self, VALUE hash)
162
232
  {
163
233
  CassandraStatement *cassandra_statement;
234
+ statement_bind_context ctx;
235
+ VALUE bound_values;
164
236
 
165
237
  Check_Type(hash, T_HASH);
166
238
  TypedData_Get_Struct(self, CassandraStatement, &cassandra_statement_data_type, cassandra_statement);
167
239
 
168
- rb_hash_foreach(hash, hash_cb, (VALUE)cassandra_statement);
240
+ // Merge into a copy instead of mutating in place: the previous hash may be
241
+ // shared with a frozen (Ractor-shareable) statement or be iterated by an
242
+ // execution on another thread.
243
+ if (NIL_P(cassandra_statement->bound_values)) {
244
+ bound_values = rb_hash_new();
245
+ } else {
246
+ bound_values = rb_hash_dup(cassandra_statement->bound_values);
247
+ }
248
+ RB_OBJ_WRITE(self, &cassandra_statement->bound_values, bound_values);
249
+
250
+ ctx.prepared = cassandra_statement->prepared;
251
+ ctx.statement = cassandra_statement->statement;
252
+ ctx.bound_values = bound_values;
253
+
254
+ rb_hash_foreach(hash, hash_cb, (VALUE)&ctx);
169
255
  return self;
170
256
  }
171
257
 
@@ -180,7 +266,8 @@ static VALUE statement_page_size(VALUE self, VALUE page_size)
180
266
  CassandraStatement *cassandra_statement;
181
267
 
182
268
  GET_STATEMENT(self, cassandra_statement);
183
- cass_statement_set_paging_size(cassandra_statement->statement, NUM2INT(page_size));
269
+ cassandra_statement->page_size = NUM2INT(page_size);
270
+ cass_statement_set_paging_size(cassandra_statement->statement, cassandra_statement->page_size);
184
271
  return self;
185
272
  }
186
273
 
@@ -197,7 +284,8 @@ static VALUE statement_idempotent(VALUE self, VALUE idempotent)
197
284
  CassandraStatement *cassandra_statement;
198
285
 
199
286
  GET_STATEMENT(self, cassandra_statement);
200
- cass_statement_set_is_idempotent(cassandra_statement->statement, RTEST(idempotent) ? cass_true : cass_false);
287
+ cassandra_statement->idempotent = RTEST(idempotent) ? idempotency_true : idempotency_false;
288
+ cass_statement_set_is_idempotent(cassandra_statement->statement, cassandra_statement->idempotent == idempotency_true ? cass_true : cass_false);
201
289
  return self;
202
290
  }
203
291
 
@@ -205,6 +293,7 @@ static void statement_mark(void *ptr)
205
293
  {
206
294
  CassandraStatement *cassandra_statement = (CassandraStatement *)ptr;
207
295
  rb_gc_mark_movable(cassandra_statement->session_obj);
296
+ rb_gc_mark_movable(cassandra_statement->bound_values);
208
297
  }
209
298
 
210
299
  static void statement_destroy(void *ptr)
@@ -230,6 +319,7 @@ static void statement_compact(void *ptr)
230
319
  CassandraStatement *cassandra_statement = (CassandraStatement *)ptr;
231
320
 
232
321
  cassandra_statement->session_obj = rb_gc_location(cassandra_statement->session_obj);
322
+ cassandra_statement->bound_values = rb_gc_location(cassandra_statement->bound_values);
233
323
  }
234
324
 
235
325
  void Init_statement(void)
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.0.4'
4
+ VERSION = '1.0.6'
5
5
  public_constant :VERSION
6
6
 
7
7
  CASSANDRA_CPP_DRIVER_VERSION = '2.17.1'
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.0.4
4
+ version: 1.0.6
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.0.4
61
+ documentation_uri: https://www.rubydoc.info/gems/ilios/1.0.6
62
62
  rubygems_mfa_required: 'true'
63
63
  rdoc_options: []
64
64
  require_paths:
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
75
  version: '0'
76
76
  requirements:
77
77
  - cmake
78
- rubygems_version: 4.0.15
78
+ rubygems_version: 4.0.16
79
79
  specification_version: 4
80
80
  summary: Cassandra driver written by C language
81
81
  test_files: []