First steps towards PostgreSQL integration - #651
Conversation
Tests are yet to be implemented, so the code remains in an early stage, completely untested even for basic functionality
There was a problem hiding this comment.
🟡 Changes recommended
There are multiple confirmed correctness/build issues (schema DDL syntax error, incorrect SELECT result handling via affected_rows(), missing test source file in CMake, and credential logging risk) that must be fixed before it can be safely validated.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Introduces an initial PostgreSQL-backed implementation of the FaultStorage backend for ros2_medkit_fault_manager, wiring it into FaultManagerNode via a new storage_type=postgres option and a database_url parameter.
Changes:
- Add
PgFaultStorage(libpqxx-based) with schema initialization and implementations for fault/events, snapshots, near-misses, and rosbag retention APIs. - Extend
FaultManagerNodeto select PostgreSQL storage via parameters. - Update build/package dependencies to include PostgreSQL/libpqxx and add a placeholder GTest target for PostgreSQL storage.
File summaries
| File | Description |
|---|---|
| src/ros2_medkit_fault_manager/src/postgres_fault_storage.cpp | New PostgreSQL FaultStorage implementation and schema creation logic. |
| src/ros2_medkit_fault_manager/include/ros2_medkit_fault_manager/postgres_fault_storage.hpp | Public header for PgFaultStorage implementing the FaultStorage interface. |
| src/ros2_medkit_fault_manager/src/fault_manager_node.cpp | Adds database_url param and selects PostgreSQL storage when storage_type=postgres. |
| src/ros2_medkit_fault_manager/include/ros2_medkit_fault_manager/fault_manager_node.hpp | Stores new database_url_ member. |
| src/ros2_medkit_fault_manager/CMakeLists.txt | Adds PostgreSQL dependency linkage and a PostgreSQL test target (currently missing source). |
| src/ros2_medkit_fault_manager/package.xml | Declares libpqxx dependency. |
Review details
Suppressed comments (4)
src/ros2_medkit_fault_manager/src/postgres_fault_storage.cpp:547
get_fault()usesaffected_rows()on a SELECT result; this can incorrectly return nullopt even when a fault exists. Useres.empty()to test whether the query returned rows.
auto res = tx.exec_params(
"SELECT fault_code, severity, description, first_occurred_ns, last_occurred_ns, occurrence_count, status, "
"reporting_sources, last_passed_ns FROM faults WHERE fault_code = $1",
fault_code);
if (res.affected_rows() == 0) {
src/ros2_medkit_fault_manager/src/postgres_fault_storage.cpp:653
contains()usesaffected_rows()on a SELECT result; that can incorrectly reportfalseeven when the row exists. For SELECT queries, checkres.empty()instead.
auto res = tx.exec_params("SELECT 1 FROM faults WHERE fault_code = $1 LIMIT 1", fault_code);
tx.commit();
return res.affected_rows() > 0;
src/ros2_medkit_fault_manager/src/postgres_fault_storage.cpp:748
- The newest-capture query aliases the column as
max_capture_idbut then readsmax_capture, and also usesaffected_rows()on a SELECT. This can throw at runtime and/or disable trimming. Prefer COALESCE +res.empty()and read the correct alias.
auto res =
tx.exec_params("SELECT MAX(capture_id) AS max_capture_id FROM snapshots WHERE fault_code = $1", fault_code);
int64_t newest_capture = 0;
if (res.affected_rows() > 0) {
newest_capture = res[0]["max_capture"].as<int64_t>();
src/ros2_medkit_fault_manager/src/postgres_fault_storage.cpp:753
- The snapshot-trimming loop uses
count_res.affected_rows() == 0on aSELECT COUNT(*)query; for SELECTs this is not a valid emptiness check and can short-circuit trimming unexpectedly. Usecount_res.empty()(or just read the first row) instead.
auto count_res = tx.exec_params("SELECT COUNT(*) AS sz FROM snapshots WHERE fault_code = $1", fault_code);
if (count_res.affected_rows() == 0 || count_res[0]["sz"].as<size_t>() <= max_snapshots_per_fault_) {
break;
- Files reviewed: 6/6 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # PostgreSQL storage tests | ||
| medkit_add_gtest(test_postgres_storage test/test_postgres_storage.cpp) | ||
| target_link_libraries(test_postgres_storage fault_manager_lib) | ||
| medkit_target_dependencies(test_postgres_storage rclcpp ros2_medkit_msgs) | ||
|
|
| } | ||
|
|
||
| if (storage_type_ == "postgres") { | ||
| RCLCPP_INFO(get_logger(), "Using PostgreSQL fault storage: %s", database_url_.c_str()); |
| CREATE INDEX IF NOT EXISTS idx_snapshots_fault_code ON snapshots(fault_code); | ||
| CREATE INDEX IF NOT EXISTS idx_snapshots_fault_topic ON snapshots(fault_code, topic))"); |
| "first_occurred_ns FROM faults WHERE fault_code = $1", | ||
| fault_code); | ||
|
|
||
| if (res.affected_rows() > 0) { |
| auto res = tx.exec_params("SELECT COUNT(*) FROM rosbag_files WHERE file_path = $1", file_path); | ||
| tx.commit(); | ||
| return res.affected_rows(); |
| for (const auto & r : res) { | ||
| paths.insert(r["file_path"].as<std::string>()); | ||
| } | ||
| removed = res.affected_rows() > 0; |
|
Oh, wow, Copilot came in aggressively! I will consider the LLM's remarks as I am implementing the tests. |
No worries, he is always like that. Use your own judgement, some of his comments are not worth fixing. Happy to take a look. |
Pull Request
Summary
As discussed in #649, this is an early implementation of the PostgreSQL integration. Keep in mind that currently the code does not compile because the testing suite is not included in this PR.
Issue
Link the related issue (required):
Type
Testing
Tests are yet to be implemented, so the code remains in an early stage, completely untested even for basic functionality
Checklist
TODOs (based on your checklist, will tick the list as improvements come along)
As this is still a WIP, feel free to offer suggestions, recommendations or problems you might think of.