|
@@ -1009,6 +1009,40 @@ void test_indexed_and_unindexed_plans_agree() {
|
|
|
"the unselective index does exist and holds 150 of 300 rows");
|
|
"the unselective index does exist and holds 150 of 300 rows");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// An empty document id is a zero-length LMDB key, which mdb_get rejects with
|
|
|
|
|
+// MDB_BAD_VALSIZE. That surfaced in production as a gRPC INTERNAL and an ERROR
|
|
|
|
|
+// log line every time a consumer asked for one - noise that masks real failures.
|
|
|
|
|
+// A key that cannot exist is absent, not an error.
|
|
|
|
|
+void test_empty_id_reads_as_absent() {
|
|
|
|
|
+ TmpEnv t("empty-id");
|
|
|
|
|
+ LmdbDocumentStore store(t.env);
|
|
|
|
|
+
|
|
|
|
|
+ Document d;
|
|
|
|
|
+ d.id = "real";
|
|
|
|
|
+ d.collection = "c";
|
|
|
|
|
+ d.set_data(nlohmann::json{{"x", 1}});
|
|
|
|
|
+ store.put("c", "real", d);
|
|
|
|
|
+
|
|
|
|
|
+ bool threw = false;
|
|
|
|
|
+ try {
|
|
|
|
|
+ check(!store.get("c", "").has_value(), "an empty id reads as absent");
|
|
|
|
|
+ } catch (const std::exception&) {
|
|
|
|
|
+ threw = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ check(!threw, "and does NOT throw - MDB_BAD_VALSIZE became a gRPC INTERNAL");
|
|
|
|
|
+
|
|
|
|
|
+ threw = false;
|
|
|
|
|
+ try {
|
|
|
|
|
+ check(!store.del("c", ""), "deleting an empty id is a no-op");
|
|
|
|
|
+ } catch (const std::exception&) {
|
|
|
|
|
+ threw = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ check(!threw, "and does not throw either");
|
|
|
|
|
+
|
|
|
|
|
+ check(store.get("c", "real").has_value(), "real ids still work");
|
|
|
|
|
+ check(store.count("c") == 1, "and nothing was disturbed");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
} // namespace
|
|
} // namespace
|
|
|
|
|
|
|
|
int main() {
|
|
int main() {
|
|
@@ -1029,6 +1063,7 @@ int main() {
|
|
|
test_build_index_over_existing_rows();
|
|
test_build_index_over_existing_rows();
|
|
|
test_index_numeric_equality_matches_scan();
|
|
test_index_numeric_equality_matches_scan();
|
|
|
test_indexed_and_unindexed_plans_agree();
|
|
test_indexed_and_unindexed_plans_agree();
|
|
|
|
|
+ test_empty_id_reads_as_absent();
|
|
|
|
|
|
|
|
std::cout << "passed: " << g_pass << ", failed: " << g_fail << "\n";
|
|
std::cout << "passed: " << g_pass << ", failed: " << g_fail << "\n";
|
|
|
return g_fail == 0 ? 0 : 1;
|
|
return g_fail == 0 ? 0 : 1;
|