-
Notifications
You must be signed in to change notification settings - Fork 998
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
feat: Add GEOSEARCH support #2070
Conversation
Signed-off-by: azuredream <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for your contribution!
Please see some comments below, also feel free to point out anything I might have missed
src/server/zset_family.cc
Outdated
@@ -48,6 +54,20 @@ using MScoreResponse = std::vector<std::optional<double>>; | |||
using ScoredMember = std::pair<std::string, double>; | |||
using ScoredArray = std::vector<ScoredMember>; | |||
|
|||
typedef struct GeoPoint { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typedef struct GeoPoint { | |
struct GeoPoint { |
This is c-syntax, we're in a C++ file :)
src/server/zset_family.cc
Outdated
const std::string& _member) | ||
: longitude(_longitude), latitude(_latitude), dist(_dist), score(_score), member(_member){}; | ||
|
||
} GeoPoint; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} GeoPoint; | |
}; |
src/server/zset_family.cc
Outdated
double dist; | ||
double score; | ||
std::string member; | ||
GeoPoint() = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will not zero the double
members of the struct.
I suggest removing both constructors, and add default initializers (e.g. double logitude = 0.0;
).
As for the c'tor accepting arguments, one could initialize the struct with GeoPoint{a, b, c, ...}
or even better GeoPoint{.logitude=a, .latitude=b, ...}
src/server/zset_family.cc
Outdated
@@ -2599,6 +2636,296 @@ void ZSetFamily::GeoDist(CmdArgList args, ConnectionContext* cntx) { | |||
distance_multiplier); | |||
} | |||
|
|||
// Search all eight neighbors + self geohash box | |||
int membersOfAllNeighbors(ConnectionContext* cntx, string_view key, const GeoHashRadius* n, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please:
- Capitalize the first character (
MembersOfAllNeighbors
) - Add anonymous namespace wrapper around this function for internal linkage (i.e. add
namespace {
before and} // namespace
after
src/server/zset_family.cc
Outdated
bool withhash = false; | ||
|
||
// parse arguments | ||
size_t i = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move i
to be defined as part of the loop?
src/server/zset_family.cc
Outdated
} | ||
} else if (cur_arg == "COUNT") { | ||
if (i + 1 < args.size()) { | ||
// TODO: need a string_view impl of stoull |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
absl::SimpleAtoi()
src/server/zset_family.cc
Outdated
withhash = true; | ||
else { | ||
return (*cntx)->SendError(kSyntaxErr); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for break
after return
:)
src/server/zset_family.cc
Outdated
auto asc_comparator = [](const GeoPoint& a, const GeoPoint& b) { return a.dist < b.dist; }; | ||
auto desc_comparator = [](const GeoPoint& a, const GeoPoint& b) { return a.dist > b.dist; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you reduce the scope of asc_comparator
and desc_comparator
to the smallest possible?
Hi @chakaz. Thanks a lot for your review. I haven't clean it up when I submitted it as draft. Your insights saved me a lot of time. I'll accept most of your comments but have question about two: using neighbor vector, and const ref |
Signed-off-by: azuredream <[email protected]>
Gladly, thank you for your contribution! |
If accepting |
Signed-off-by: azuredream <[email protected]>
@@ -48,6 +48,8 @@ class ZSetFamily { | |||
struct ZRangeSpec { | |||
std::variant<IndexInterval, ScoreInterval, LexInterval, TopNScored> interval; | |||
RangeParams params; | |||
ZRangeSpec() = default; | |||
ZRangeSpec(const ScoreInterval& si, const RangeParams& rp) : interval(si), params(rp){}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Required by emplace_back().
Thanks for your advice. const_cast sounds good. To-do list for future developments:
Once this PR is merged, implementing #1548 and #1878 should be straightforward. Please feel free to assign them to me if you need. |
Signed-off-by: azuredream <[email protected]>
Signed-off-by: azuredream <[email protected]>
src/server/zset_family.cc
Outdated
double longitude; | ||
double latitude; | ||
double dist; | ||
double score; | ||
std::string member; | ||
GeoPoint() = default; | ||
GeoPoint() : longitude(0.0), latitude(0.0), dist(0.0), score(0.0), member(""){}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nits:
- It's slightly better to use initializers when defining the members (like
double logitude = 0.0;
) instead of here. The reason is that future c'tors will work with 0s by default, unless modified - No need to initialize
member
as it's a class, its c'tor will be called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed member("")
. Set it just for consistency.
A default constructor is required by ga.resize(), I can't remove GeoPoint()
:
../src/server/zset_family.cc:2739:21: required from here
/usr/include/c++/9/bits/stl_construct.h:75:7: error: no matching function for call to ‘dfly::{anonymous}::GeoPoint::GeoPoint()’
src/server/zset_family.cc
Outdated
// Search all eight neighbors + self geohash box | ||
int MembersOfAllNeighbors(ConnectionContext* cntx, string_view key, const GeoHashRadius& n, | ||
const GeoShape& shape_ref, GeoArray* ga, unsigned long limit) { | ||
vector<GeoHashBits> neighbors(9); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because you know the size in advance, you could use std::array<GeoHashBits, 9>
instead of a vector
. It would save a dynamic memory allocation
src/server/zset_family.cc
Outdated
GeoShape* shape = &(const_cast<GeoShape&>(shape_ref)); | ||
GeoHashRadius georadius = geohashCalculateAreasByShapeWGS84(shape); | ||
GeoArray ga; | ||
MembersOfAllNeighbors(cntx, key, georadius, shape_ref, &ga, any ? count : 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function will reply to the client in case of error, so you have to check its return value here. We can't reply twice.
Further, it seems like you only case about the error case of the function, so no need to calculate and return count
there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modified MembersOfAllNeighbors
to return a bool value. It would return false if already replied inside.
|
||
// if no matching results, the user gets an empty reply. | ||
if (ga.empty()) { | ||
(*cntx)->SendNull(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return
after replying
src/server/zset_family.cc
Outdated
double conversion = geo_ops.conversion; | ||
uint64_t count = geo_ops.count; | ||
bool asc = geo_ops.asc; | ||
bool desc = geo_ops.desc; | ||
bool any = geo_ops.any; | ||
bool withdist = geo_ops.withdist; | ||
bool withcoord = geo_ops.withcoord; | ||
bool withhash = geo_ops.withhash; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd remove all of these variables, they don't really help but make this function more bloat. You could just use geo_ops.*
instead.
For example, conversion
is only used once (with a fairly short line)
src/server/zset_family.cc
Outdated
bool asc = 0; | ||
bool desc = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add an enum like:
enum class Sorting { kUnsorted, kAsc, kDesc };
And replace these members with Sorting sorting;
?
This would be easier to read than a bunch of booleans, and also will only represent valid cases (with this code - both could theoretically be true, which doesn't make sense)
src/server/zset_family.cc
Outdated
if (count > 0) { | ||
std::partial_sort(ga.begin(), ga.begin() + count, ga.end(), asc_comparator); | ||
ga.resize(count); | ||
} else { | ||
std::sort(ga.begin(), ga.end(), asc_comparator); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you create a function (in the anonymous namespace) with this, which accepts a comparator (probably of type absl::FunctionRef<bool(const GeoPoint&, const GeoPoint&)>
)?
This way you both shorten this function, and remove code copy-paste
An alternative would be to create a function which will take the above-mentioned enum
and sort internally. Something like:
void SortIfNeeded(GeoArray* ga, Sorting sorting) {
if (sorting == kUnsorted)
return;
auto comparator = [&](const GeoPoint& a, const GeoPoint& b) {
if (sorting == Sorting::kAsc) {
return a.dist < b.dist;
} else {
DCHECK(sorting == Sorting::kDesc);
return a.dist > b.dist;
}
};
if (count > 0) {
std::partial_sort(ga.begin(), ga.begin() + count, ga.end(), comparator);
ga.resize(count);
} else {
std::sort(ga.begin(), ga.end(), comparator);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created SortIfNeeded
as you suggested:)
Signed-off-by: azuredream <[email protected]>
PR updated. Thanks again for your review and suggestions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woohoo, thank you again for your contribution!
(and for withstanding my comments :)
thank you @azuredream ! |
FULL SUPPORT? |
#1879