|
|
@@ -337,6 +337,47 @@ bool MigrationRunner::applyOperation(const nlohmann::json& operation) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ if (type == "delete_where_id_prefix") {
|
|
|
+ std::string collection = operation.value("collection", "");
|
|
|
+ std::string id_prefix = operation.value("id_prefix", "");
|
|
|
+
|
|
|
+ if (collection.empty() || id_prefix.empty()) {
|
|
|
+ spdlog::error("delete_where_id_prefix: missing collection or id_prefix");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Collect matching ids first so we don't mutate the collection
|
|
|
+ // mid-iteration. Use a generous limit — migrations should never
|
|
|
+ // silently truncate.
|
|
|
+ Query query;
|
|
|
+ query.limit = 1'000'000;
|
|
|
+ auto result = store_.find(collection, query);
|
|
|
+
|
|
|
+ std::vector<std::string> to_delete;
|
|
|
+ to_delete.reserve(result.documents.size());
|
|
|
+ for (const auto& doc : result.documents) {
|
|
|
+ if (doc.id.rfind(id_prefix, 0) == 0) {
|
|
|
+ to_delete.push_back(doc.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int removed = 0;
|
|
|
+ for (const auto& id : to_delete) {
|
|
|
+ try {
|
|
|
+ if (store_.remove(collection, id)) {
|
|
|
+ ++removed;
|
|
|
+ }
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::warn("delete_where_id_prefix: failed to remove '{}' from '{}': {}",
|
|
|
+ id, collection, e.what());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ spdlog::info("delete_where_id_prefix: removed {} docs from '{}' with prefix '{}'",
|
|
|
+ removed, collection, id_prefix);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
if (type == "alter_collection") {
|
|
|
std::string collection = operation.value("collection", "");
|
|
|
if (collection.empty()) {
|