Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Landmark Routing 1 PR: make landmark db operations a pimpl #4202

Merged
merged 4 commits into from
Jul 15, 2023

Conversation

kevinkreiser
Copy link
Member

@kevinkreiser kevinkreiser commented Jul 14, 2023

The pimpl idiom is a powerful one especially for api design. It allows the designer to offer up the api interface without exposing the details of how its implemented, effectively giving the api a PrivateIMPLementation.

I think this best suits pretty much all of our c++ apis but sadly not all of them adhere to this. At any rate this is an attempt to refactor the recent work in #4189 to model this.

In addition to the pimpl I cut down logging as much as possible, I only close the db in the destructor and instead of returning booleans I throw when there is a problem that is unrecoverable

By and large the code is pretty much the same just leaner and meaner

@kevinkreiser kevinkreiser requested a review from nilsnolde July 14, 2023 13:27
@@ -15,6 +15,7 @@
* CHANGED: write traffic tile headers in `valhalla_build_extract` [#4195](https://github.com/valhalla/valhalla/pull/4195)
* ADDED: `source_percent_along` & `target_percent_along` to /trace_attributes JSON response [#4199](https://github.com/valhalla/valhalla/pull/4199)
* ADDED: sqlite database to store landmarks along with interfaces of insert and bounding box queries [#4189](https://github.com/valhalla/valhalla/pull/4189)
* CHANGED: refactor landmark database interface to use a pimpl [#4202](https://github.com/valhalla/valhalla/pull/4202)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vesperlou the diff for the source files below is large so its hard to read. i would suggest just looking at the files directly (in the 3 dots on the side you can click "view file")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will take a look

Comment on lines +19 to 21
LandmarkDatabase(const std::string& db_name, bool read_only);
void insert_landmark(const Landmark& landmark);
std::vector<Landmark> get_landmarks_in_bounding_box(const double minLat,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the interface becomes just the functions that we want to publicly expose. the only thing the outside caller can see that is protected is a pimpl which gives no clue about its implementation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. I have studied C++ PImpl. nice job, thanks!

// TODO: this can be a utility and be more generic with a few more options, we could make the prepared
// statements on the fly and retrievable by the caller, then anything in the code base that wants to
// use sqlite can make use of this utility class. for now its ok to be specific to landmarks though
struct LandmarkDatabase::db_pimpl {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this class could get more generic and let users of it register prepared statements, retrieve them and run them. then we could use this class for any sqlite needs. for now i didnt go that far but if we do then this can go to a private header

std::shared_ptr<void> spatial_lite;
bool vacuum_analyze = false;

db_pimpl(const std::string& db_name, bool read_only) : insert_stmt(nullptr) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the constructor does all the initialization stuff directly rather than having myriad functions. if anything goes wrong it throws. if something upstream wants to catch what is thrown and log it fine. i would expect the graph enhancer for example to catch, log that i couldnt open the landmarks database and that its skipping adding landmarks to the tiles. for the writer part i would just let the throw abort the process (ie dont catch)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense.

@@ -164,7 +148,7 @@ std::vector<Landmark> LandmarkDatabase::get_landmarks_in_bounding_box(const doub
sqlite3_bind_double(bounding_box_stmt, 3, maxLong);
sqlite3_bind_double(bounding_box_stmt, 4, maxLat);

LOG_INFO(sqlite3_expanded_sql(bounding_box_stmt));
LOG_TRACE(sqlite3_expanded_sql(bounding_box_stmt));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i pretty much removed all logs except:

  1. the destructor logs when vacuum analyze fails
  2. insert/select log trace now because anything else is too verbose

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the same.

@kevinkreiser kevinkreiser requested a review from vesperlou July 14, 2023 13:48
@kevinkreiser
Copy link
Member Author

@vesperlou @nilsnolde do either of you have time to look this pr over?

Comment on lines +19 to 21
LandmarkDatabase(const std::string& db_name, bool read_only);
void insert_landmark(const Landmark& landmark);
std::vector<Landmark> get_landmarks_in_bounding_box(const double minLat,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. I have studied C++ PImpl. nice job, thanks!

Comment on lines +3 to +5
#include <memory>
#include <string>
#include <vector>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only include the necessary, basic standard libraries to avoid unused imports?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you look below these are the only things the interface makes use of, IWYU

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! got it

std::shared_ptr<void> spatial_lite;
bool vacuum_analyze = false;

db_pimpl(const std::string& db_name, bool read_only) : insert_stmt(nullptr) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense.

@@ -164,7 +148,7 @@ std::vector<Landmark> LandmarkDatabase::get_landmarks_in_bounding_box(const doub
sqlite3_bind_double(bounding_box_stmt, 3, maxLong);
sqlite3_bind_double(bounding_box_stmt, 4, maxLat);

LOG_INFO(sqlite3_expanded_sql(bounding_box_stmt));
LOG_TRACE(sqlite3_expanded_sql(bounding_box_stmt));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the same.

Comment on lines +164 to +165
throw std::runtime_error("Sqlite could not query landmarks in bounding box: " +
pimpl->last_error());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before throw an exception we don't need to close database connection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, the connection will be closed when the object is destructed no need to close it ahead of time.

Copy link
Contributor

@vesperlou vesperlou Jul 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we need to finalize statement before destructing the object?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep the finalize statement is in the destructor above before the call to close the connection

@vesperlou
Copy link
Contributor

@vesperlou @nilsnolde do either of you have time to look this pr over?

@kevinkreiser I reviewed yesterday but didn't know I should submit it ooops ;) left 2 question marks above.

@kevinkreiser
Copy link
Member Author

but didn't know I should submit it ooops

yeah that happens all the time, its a stupid UI design on githubs part

@kevinkreiser
Copy link
Member Author

kevinkreiser commented Jul 15, 2023

@vesperlou to finalize the review you can either select "approve" or "request changes" on the diff tab in the upper right hand side.

Copy link
Member

@nilsnolde nilsnolde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks

@vesperlou
Copy link
Contributor

@vesperlou to finalize the review you can either select "approve" or "request changes" on the diff tab in the upper right hand side.

thanks! approved :)

sqlite3_free(err_msg);
sqlite3_close(db);
return false;
sqlite3_finalize(insert_stmt);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you can see we finalize the statements and then close the connection

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. I am actually wondering why we should explicitly finalize statements in destructor, because I thought they are going to be cleaned automatically when the object is destroyed, like before throwing we dont have to close the connection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like the db connection the prepared statements are pointing to some memory that was dynamically allocated. in order for us to not "leak" memory we do need to manually deallocate that memory. when we throw the call immediately bubbles that exception upwards however the rules about what happens to objects when their life time ends still apply. that means if the outside scope that caused the exception was the scope that owned the LandmarkDatabase object lets that object go out of scope, its destructor (here) will be called and make sure to clean up the connection and the prepared statements.

if you mean closing the connection should finalize any statements, i dont think so. prepared statemnts are not tied to any connection they are a wholly separate construct. from the manual:

The application must finalize every prepared statement in order to avoid resource leaks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I see. I mixed up some concepts earlier but now I understand everything.
thanks for the detailed explanation!

@kevinkreiser kevinkreiser merged commit d544de3 into master Jul 15, 2023
@kevinkreiser kevinkreiser deleted the kk_simplify_landmarks branch July 15, 2023 18:48
@vesperlou vesperlou changed the title make landmark db operations a pimpl Landmark Routing 1 PR: make landmark db operations a pimpl Sep 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants