CXX-727 Make it possible to rename collections #367
Conversation
The method was unimplemented and renaming databases is currently not something mongodb provides for it's drivers. See https://jira.mongodb.org/browse/SERVER-701
/// @param overwrite_old Whether to overwrite any | ||
/// existing collections called new_name. Default: false | ||
/// @throws exception::operation if the operation fails. | ||
void name(stdx::string_view new_name, bool overwrite_old = false); |
Machyne
Nov 4, 2015
Contributor
Can you rename overwrite_old
to drop_target_before_rename
to match the C driver?
Can you rename overwrite_old
to drop_target_before_rename
to match the C driver?
Machyne
Nov 4, 2015
Contributor
Calling this rename
would be more clear.
Calling this rename
would be more clear.
koraa
Nov 4, 2015
Author
Contributor
sure, I will change both.
drop_target_before_rename
hmm. IMO overwrite_old is much more clear.
rename
Sure about that? I deliberately elected not to call it rename, because this implements a getter/setter pattern. If mongocxx generally uses getters/setters I think it would be more idiomatic to consider the name a gettable and settable property too.
sure, I will change both.
drop_target_before_rename
hmm. IMO overwrite_old is much more clear.
rename
Sure about that? I deliberately elected not to call it rename, because this implements a getter/setter pattern. If mongocxx generally uses getters/setters I think it would be more idiomatic to consider the name a gettable and settable property too.
/// | ||
/// @param new_name The new name to assign to the collection. | ||
/// @param overwrite_old Whether to overwrite any | ||
/// existing collections called new_name. Default: false |
Machyne
Nov 4, 2015
Contributor
Add @see https://docs.mongodb.org/manual/reference/command/renameCollection/
Add @see https://docs.mongodb.org/manual/reference/command/renameCollection/
Machyne
Nov 4, 2015
Contributor
Also, please change the last line to "The default is false." for consistency.
Also, please change the last line to "The default is false." for consistency.
bson_error_t error; | ||
|
||
_impl->name = (std::string)new_name; | ||
auto result = mongoc_collection_rename( |
Machyne
Nov 4, 2015
Contributor
Please run clang-format on this to follow our style format.
Please run clang-format on this to follow our style format.
/// @param new_name the new name for the database. | ||
/// @param drop_target_before_rename whether to drop existing databases with the new name. | ||
/// | ||
void rename(stdx::string_view new_name, bool drop_target_before_rename); |
Machyne
Nov 4, 2015
Contributor
Good catch, thanks!
Good catch, thanks!
Thanks for the PR and the work! :) I'll be happy to merge this in after you fix some small comments. |
void collection::name(stdx::string_view new_name, bool overwrite_old) { | ||
bson_error_t error; | ||
|
||
_impl->name = (std::string)new_name; |
amidvidy
Nov 4, 2015
Contributor
move this line to line 83 in case the rename fails. Also, please just write _impl->name = new_name
. The cast should not be needed as string_view defines a conversion operator to std::string.
move this line to line 83 in case the rename fails. Also, please just write _impl->name = new_name
. The cast should not be needed as string_view defines a conversion operator to std::string.
Machyne
Nov 4, 2015
Contributor
Edit: for my original comment I was confused and looking at mongoc_collection_t instead of the impl class, follow @amidvidy 's comment
Edit: for my original comment I was confused and looking at mongoc_collection_t instead of the impl class, follow @amidvidy 's comment
koraa
Nov 4, 2015
Author
Contributor
@amidvidy I initially did not use the cast, but it failed. Further investigation revealed that the string conversion operator in mnmlstc is marked explicit.
@amidvidy I initially did not use the cast, but it failed. Further investigation revealed that the string conversion operator in mnmlstc is marked explicit.
amidvidy
Nov 4, 2015
Contributor
understood. In that case, please use static_cast, we try to avoid C-style casts.
understood. In that case, please use static_cast, we try to avoid C-style casts.
Can you add a unit test to collection_mocked showing that _impl->name changes to the new name if the call to the C-driver succeeds, and that _impl->name does not change if the call to the C-driver fails? @Machyne can help you out with the mocking framework. Thanks again for the PR. |
auto result = mongoc_collection_rename( | ||
_impl->collection_t | ||
, _impl->database_name.c_str() | ||
, _impl->name.c_str() |
Machyne
Nov 4, 2015
Contributor
This should just be new_name.data()
. :)
This should just be new_name.data()
. :)
koraa
Nov 4, 2015
Author
Contributor
Sure about that?
https://mnmlstc.github.io/core/string.html#basic_string_view:T:::dataCCE
"This pointer is not guaranteed to be null terminated, and should be treated as such."
Sure about that?
https://mnmlstc.github.io/core/string.html#basic_string_view:T:::dataCCE
"This pointer is not guaranteed to be null terminated, and should be treated as such."
Machyne
Nov 4, 2015
Contributor
Interesting... I suggested it because it is used elsewhere (ex: collection constructor).
c_str
is probably safer though.
Interesting... I suggested it because it is used elsewhere (ex: collection constructor).
c_str
is probably safer though.
@amidvidy Is there any particular reason why collection::name() stores the name internally rather than returning a string view on mongoc_collection_get_name()? If we altered name() to use that we could free ourselves from the added state synchronization and also save on the added test. |
fdc00c4
to
d7bd777
good idea @koraa, I do not see why it is needed (some of this code predates me). I think that would be a good change to make. If you do that, we can remove the name member from collection::impl as well. |
perfect... |
void collection::rename(stdx::string_view new_name, bool drop_target_before_rename) { | ||
bson_error_t error; | ||
|
||
auto result = mongoc_collection_rename(_impl->collection_t, _impl->database_name.c_str(), |
samantharitter
Nov 4, 2015
Collaborator
Hi @koraa , please also add this symbol to the list in src/mongocxx/private/libmongoc_symbols.hpp
and then call libmongoc::collection_rename
instead of calling mongoc_collection_rename
directly.
Hi @koraa , please also add this symbol to the list in src/mongocxx/private/libmongoc_symbols.hpp
and then call libmongoc::collection_rename
instead of calling mongoc_collection_rename
directly.
/// @param drop_target_before_rename Whether to overwrite any | ||
/// existing collections called new_name. The default is false. | ||
/// @throws exception::operation if the operation fails. | ||
void name(stdx::string_view new_name, bool drop_target_before_rename = false); |
samantharitter
Nov 4, 2015
Collaborator
Just some style things, please change your doc comment indentation so it reads like:
/// @param new_name
/// The new name to assign to the collection.
Also, please add an empty ///
line between the @param
's and the @throws
, and one at the end before the function declaration.
Just some style things, please change your doc comment indentation so it reads like:
/// @param new_name
/// The new name to assign to the collection.
Also, please add an empty ///
line between the @param
's and the @throws
, and one at the end before the function declaration.
Thanks for your work @koraa ! On the name() vs. rename() debate, I prefer rename(). Yes, we do follow a getter/setter pattern in much of the codebase, but most of those are for attributes that exist in driver classes for convenience. This is a bit different because it translates directly to a database operation, and since that's called renameCollection() we should use rename() here. Storing the name and returning it with name() actually makes some sense. You can't get a valid collection object without knowing its name, and since we have it we can avoid network round trips to the database if we just cache the name. With renaming, we'll have to update the cached value. |
Well, then. It's got tests now (let's see it whether the CI successfully runs them because locally they did not work in the first place). I am also using sring_view::data() now to get the c_string, but strictly speaking this is not correct: the string returned by data() is not guaranteed to be a correctly zero-terminated string. It frequently contains one, when it represents a full std::string or a full c string, but in cases where string_view is most useful – when it references part of a bigger underlying buffer – it wont be. We could try to solve this by creating a helper function that checks whether string_view::end() references a \0 byte, but this optimization is usually not applicable, because we don't know anything about the character referenced by end(); it could be valid, but it also could be part of a buffer used and written to by another thread or it could be unallocated memory. See https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/LEPhIf_msXw Optimally you would get mongoc/bsonc to support string parameters with sizes. |
@@ -466,4 +466,24 @@ TEST_CASE("Collection", "[collection]") { | |||
REQUIRE(bulk_operation_execute_called); | |||
REQUIRE(bulk_operation_destroy_called); | |||
} | |||
|
|||
SECTION("Rename Fails, name stays the same", "collection::rename") { |
Machyne
Nov 4, 2015
Contributor
Can you run clang-format on this too?
Can you run clang-format on this too?
|
||
SECTION("Rename Fails, name stays the same", "collection::rename") { | ||
bool collection_rename_called = false, collection_rename_error = false; | ||
std::string old_name = (std::string)mongo_coll.name(); |
Machyne
Nov 4, 2015
Contributor
Use std::string old_name{mongo_coll.name()};
instead? That would also avoid the c style cast.
Use std::string old_name{mongo_coll.name()};
instead? That would also avoid the c style cast.
|
||
collection_rename->interpose([&](mongoc_collection_t *, const char*, const char*, bool, bson_error_t*) { | ||
collection_rename_called = true; | ||
return false; |
Machyne
Nov 4, 2015
Contributor
verify here that the expected name (in this case "bad_name") is getting passed in
verify here that the expected name (in this case "bad_name") is getting passed in
Thanks for adding the test! I agree with your comment on |
@Machyne Actually I think the correct way to address this is to redesign the API to take strings OR c strings (with the string implementation just extracting the c string). This way copying is only necessary in rare cases and explicit. Then you should get mongoc to take strings with lengths and then go back to using string_views. If you just copy unconditionally, you will create lot's of unnecessary overhead. |
@Machyne As it turns out, C++14 is totally awesome, but I believe that syntax inherited from C and C++03 still have very valid applications in modern C++14 code for their specific use cases. Don't discourage a syntax just because it seems old :) |
The reason we discourage C-style casts in the driver is that C++ provides its own casts, and these are safer as they allow the compiler to do more checking. They also require the programmer to be much more deliberate about the cast and are much easier to search for in code. However because this isn't a performance-critical path (renaming collections often is kind of an anti-pattern) I think the best solution here is for us to make the extra copy and build a string from the We don't really want to provide an interface that takes Thank you for finding this, we'll have to do an audit of other |
@samantharitter I am well aware of the c++ casts, but this has nothing to do with runtime polymorphism. This is explicitly a call to the string view's explicit-marked cast operator. |
By the way, I just looked it up and libmongoc actually does not query the database in order to retrieve the database name; it is already caching the database name in an internal structure, which kind of makes sense since mongoc_collection_get_name() needs to return a persistent pointer. https://github.com/mongodb/mongo-c-driver/blob/master/src/mongoc/mongoc-collection-private.h#L33 |
1554f42
to
af3d0d7
Hi @koraa, sorry for the delay in getting back to you. Thanks for looking into the libmongoc code, I should not have assumed that it was running a db operation there. I wasn't aware that string_data had an operator basic_string, that's very handy. However, since that conversion is still going to copy the underlying string, let's either put a comment explaining the intention there above the call, or use to_string(), which will have the same effect but is in my opinion clearer. |
@@ -44,6 +44,8 @@ MONGOCXX_LIBMONGOC_SYMBOL(client_pool_try_pop) | |||
MONGOCXX_LIBMONGOC_SYMBOL(client_set_read_prefs) | |||
MONGOCXX_LIBMONGOC_SYMBOL(client_set_ssl_opts) | |||
MONGOCXX_LIBMONGOC_SYMBOL(client_set_write_concern) | |||
MONGOCXX_LIBMONGOC_SYMBOL(collection_get_name) |
samantharitter
Nov 9, 2015
Collaborator
Please sort these new entries into alphabetical order with the other collection_* symbols
Please sort these new entries into alphabetical order with the other collection_* symbols
koraa
Nov 9, 2015
Author
Contributor
(oh, right. Thought I'd done that)
(oh, right. Thought I'd done that)
auto collection_aggregate = libmongoc::collection_aggregate.create_instance(); \ | ||
auto collection_get_name = libmongoc::collection_get_name.create_instance(); \ | ||
collection_get_name->interpose([](mongoc_collection_t*){ return "dummy_collection"; }); \ | ||
auto collection_rename = libmongoc::collection_rename.create_instance(); |
samantharitter
Nov 9, 2015
Collaborator
I see that you've added new mocked helpers to test this feature, did you have a chance to write some specific test cases for the operation? I'd suggest interposing a method on rename() to test it mocked, and I think we should also add a test case for rename() to our integration tests, in src/mongocxx/test/collection.cpp
.
I see that you've added new mocked helpers to test this feature, did you have a chance to write some specific test cases for the operation? I'd suggest interposing a method on rename() to test it mocked, and I think we should also add a test case for rename() to our integration tests, in src/mongocxx/test/collection.cpp
.
koraa
Nov 9, 2015
Author
Contributor
No, the old test code relied on name() to work; since it now relies on a mongoc method I had to mock that.
Personally I don't think we can meaningfully test for rename(); we can check whether the basic interface was used, but the resulting code would be more of a liability than safety.
If we want to test rename() we should do it against a real db.
No, the old test code relied on name() to work; since it now relies on a mongoc method I had to mock that.
Personally I don't think we can meaningfully test for rename(); we can check whether the basic interface was used, but the resulting code would be more of a liability than safety.
If we want to test rename() we should do it against a real db.
samantharitter
Nov 9, 2015
Collaborator
Right, that's what the integration test in src/mongocxx/test/collection.cpp
would be for, running the operation against a db. I think there's still validity to testing both the interface and the end-to-end behavior of the call.
Right, that's what the integration test in src/mongocxx/test/collection.cpp
would be for, running the operation against a db. I think there's still validity to testing both the interface and the end-to-end behavior of the call.
koraa
Nov 9, 2015
Author
Contributor
That would be good indeed. But at this point I am afraid my boss told me not to use too much time on this PR any more.
That would be good indeed. But at this point I am afraid my boss told me not to use too much time on this PR any more.
Hi @samantharitter, Neither did I copy the string_view to fix the problem with the lack of a zero terminator since I thought you'd have some internal discussion about how you're going to deal with that. |
I saw that you'd gone back to using .data(), that's totally fine. Under the umbrella of CXX-730 we're going to go through and fix our uses of string_view, we can address this use there instead. |
@@ -38,8 +38,7 @@ class collection::impl { | |||
const class client::impl* client, stdx::string_view name) |
Machyne
Nov 9, 2015
Contributor
Since name
is no longer used here, let's remove it. I think the only place we call this constructor is collection.cpp line 83.
Since name
is no longer used here, let's remove it. I think the only place we call this constructor is collection.cpp line 83.
One small comment, otherwise LGTM! Thanks again for the PR :) |
Previous to this we have been caching the name of a collection, but this is not necessary since the C api already does the same.
Until now we had support for getting a collection's name by using the getter collection::name(...); this overloads this method with an additional name setter.
No description provided.