From d02a7aa7dc31a5cce6ef5f4069b30f9eaaa92940 Mon Sep 17 00:00:00 2001 From: silviu caragea Date: Tue, 8 Nov 2022 00:10:48 +0200 Subject: [PATCH 1/3] When using undefined for binding we don't set null to avoid creating a tombstone --- c_src/cass_binding.cc | 3 +++ c_src/constants.cc | 1 + c_src/constants.h | 1 + c_src/erlcass.cc | 1 + c_src/erlcass.h | 1 + 5 files changed, 7 insertions(+) diff --git a/c_src/cass_binding.cc b/c_src/cass_binding.cc index cc9ab13..cd81f39 100644 --- a/c_src/cass_binding.cc +++ b/c_src/cass_binding.cc @@ -464,6 +464,9 @@ ERL_NIF_TERM nif_list_to_cass_collection(ErlNifEnv* env, ERL_NIF_TERM list, cons ERL_NIF_TERM cass_bind_by_index(ErlNifEnv* env, CassStatement* statement, size_t index, const datastax::internal::core::DataType* data_type, ERL_NIF_TERM value) { + if(enif_is_identical(value, ATOMS.atomUndefined)) + return ATOMS.atomOk; + if(enif_is_identical(value, ATOMS.atomNull)) return cass_error_to_nif_term(env, cass_statement_bind_null(statement, index)); diff --git a/c_src/constants.cc b/c_src/constants.cc index bac13cc..4d03766 100644 --- a/c_src/constants.cc +++ b/c_src/constants.cc @@ -22,6 +22,7 @@ const char kAtomError[] = "error"; const char kAtomTrue[] = "true"; const char kAtomFalse[] = "false"; const char kAtomNull[] = "null"; +const char kAtomUndefined[] = "undefined"; const char kAtomBadArg[] = "badarg"; const char kAtomOptions[] = "options"; const char kAtomConsistencyLevel[] = "consistency_level"; diff --git a/c_src/constants.h b/c_src/constants.h index 171c9a9..70c8c18 100644 --- a/c_src/constants.h +++ b/c_src/constants.h @@ -23,6 +23,7 @@ extern const char kAtomError[]; extern const char kAtomTrue[]; extern const char kAtomFalse[]; extern const char kAtomNull[]; +extern const char kAtomUndefined[]; extern const char kAtomBadArg[]; extern const char kAtomOptions[]; extern const char kAtomConsistencyLevel[]; diff --git a/c_src/erlcass.cc b/c_src/erlcass.cc index 1ec1d9c..3b65124 100755 --- a/c_src/erlcass.cc +++ b/c_src/erlcass.cc @@ -25,6 +25,7 @@ int on_nif_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) ATOMS.atomTrue = make_atom(env, erlcass::kAtomTrue); ATOMS.atomFalse = make_atom(env, erlcass::kAtomFalse); ATOMS.atomNull = make_atom(env, erlcass::kAtomNull); + ATOMS.atomUndefined = make_atom(env, erlcass::kAtomUndefined); ATOMS.atomBadArg = make_atom(env, erlcass::kAtomBadArg); ATOMS.atomOptions = make_atom(env, erlcass::kAtomOptions); ATOMS.atomConsistencyLevel = make_atom(env, erlcass::kAtomConsistencyLevel); diff --git a/c_src/erlcass.h b/c_src/erlcass.h index 82f5d22..16daaae 100644 --- a/c_src/erlcass.h +++ b/c_src/erlcass.h @@ -11,6 +11,7 @@ struct atoms ERL_NIF_TERM atomTrue; ERL_NIF_TERM atomFalse; ERL_NIF_TERM atomNull; + ERL_NIF_TERM atomUndefined; ERL_NIF_TERM atomBadArg; ERL_NIF_TERM atomOptions; ERL_NIF_TERM atomConsistencyLevel; From 8b8a2fdee99b05145c6f8c0e52c505746e1cc718 Mon Sep 17 00:00:00 2001 From: silviu caragea Date: Tue, 8 Nov 2022 12:53:22 +0200 Subject: [PATCH 2/3] Set option to avoid null binding. --- c_src/cass_binding.cc | 18 ++++++++---- c_src/cass_binding.h | 2 +- c_src/constants.cc | 1 + c_src/constants.h | 1 + c_src/erlcass.cc | 1 + c_src/erlcass.h | 1 + c_src/nif_cass_prepared.cc | 6 ++-- c_src/nif_cass_prepared.h | 2 +- c_src/nif_cass_session.cc | 4 ++- c_src/nif_cass_statement.cc | 19 +++++++++---- c_src/nif_cass_statement.h | 2 +- c_src/nif_utils.cc | 56 ++++++++++++++++++++++++++++++++++++- c_src/nif_utils.h | 1 + 13 files changed, 96 insertions(+), 18 deletions(-) diff --git a/c_src/cass_binding.cc b/c_src/cass_binding.cc index cd81f39..d002b07 100644 --- a/c_src/cass_binding.cc +++ b/c_src/cass_binding.cc @@ -462,13 +462,21 @@ ERL_NIF_TERM nif_list_to_cass_collection(ErlNifEnv* env, ERL_NIF_TERM list, cons return ATOMS.atomOk; } -ERL_NIF_TERM cass_bind_by_index(ErlNifEnv* env, CassStatement* statement, size_t index, const datastax::internal::core::DataType* data_type, ERL_NIF_TERM value) +ERL_NIF_TERM cass_bind_by_index(ErlNifEnv* env, CassStatement* statement, size_t index, const datastax::internal::core::DataType* data_type, ERL_NIF_TERM value, bool null_binding) { - if(enif_is_identical(value, ATOMS.atomUndefined)) - return ATOMS.atomOk; + if(enif_is_atom(env, value)) + { + if(enif_is_identical(value, ATOMS.atomNull)) + { + if(null_binding) + return cass_error_to_nif_term(env, cass_statement_bind_null(statement, index)); + else + return ATOMS.atomOk; + } - if(enif_is_identical(value, ATOMS.atomNull)) - return cass_error_to_nif_term(env, cass_statement_bind_null(statement, index)); + if(enif_is_identical(value, ATOMS.atomUndefined)) + return ATOMS.atomOk; + } return cass_set_from_nif(env, statement, index, kCassStatementFuns, data_type, value); } diff --git a/c_src/cass_binding.h b/c_src/cass_binding.h index af0ba91..79cd5c1 100644 --- a/c_src/cass_binding.h +++ b/c_src/cass_binding.h @@ -8,6 +8,6 @@ namespace datastax { namespace internal { namespace core { class DataType; }}} -ERL_NIF_TERM cass_bind_by_index(ErlNifEnv* env, CassStatement* statement, size_t index, const datastax::internal::core::DataType* data_type, ERL_NIF_TERM value); +ERL_NIF_TERM cass_bind_by_index(ErlNifEnv* env, CassStatement* statement, size_t index, const datastax::internal::core::DataType* data_type, ERL_NIF_TERM value, bool null_binding); #endif // C_SRC_CASS_BINDING_H_ diff --git a/c_src/constants.cc b/c_src/constants.cc index 4d03766..93e7016 100644 --- a/c_src/constants.cc +++ b/c_src/constants.cc @@ -28,6 +28,7 @@ const char kAtomOptions[] = "options"; const char kAtomConsistencyLevel[] = "consistency_level"; const char kAtomSerialConsistencyLevel[] = "serial_consistency_level"; const char kAtomLogMsgRecord[] = "log_msg"; +const char kAtomNullBinding[] = "null_binding"; // events atoms diff --git a/c_src/constants.h b/c_src/constants.h index 70c8c18..5e0eee9 100644 --- a/c_src/constants.h +++ b/c_src/constants.h @@ -29,6 +29,7 @@ extern const char kAtomOptions[]; extern const char kAtomConsistencyLevel[]; extern const char kAtomSerialConsistencyLevel[]; extern const char kAtomLogMsgRecord[]; +extern const char kAtomNullBinding[]; // events atoms diff --git a/c_src/erlcass.cc b/c_src/erlcass.cc index 3b65124..a268a8a 100755 --- a/c_src/erlcass.cc +++ b/c_src/erlcass.cc @@ -31,6 +31,7 @@ int on_nif_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) ATOMS.atomConsistencyLevel = make_atom(env, erlcass::kAtomConsistencyLevel); ATOMS.atomSerialConsistencyLevel = make_atom(env, erlcass::kAtomSerialConsistencyLevel); ATOMS.atomLogMsgRecord = make_atom(env, erlcass::kAtomLogMsgRecord); + ATOMS.atomNullBinding = make_atom(env, erlcass::kAtomNullBinding); // events atoms diff --git a/c_src/erlcass.h b/c_src/erlcass.h index 16daaae..b6513af 100644 --- a/c_src/erlcass.h +++ b/c_src/erlcass.h @@ -17,6 +17,7 @@ struct atoms ERL_NIF_TERM atomConsistencyLevel; ERL_NIF_TERM atomSerialConsistencyLevel; ERL_NIF_TERM atomLogMsgRecord; + ERL_NIF_TERM atomNullBinding; // events atoms diff --git a/c_src/nif_cass_prepared.cc b/c_src/nif_cass_prepared.cc index e197e15..5f313e4 100644 --- a/c_src/nif_cass_prepared.cc +++ b/c_src/nif_cass_prepared.cc @@ -7,9 +7,10 @@ struct enif_cass_prepared { const CassPrepared* prepared; ConsistencyLevelOptions consistency; + bool null_binding; }; -ERL_NIF_TERM nif_cass_prepared_new(ErlNifEnv* env, ErlNifResourceType* rs, const CassPrepared* prep, const ConsistencyLevelOptions& consistency) +ERL_NIF_TERM nif_cass_prepared_new(ErlNifEnv* env, ErlNifResourceType* rs, const CassPrepared* prep, const ConsistencyLevelOptions& consistency, bool null_binding) { enif_cass_prepared* enif_obj = static_cast(enif_alloc_resource(rs, sizeof(enif_cass_prepared))); @@ -18,6 +19,7 @@ ERL_NIF_TERM nif_cass_prepared_new(ErlNifEnv* env, ErlNifResourceType* rs, const enif_obj->prepared = prep; enif_obj->consistency = consistency; + enif_obj->null_binding = null_binding; ERL_NIF_TERM term = enif_make_resource(env, enif_obj); enif_release_resource(enif_obj); @@ -42,7 +44,7 @@ ERL_NIF_TERM nif_cass_prepared_bind(ErlNifEnv* env, int argc, const ERL_NIF_TERM if(!enif_get_resource(env, argv[0], data->resCassPrepared, reinterpret_cast(&enif_prep))) return make_badarg(env); - ERL_NIF_TERM term = nif_cass_statement_new(env, data->resCassStatement, enif_prep->prepared, enif_prep->consistency); + ERL_NIF_TERM term = nif_cass_statement_new(env, data->resCassStatement, enif_prep->prepared, enif_prep->consistency, enif_prep->null_binding); if(enif_is_tuple(env, term)) return term; diff --git a/c_src/nif_cass_prepared.h b/c_src/nif_cass_prepared.h index f86b2e4..c29e952 100644 --- a/c_src/nif_cass_prepared.h +++ b/c_src/nif_cass_prepared.h @@ -5,7 +5,7 @@ #include "cassandra.h" #include "nif_utils.h" -ERL_NIF_TERM nif_cass_prepared_new(ErlNifEnv* env, ErlNifResourceType* rs, const CassPrepared* prep, const ConsistencyLevelOptions& consistency); +ERL_NIF_TERM nif_cass_prepared_new(ErlNifEnv* env, ErlNifResourceType* rs, const CassPrepared* prep, const ConsistencyLevelOptions& consistency, bool null_binding); ERL_NIF_TERM nif_cass_prepared_bind(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); void nif_cass_prepared_free(ErlNifEnv* env, void* obj); diff --git a/c_src/nif_cass_session.cc b/c_src/nif_cass_session.cc index 2e382ef..93abf71 100644 --- a/c_src/nif_cass_session.cc +++ b/c_src/nif_cass_session.cc @@ -32,6 +32,7 @@ struct callback_statement_info ERL_NIF_TERM arguments; ErlNifResourceType* prepared_res; ConsistencyLevelOptions consistency; + bool null_binding; CassSession* session; }; @@ -115,7 +116,7 @@ void on_statement_prepared(CassFuture* future, void* user_data) { const CassPrepared* prep = cass_future_get_prepared(future); - ERL_NIF_TERM term = nif_cass_prepared_new(cb->env, cb->prepared_res, prep, cb->consistency); + ERL_NIF_TERM term = nif_cass_prepared_new(cb->env, cb->prepared_res, prep, cb->consistency, cb->null_binding); if(enif_is_tuple(cb->env, term)) { @@ -292,6 +293,7 @@ ERL_NIF_TERM nif_cass_session_prepare(ErlNifEnv* env, int argc, const ERL_NIF_TE callback->arguments = enif_make_copy(callback->env, argv[3]); callback->consistency = q.consistency; callback->session = enif_session->session; + callback->null_binding = q.null_binding; CassFuture* future = cass_session_prepare_n(enif_session->session, BIN_TO_STR(q.query.data), q.query.size); diff --git a/c_src/nif_cass_statement.cc b/c_src/nif_cass_statement.cc index d8cc408..f449f60 100644 --- a/c_src/nif_cass_statement.cc +++ b/c_src/nif_cass_statement.cc @@ -7,16 +7,19 @@ #define BIND_BY_INDEX 1 #define BIND_BY_NAME 2 +namespace { + struct enif_cass_statement { CassStatement* statement; + bool null_binding; }; -ERL_NIF_TERM bind_prepared_statement_params(ErlNifEnv* env, CassStatement* statement, int type, ERL_NIF_TERM list) +ERL_NIF_TERM bind_prepared_statement_params(ErlNifEnv* env, enif_cass_statement* enif_stm, int type, ERL_NIF_TERM list) { ERL_NIF_TERM head; - datastax::internal::core::Statement* stm = static_cast(statement); + datastax::internal::core::Statement* stm = static_cast(enif_stm->statement); const datastax::internal::core::ResultResponse* result = static_cast(stm)->prepared()->result().get(); if(type == BIND_BY_NAME) @@ -42,7 +45,7 @@ ERL_NIF_TERM bind_prepared_statement_params(ErlNifEnv* env, CassStatement* state size_t index = indices[0]; const datastax::internal::core::DataType* data_type = result->metadata()->get_column_definition(index).data_type.get(); - ERL_NIF_TERM nif_result = cass_bind_by_index(env, statement, index, data_type, items[1]); + ERL_NIF_TERM nif_result = cass_bind_by_index(env, enif_stm->statement, index, data_type, items[1], enif_stm->null_binding); if(!enif_is_identical(nif_result, ATOMS.atomOk)) return nif_result; @@ -66,7 +69,7 @@ ERL_NIF_TERM bind_prepared_statement_params(ErlNifEnv* env, CassStatement* state const datastax::internal::core::DataType* data_type = def.data_type.get(); - ERL_NIF_TERM nif_result = cass_bind_by_index(env, statement, index, data_type, head); + ERL_NIF_TERM nif_result = cass_bind_by_index(env, enif_stm->statement, index, data_type, head, enif_stm->null_binding); if(!enif_is_identical(nif_result, ATOMS.atomOk)) return nif_result; @@ -78,6 +81,8 @@ ERL_NIF_TERM bind_prepared_statement_params(ErlNifEnv* env, CassStatement* state return ATOMS.atomOk; } +} + CassStatement* get_statement(ErlNifEnv* env, ErlNifResourceType* resource_type, ERL_NIF_TERM arg) { enif_cass_statement* enif_stm = NULL; @@ -120,6 +125,7 @@ ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM return make_error(env, erlcass::kFailedToAllocResourceMsg); enif_obj->statement = stm; + enif_obj->null_binding = q.null_binding; ERL_NIF_TERM term = enif_make_resource(env, enif_obj); enif_release_resource(enif_obj); @@ -127,7 +133,7 @@ ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM return enif_make_tuple2(env, ATOMS.atomOk, term); } -ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, ErlNifResourceType* resource_type, const CassPrepared* prep, const ConsistencyLevelOptions& consistency) +ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, ErlNifResourceType* resource_type, const CassPrepared* prep, const ConsistencyLevelOptions& consistency, bool null_binding) { enif_cass_statement* enif_obj = static_cast(enif_alloc_resource(resource_type, sizeof(enif_cass_statement))); @@ -135,6 +141,7 @@ ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, ErlNifResourceType* resource return make_error(env, erlcass::kFailedToAllocResourceMsg); enif_obj->statement = cass_prepared_bind(prep); + enif_obj->null_binding = null_binding; CassError cass_result = cass_statement_set_consistency(enif_obj->statement, consistency.cl); @@ -177,7 +184,7 @@ ERL_NIF_TERM nif_cass_statement_bind_parameters(ErlNifEnv* env, int argc, const if(!enif_get_int(env, argv[1], &bind_type) || (bind_type != BIND_BY_INDEX && bind_type != BIND_BY_NAME)) return make_badarg(env); - return bind_prepared_statement_params(env, enif_stm->statement, bind_type, argv[2]); + return bind_prepared_statement_params(env, enif_stm, bind_type, argv[2]); } ERL_NIF_TERM nif_cass_statement_set_paging_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) diff --git a/c_src/nif_cass_statement.h b/c_src/nif_cass_statement.h index 172d8e7..e6d1156 100644 --- a/c_src/nif_cass_statement.h +++ b/c_src/nif_cass_statement.h @@ -6,7 +6,7 @@ CassStatement* get_statement(ErlNifEnv* env, ErlNifResourceType* resource_type, ERL_NIF_TERM arg); ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, ErlNifResourceType* resource_type, const CassPrepared* prep, const ConsistencyLevelOptions& consistency); +ERL_NIF_TERM nif_cass_statement_new(ErlNifEnv* env, ErlNifResourceType* resource_type, const CassPrepared* prep, const ConsistencyLevelOptions& consistency, bool null_binding); ERL_NIF_TERM nif_cass_statement_bind_parameters(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM nif_cass_statement_set_paging_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); void nif_cass_statement_free(ErlNifEnv* env, void* obj); diff --git a/c_src/nif_utils.cc b/c_src/nif_utils.cc index 4f8c082..37d30d4 100644 --- a/c_src/nif_utils.cc +++ b/c_src/nif_utils.cc @@ -3,6 +3,60 @@ #include +namespace { + +ERL_NIF_TERM parse_query_options(ErlNifEnv* env, ERL_NIF_TERM options_list, QueryTerm* q) +{ + ERL_NIF_TERM head; + const ERL_NIF_TERM* items; + int arity; + + while(enif_get_list_cell(env, options_list, &head, &options_list)) + { + if(!enif_get_tuple(env, head, &arity, &items) || arity != 2) + return make_bad_options(env, head); + + ERL_NIF_TERM key = items[0]; + ERL_NIF_TERM value = items[1]; + + if(enif_is_identical(key, ATOMS.atomConsistencyLevel)) + { + int c_level; + + if(!enif_get_int(env, value, &c_level)) + return make_bad_options(env, head); + + q->consistency.cl = static_cast(c_level); + } + else if(enif_is_identical(key, ATOMS.atomSerialConsistencyLevel)) + { + int c_level; + + if(!enif_get_int(env, value, &c_level)) + return make_bad_options(env, head); + + q->consistency.serial_cl = static_cast(c_level); + } + else if(enif_is_identical(key, ATOMS.atomNullBinding)) + { + cass_bool_t bool_value; + + if(!get_boolean(value, &bool_value)) + return make_badarg(env); + + q->null_binding = static_cast(bool_value); + } + else + { + return make_bad_options(env, head); + } + } + + return ATOMS.atomOk; +} + +} + ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name) { ERL_NIF_TERM ret; @@ -143,7 +197,7 @@ ERL_NIF_TERM parse_query_term(ErlNifEnv* env, ERL_NIF_TERM qterm, QueryTerm* q) if(enif_is_list(env, items[1])) { - return parse_consistency_level_options(env, items[1], &q->consistency); + return parse_query_options(env, items[1], q); } else { diff --git a/c_src/nif_utils.h b/c_src/nif_utils.h index 58e3293..a7e14d1 100644 --- a/c_src/nif_utils.h +++ b/c_src/nif_utils.h @@ -19,6 +19,7 @@ struct QueryTerm ErlNifBinary query; ConsistencyLevelOptions consistency; + bool null_binding = true; }; ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name); From a0e658e3a7090e5cff49e4e09732c86b5cf6c1fc Mon Sep 17 00:00:00 2001 From: silviu caragea Date: Tue, 8 Nov 2022 13:54:11 +0200 Subject: [PATCH 3/3] Add tests and update changelog --- CHANGELOG.md | 5 +++++ README.md | 3 +++ src/erlcass.app.src | 2 +- test/integrity_test_SUITE.erl | 26 ++++++++++++++++++++++---- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9547a..0312f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ### Changelog: +##### v4.0.8 + +- Update cpp-driver to 2.16.2 +- Add support to avoid creating tombstones while inserting data using prepared statements (https://github.com/silviucpp/erlcass/wiki/Null-bindings-on-prepared-statements-and-undesired-tombstone-creation). + ##### v4.0.7 - Fix compilation on architectures where char is unsigned by default https://github.com/silviucpp/erlcass/issues/53 diff --git a/README.md b/README.md index 888cf51..1bbfdc4 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ List of supported features: - Asynchronous API - Synchronous API - Simple, Prepared, and Batch statements +- [Avoid undesired tombstone while null binding][10] (only on protocol 4 or newer). - Paged queries - Asynchronous I/O, parallel execution, and request pipelining - Connection pooling @@ -204,6 +205,7 @@ Also this is possible using `{Query, Options}` where options is a proplist with - `serial_consistency_level` - This consistency can only be either `?CASS_CONSISTENCY_SERIAL` or `?CASS_CONSISTENCY_LOCAL_SERIAL` and if not present, it defaults to `?CASS_CONSISTENCY_SERIAL`. This option will be ignored for anything else that a conditional update/insert. +- `null_binding` - Boolean (by default `true`). Provides a way to disable the null values binding. [Binding null values][10] will create undesired tombstone in cassandra. Example: @@ -435,3 +437,4 @@ For mode details about bind by index and name please see: 'Run a prepared statem [7]:https://github.com/lpgauth/marina [8]:https://github.com/silviucpp/erlcass [9]:https://github.com/silviucpp/erlcass/wiki/Todo-list +[10]:https://github.com/silviucpp/erlcass/wiki/Null-bindings-on-prepared-statements-and-undesired-tombstone-creation diff --git a/src/erlcass.app.src b/src/erlcass.app.src index f26d7b6..8f3cba8 100644 --- a/src/erlcass.app.src +++ b/src/erlcass.app.src @@ -2,7 +2,7 @@ {description, "ErlCass - Erlang Cassandra Driver"}, {licenses, ["MIT"]}, {links,[{"Github","https://github.com/silviucpp/erlcass"}]}, - {vsn, "4.0.7"}, + {vsn, "4.0.8"}, {registered, []}, {applications, [kernel, stdlib, lager]}, {mod, {erlcass_app, []}}, diff --git a/test/integrity_test_SUITE.erl b/test/integrity_test_SUITE.erl index a292672..1a48490 100644 --- a/test/integrity_test_SUITE.erl +++ b/test/integrity_test_SUITE.erl @@ -31,6 +31,7 @@ groups() -> [ date_time_testing, get_metrics, paged_query, + null_prepared_stm_binding, drop_keyspace ]} ]. @@ -496,11 +497,28 @@ paged_query(_Config) -> {ok, Stm} = erlcass:bind_prepared_statement(paged_query_prep), PageSize = 3, ok = erlcass:set_paging_size(Stm, PageSize), - {ok, _Columns, [[1],[2],[3]] = _Rows1, true = _HasMore1} = erlcass:execute_paged(Stm, paged_query_prep), - {ok, _Columns, [[4],[5],[6]] = _Rows2, true = _HasMore2} = erlcass:execute_paged(Stm, paged_query_prep), - {ok, _Columns, [[7],[8],[9]] = _Rows3, true = _HasMore3} = erlcass:execute_paged(Stm, paged_query_prep), - {ok, _Columns, [[10]] = _Rows4, false = _HasMore4} = erlcass:execute_paged(Stm, paged_query_prep), + {ok, _Columns1, [[1],[2],[3]] = _Rows1, true = _HasMore1} = erlcass:execute_paged(Stm, paged_query_prep), + {ok, _Columns2, [[4],[5],[6]] = _Rows2, true = _HasMore2} = erlcass:execute_paged(Stm, paged_query_prep), + {ok, _Columns3, [[7],[8],[9]] = _Rows3, true = _HasMore3} = erlcass:execute_paged(Stm, paged_query_prep), + {ok, _Columns4, [[10]] = _Rows4, false = _HasMore4} = erlcass:execute_paged(Stm, paged_query_prep), ok. +null_prepared_stm_binding(_Config) -> + % none of the following inserts will create a tombstone while insert. To avoid tombstone while using prepared statements you can use one of: + % a) use bind by name and add into the binding params only the one with values (not null). + % b) while using binding by index use `undefined` atom for the values that are not set. + % c) when you prepare the statement use the option: `null_binding` as false. + ok = erlcass:query(<<"CREATE TABLE erlang_driver_test.null_prepared_stm_binding(key int PRIMARY KEY, name text, age int)">>), + ok = erlcass:add_prepare_statement(insert_unset_bind, <<"INSERT INTO erlang_driver_test.null_prepared_stm_binding (key, name, age) VALUES(?,?,?)">>), + ok = erlcass:add_prepare_statement(insert_unset_bind_2, {<<"INSERT INTO erlang_driver_test.null_prepared_stm_binding (key, name, age) VALUES(?,?,?)">>, [{null_binding, false}]}), + ok = erlcass:add_prepare_statement(select_unset_bind, <<"SELECT key, name, age FROM erlang_driver_test.null_prepared_stm_binding where key = ?">>), + ok = erlcass:execute(insert_unset_bind, ?BIND_BY_NAME, [{<<"key">>, 1}]), + ok = erlcass:execute(insert_unset_bind, ?BIND_BY_INDEX, [2, undefined, undefined]), + ok = erlcass:execute(insert_unset_bind_2, [3, null, null]), + + {ok, _, [[1, null, null]]} = erlcass:execute(select_unset_bind, [1]), + {ok, _, [[2, null, null]]} = erlcass:execute(select_unset_bind, [2]), + {ok, _, [[3, null, null]]} = erlcass:execute(select_unset_bind, [3]). + drop_keyspace(_Config) -> ok = erlcass:query(<<"DROP KEYSPACE erlang_driver_test">>).