Prechádzať zdrojové kódy

feat(indexes): unique indexes with 409 duplicate reporting

fszontagh 1 mesiac pred
rodič
commit
663bf72b7a
2 zmenil súbory, kde vykonal 53 pridanie a 0 odobranie
  1. 20 0
      src/handlers/indexes.cpp
  2. 33 0
      tests/test_api_integration.cpp

+ 20 - 0
src/handlers/indexes.cpp

@@ -25,6 +25,26 @@ void registerIndexRoutes(ApiServer& s) {
         if (!d->db.client().getCollectionInfo(qc))
             throw ApiError(ErrCode::NotFound, "not_found", "collection not found");
 
+        const bool unique = body.value("unique", false);
+        if (unique) {
+            auto r = d->db.client().createUniqueIndex(qc, field);
+            if (!r.success) {
+                if (!r.duplicateExamples.empty()) {
+                    nlohmann::json ex = nlohmann::json::array();
+                    for (const auto& id : r.duplicateExamples) ex.push_back(id);
+                    throw ApiError(ErrCode::Conflict, "duplicate_values",
+                                   "field '" + field + "' already holds duplicate values",
+                                   {{"duplicate_examples", ex}});
+                }
+                throw ApiError(ErrCode::Unavailable, "db_error",
+                               r.error.empty() ? "failed to create unique index" : r.error);
+            }
+            sendJson(res, 201, {{"field", field}, {"unique", true},
+                                {"rows_indexed", r.rowsIndexed},
+                                {"already_existed", r.alreadyExisted}});
+            return;
+        }
+
         // createIndex() is idempotent on the DB side but does not itself report
         // whether the index already existed, so check the current list first.
         bool alreadyExisted = false;

+ 33 - 0
tests/test_api_integration.cpp

@@ -928,3 +928,36 @@ TEST_F(ApiFixture, IndexCreateRequiresField) {
     auto bad = c.Post((base + "/idxv/indexes").c_str(), nlohmann::json{}.dump(), "application/json");
     ASSERT_TRUE(bad); EXPECT_EQ(bad->status, 422);
 }
+
+TEST_F(ApiFixture, UniqueIndexRejectsExistingDuplicates) {
+    auto c = admin();
+    std::string base = "/api/v1/projects/" + project_ + "/collections";
+    ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","uq"},{"kind","json"}}.dump(),
+                     "application/json")->status, 201);
+    std::string docs = base + "/uq/documents";
+    ASSERT_EQ(c.Post(docs.c_str(), nlohmann::json{{"sku","A1"}}.dump(), "application/json")->status, 201);
+    ASSERT_EQ(c.Post(docs.c_str(), nlohmann::json{{"sku","A1"}}.dump(), "application/json")->status, 201);
+
+    auto conflict = c.Post((base + "/uq/indexes").c_str(),
+                           nlohmann::json{{"field","sku"},{"unique",true}}.dump(), "application/json");
+    ASSERT_TRUE(conflict); EXPECT_EQ(conflict->status, 409);
+    auto body = nlohmann::json::parse(conflict->body);
+    EXPECT_EQ(body["error"]["code"], "duplicate_values");
+    ASSERT_TRUE(body["error"]["details"].contains("duplicate_examples"));
+    EXPECT_GE(body["error"]["details"]["duplicate_examples"].size(), 1u);
+}
+
+TEST_F(ApiFixture, UniqueIndexOnCleanFieldSucceeds) {
+    auto c = admin();
+    std::string base = "/api/v1/projects/" + project_ + "/collections";
+    ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","uq2"},{"kind","json"}}.dump(),
+                     "application/json")->status, 201);
+    std::string docs = base + "/uq2/documents";
+    ASSERT_EQ(c.Post(docs.c_str(), nlohmann::json{{"sku","B1"}}.dump(), "application/json")->status, 201);
+    ASSERT_EQ(c.Post(docs.c_str(), nlohmann::json{{"sku","B2"}}.dump(), "application/json")->status, 201);
+
+    auto made = c.Post((base + "/uq2/indexes").c_str(),
+                       nlohmann::json{{"field","sku"},{"unique",true}}.dump(), "application/json");
+    ASSERT_TRUE(made); EXPECT_EQ(made->status, 201);
+    EXPECT_TRUE(nlohmann::json::parse(made->body)["unique"].get<bool>());
+}