From 128f3c0eaa0354f5a6f02f20c9dca8c04b56b697 Mon Sep 17 00:00:00 2001 From: James Stone Date: Thu, 18 May 2023 19:53:06 -0700 Subject: [PATCH] Geospatial feedback (#6645) * verify local results match a server query * disallow geowithin on top level tables * fix geo queries with ANY/ALL/NONE * geospatial validation of points * rename GeoCenterSphere -> GeoCircle * review feedback * better testing and fix any/all/none geospatial * format --- CHANGELOG.md | 1 + src/realm/geospatial.cpp | 73 ++- src/realm/geospatial.hpp | 20 +- src/realm/parser/driver.cpp | 4 +- src/realm/parser/driver.hpp | 4 +- src/realm/parser/generated/query_bison.cpp | 8 +- src/realm/parser/generated/query_bison.hpp | 14 +- src/realm/parser/generated/query_flex.cpp | 528 ++++++++++----------- src/realm/parser/query_bison.yy | 4 +- src/realm/parser/query_flex.ll | 2 +- src/realm/query_expression.hpp | 73 ++- test/object-store/sync/flx_sync.cpp | 115 ++++- test/test_parser.cpp | 117 +++-- test/test_query_geo.cpp | 81 +++- 14 files changed, 663 insertions(+), 381 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd302a28dc..3e40b41d6d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * `platform` and `cpu_arch` fields in the `device_info` structure in App::Config can no longer be provided by the SDK's, they are inferred by the library ([PR #6612](https://github.com/realm/realm-core/pull/6612)) * `bundle_id` is now a required field in the `device_info` structure in App::Config ([PR #6612](https://github.com/realm/realm-core/pull/6612)) * The API for sectioned results change notifications has changed. Changes are now reported in a vector rather than a sparse map. +* Renamed `GeoCenterSphere` to `GeoCircle` and in RQL `geoSphere` to `geoCircle`. The GeoPoints of query shapes are now validated before use and an exception will be thrown if invalid. Geospatial queries are no longer allowed on top-level tables. Fixed query results using ANY/ALL/NONE and matching on lists ([PR #6645](https://github.com/realm/realm-core/issues/6645)) ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. diff --git a/src/realm/geospatial.cpp b/src/realm/geospatial.cpp index a737b51072e..f4d6206d66a 100644 --- a/src/realm/geospatial.cpp +++ b/src/realm/geospatial.cpp @@ -72,8 +72,8 @@ std::string Geospatial::get_type_string() const noexcept return "Box"; case Type::Polygon: return "Polygon"; - case Type::CenterSphere: - return "CenterSphere"; + case Type::Circle: + return "Circle"; case Type::Invalid: return "Invalid"; } @@ -254,8 +254,8 @@ std::string Geospatial::to_string() const } return util::format("GeoPolygon(%1)", points); }, - [](const GeoCenterSphere& sphere) { - return util::format("GeoSphere(%1, %2)", point_str(sphere.center), sphere.radius_radians); + [](const GeoCircle& circle) { + return util::format("GeoCircle(%1, %2)", point_str(circle.center), circle.radius_radians); }, [](const mpark::monostate&) { return std::string("NULL"); @@ -271,9 +271,30 @@ std::ostream& operator<<(std::ostream& ostr, const Geospatial& geo) // The following validation follows the server: // https://github.com/mongodb/mongo/blob/053ff9f355555cddddf3a476ffa9ddf899b1657d/src/mongo/db/geo/geoparser.cpp#L140 -static void erase_duplicate_points(std::vector* vertices) + + +// Technically lat/long bounds, not really tied to earth radius. +static bool is_valid_lat_lng(double lng, double lat) +{ + return abs(lng) <= 180 && abs(lat) <= 90; +} + +static Status coord_to_point(double lng, double lat, S2Point& out) +{ + if (!is_valid_lat_lng(lng, lat)) + return Status(ErrorCodes::InvalidQueryArg, + util::format("Longitude/latitude is out of bounds, lng: %1 lat: %2", lng, lat)); + // Note that it's (lat, lng) for S2 but (lng, lat) for MongoDB. + S2LatLng ll = S2LatLng::FromDegrees(lat, lng).Normalized(); + // This shouldn't happen since we should only have valid lng/lats. + REALM_ASSERT_EX(ll.is_valid(), util::format("coords invalid after normalization, lng = %1, lat = %2", lng, lat)); + out = ll.ToPoint(); + return Status::OK(); +} + +static void erase_duplicate_adjacent_points(std::vector& vertices) { - vertices->erase(std::unique(vertices->begin(), vertices->end()), vertices->end()); + vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end()); } static Status is_ring_closed(const std::vector& ring, const std::vector& points) @@ -293,6 +314,7 @@ static Status is_ring_closed(const std::vector& ring, const std::vector static Status parse_polygon_coordinates(const GeoPolygon& polygon, S2Polygon* out) { + REALM_ASSERT(out); std::vector rings; rings.reserve(polygon.points.size()); Status status = Status::OK(); @@ -311,15 +333,19 @@ static Status parse_polygon_coordinates(const GeoPolygon& polygon, S2Polygon* ou std::vector points; points.reserve(polygon.points[i].size()); for (auto&& p : polygon.points[i]) { - // FIXME rewrite without copying - points.push_back(S2LatLng::FromDegrees(p.latitude, p.longitude).ToPoint()); + S2Point s2p; + status = coord_to_point(p.longitude, p.latitude, s2p); + if (!status.is_ok()) { + return status; + } + points.push_back(s2p); } status = is_ring_closed(points, polygon.points[i]); if (!status.is_ok()) return status; - erase_duplicate_points(&points); + erase_duplicate_adjacent_points(points); // Drop the duplicated last point. points.resize(points.size() - 1); @@ -414,8 +440,19 @@ GeoRegion::GeoRegion(const Geospatial& geo) Status& m_status_out; std::unique_ptr operator()(const GeoBox& box) const { - return std::make_unique(S2LatLng::FromDegrees(box.lo.latitude, box.lo.longitude), - S2LatLng::FromDegrees(box.hi.latitude, box.hi.longitude)); + S2Point s2_lo, s2_hi; + m_status_out = coord_to_point(box.lo.longitude, box.lo.latitude, s2_lo); + if (!m_status_out.is_ok()) + return nullptr; + m_status_out = coord_to_point(box.hi.longitude, box.hi.latitude, s2_hi); + if (!m_status_out.is_ok()) + return nullptr; + auto ret = std::make_unique(S2LatLng(s2_lo), S2LatLng(s2_hi)); + if (!ret->is_valid()) { + m_status_out = Status(ErrorCodes::InvalidQueryArg, "Invalid rectangle"); + return nullptr; + } + return ret; } std::unique_ptr operator()(const GeoPolygon& polygon) const @@ -425,10 +462,18 @@ GeoRegion::GeoRegion(const Geospatial& geo) return poly; } - std::unique_ptr operator()(const GeoCenterSphere& sphere) const + std::unique_ptr operator()(const GeoCircle& circle) const { - auto center = S2LatLng::FromDegrees(sphere.center.latitude, sphere.center.longitude).ToPoint(); - auto radius = S1Angle::Radians(sphere.radius_radians); + S2Point center; + m_status_out = coord_to_point(circle.center.longitude, circle.center.latitude, center); + if (!m_status_out.is_ok()) + return nullptr; + if (circle.radius_radians < 0 || std::isnan(circle.radius_radians)) { + m_status_out = + Status(ErrorCodes::InvalidQueryArg, "The radius of a circle must be a non-negative number"); + return nullptr; + } + auto radius = S1Angle::Radians(circle.radius_radians); return std::make_unique(S2Cap::FromAxisAngle(center, radius)); } diff --git a/src/realm/geospatial.hpp b/src/realm/geospatial.hpp index a8f574c3590..a82d677d0ff 100644 --- a/src/realm/geospatial.hpp +++ b/src/realm/geospatial.hpp @@ -149,11 +149,11 @@ struct GeoPolygon { std::vector> points; }; -struct GeoCenterSphere { +struct GeoCircle { double radius_radians = 0.0; GeoPoint center; - bool operator==(const GeoCenterSphere& other) const + bool operator==(const GeoCircle& other) const { return radius_radians == other.radius_radians && center == other.center; } @@ -162,15 +162,15 @@ struct GeoCenterSphere { // src/mongo/db/geo/geoconstants.h constexpr static double c_radius_meters = 6378100.0; - static GeoCenterSphere from_kms(double km, GeoPoint&& p) + static GeoCircle from_kms(double km, GeoPoint&& p) { - return GeoCenterSphere{km * 1000 / c_radius_meters, p}; + return GeoCircle{km * 1000 / c_radius_meters, p}; } }; class Geospatial { public: - enum class Type : uint8_t { Invalid, Point, Box, Polygon, CenterSphere }; + enum class Type : uint8_t { Invalid, Point, Box, Polygon, Circle }; Geospatial() : m_value(mpark::monostate{}) @@ -188,8 +188,8 @@ class Geospatial { : m_value(polygon) { } - Geospatial(GeoCenterSphere centerSphere) - : m_value(centerSphere) + Geospatial(GeoCircle circle) + : m_value(circle) { } @@ -233,7 +233,7 @@ class Geospatial { private: // Must be in the same order as the Type enum - mpark::variant m_value; + mpark::variant m_value; friend class GeoRegion; }; @@ -252,9 +252,9 @@ class GeoRegion { }; template <> -inline const GeoCenterSphere& Geospatial::get() const noexcept +inline const GeoCircle& Geospatial::get() const noexcept { - return mpark::get(m_value); + return mpark::get(m_value); } template <> diff --git a/src/realm/parser/driver.cpp b/src/realm/parser/driver.cpp index a7b13674c6e..d9f203b0cda 100644 --- a/src/realm/parser/driver.cpp +++ b/src/realm/parser/driver.cpp @@ -1361,8 +1361,8 @@ GeospatialNode::GeospatialNode(GeospatialNode::Box, GeoPoint& p1, GeoPoint& p2) { } -GeospatialNode::GeospatialNode(Sphere, GeoPoint& p, double radius) - : m_geo{Geospatial{GeoCenterSphere{radius, p}}} +GeospatialNode::GeospatialNode(Circle, GeoPoint& p, double radius) + : m_geo{Geospatial{GeoCircle{radius, p}}} { } diff --git a/src/realm/parser/driver.hpp b/src/realm/parser/driver.hpp index 2b1ad1e9be7..e540260e9f5 100644 --- a/src/realm/parser/driver.hpp +++ b/src/realm/parser/driver.hpp @@ -201,10 +201,10 @@ class GeospatialNode : public ValueNode { struct Box {}; struct Polygon {}; struct Loop {}; - struct Sphere {}; + struct Circle {}; #if REALM_ENABLE_GEOSPATIAL GeospatialNode(Box, GeoPoint& p1, GeoPoint& p2); - GeospatialNode(Sphere, GeoPoint& p, double radius); + GeospatialNode(Circle, GeoPoint& p, double radius); GeospatialNode(Polygon, GeoPoint& p); GeospatialNode(Loop, GeoPoint& p); void add_point_to_loop(GeoPoint& p); diff --git a/src/realm/parser/generated/query_bison.cpp b/src/realm/parser/generated/query_bison.cpp index 2fb312ffd81..0d1a77eb1a6 100644 --- a/src/realm/parser/generated/query_bison.cpp +++ b/src/realm/parser/generated/query_bison.cpp @@ -883,7 +883,7 @@ namespace yy { { yyo << "<>"; } break; - case symbol_kind::SYM_GEOSPHERE: // "geosphere" + case symbol_kind::SYM_GEOCIRCLE: // "geocircle" { yyo << "<>"; } break; @@ -1760,8 +1760,8 @@ namespace yy { { yylhs.value.as < GeospatialNode* > () = drv.m_parse_nodes.create(GeospatialNode::Box{}, *yystack_[3].value.as < std::optional > (), *yystack_[1].value.as < std::optional > ()); } break; - case 45: // geospatial: "geosphere" '(' geopoint ',' coordinate ')' - { yylhs.value.as < GeospatialNode* > () = drv.m_parse_nodes.create(GeospatialNode::Sphere{}, *yystack_[3].value.as < std::optional > (), yystack_[1].value.as < double > ()); } + case 45: // geospatial: "geocircle" '(' geopoint ',' coordinate ')' + { yylhs.value.as < GeospatialNode* > () = drv.m_parse_nodes.create(GeospatialNode::Circle{}, *yystack_[3].value.as < std::optional > (), yystack_[1].value.as < double > ()); } break; case 46: // geospatial: "geopolygon" '(' geopoly_content ')' @@ -2717,7 +2717,7 @@ namespace yy { "\"null\"", "\"==\"", "\"!=\"", "\"<\"", "\">\"", "\">=\"", "\"<=\"", "\"[c]\"", "\"any\"", "\"all\"", "\"none\"", "\"@links\"", "\"@max\"", "\"@min\"", "\"@sun\"", "\"@average\"", "\"&&\"", "\"||\"", "\"!\"", - "\"geobox\"", "\"geopolygon\"", "\"geosphere\"", "\"identifier\"", + "\"geobox\"", "\"geopolygon\"", "\"geocircle\"", "\"identifier\"", "\"string\"", "\"base64\"", "\"infinity\"", "\"NaN\"", "\"natural0\"", "\"number\"", "\"float\"", "\"date\"", "\"UUID\"", "\"ObjectId\"", "\"link\"", "\"typed link\"", "\"argument\"", "\"beginswith\"", diff --git a/src/realm/parser/generated/query_bison.hpp b/src/realm/parser/generated/query_bison.hpp index 6ad701c0822..2d35253fa1d 100644 --- a/src/realm/parser/generated/query_bison.hpp +++ b/src/realm/parser/generated/query_bison.hpp @@ -621,7 +621,7 @@ namespace yy { TOK_NOT = 281, // "!" TOK_GEOBOX = 282, // "geobox" TOK_GEOPOLYGON = 283, // "geopolygon" - TOK_GEOSPHERE = 284, // "geosphere" + TOK_GEOCIRCLE = 284, // "geocircle" TOK_ID = 285, // "identifier" TOK_STRING = 286, // "string" TOK_BASE64 = 287, // "base64" @@ -700,7 +700,7 @@ namespace yy { SYM_NOT = 26, // "!" SYM_GEOBOX = 27, // "geobox" SYM_GEOPOLYGON = 28, // "geopolygon" - SYM_GEOSPHERE = 29, // "geosphere" + SYM_GEOCIRCLE = 29, // "geocircle" SYM_ID = 30, // "identifier" SYM_STRING = 31, // "string" SYM_BASE64 = 32, // "base64" @@ -1467,7 +1467,7 @@ switch (yykind) { #if !defined _MSC_VER || defined __clang__ YY_ASSERT (tok == token::TOK_END - || (token::TOK_YYerror <= tok && tok <= token::TOK_GEOSPHERE) + || (token::TOK_YYerror <= tok && tok <= token::TOK_GEOCIRCLE) || tok == 43 || tok == 45 || tok == 42 @@ -1978,16 +1978,16 @@ switch (yykind) #if 201103L <= YY_CPLUSPLUS static symbol_type - make_GEOSPHERE () + make_GEOCIRCLE () { - return symbol_type (token::TOK_GEOSPHERE); + return symbol_type (token::TOK_GEOCIRCLE); } #else static symbol_type - make_GEOSPHERE () + make_GEOCIRCLE () { - return symbol_type (token::TOK_GEOSPHERE); + return symbol_type (token::TOK_GEOCIRCLE); } #endif #if 201103L <= YY_CPLUSPLUS diff --git a/src/realm/parser/generated/query_flex.cpp b/src/realm/parser/generated/query_flex.cpp index 7cc93b99d2f..a00bb565312 100644 --- a/src/realm/parser/generated/query_flex.cpp +++ b/src/realm/parser/generated/query_flex.cpp @@ -549,7 +549,7 @@ static const flex_int16_t yy_accept[418] = 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 66, 66, 20, 66, 28, 19, 66, 66, 66, 66, 49, 33, 66, 0, 0, 49, 0, 31, 66, 66, - 66, 66, 36, 24, 66, 0, 0, 0, 18, 32, + 66, 36, 66, 24, 66, 0, 0, 0, 18, 32, 66, 35, 66, 0, 0, 54, 66, 66, 0, 0, 0, 66, 66, 0, 0, 54, 66, 25, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -607,53 +607,53 @@ static const YY_CHAR yy_meta[87] = static const flex_int16_t yy_base[487] = { 0, - 0, 0, 798, 1947, 85, 781, 758, 82, 72, 764, - 85, 1947, 1947, 79, 83, 90, 111, 88, 92, 740, + 0, 0, 798, 1933, 85, 781, 758, 82, 72, 764, + 85, 1933, 1933, 79, 83, 90, 111, 88, 92, 740, 78, 109, 139, 120, 149, 134, 112, 154, 126, 168, 208, 195, 245, 215, 319, 705, 237, 257, 230, 267, 260, 330, 305, 323, 357, 342, 679, 677, 674, 673, - 116, 751, 1947, 122, 1947, 414, 275, 415, 172, 670, - 660, 655, 1947, 113, 1947, 441, 419, 435, 119, 159, - 443, 458, 487, 504, 513, 0, 1947, 1947, 1947, 1947, + 116, 751, 1933, 122, 1933, 414, 275, 415, 172, 670, + 660, 655, 1933, 113, 1933, 441, 419, 435, 119, 159, + 443, 458, 487, 504, 513, 0, 1933, 1933, 1933, 1933, 660, 664, 668, 650, 77, 113, 624, 647, 360, 485, 492, 427, 477, 506, 489, 282, 407, 513, 529, 541, 547, 551, 557, 601, 591, 576, 586, 500, 596, 646, 637, 621, 640, 655, 625, 536, 702, 705, 666, 682, 695, 647, 702, 717, 706, 724, 730, 751, 747, 721, - 1947, 743, 617, 609, 0, 605, 585, 0, 185, 191, - 676, 1947, 823, 827, 831, 835, 0, 596, 580, 575, + 1933, 743, 617, 609, 0, 605, 585, 0, 185, 191, + 676, 1933, 823, 827, 831, 835, 0, 596, 580, 575, 583, 572, 581, 567, 579, 575, 573, 777, 781, 786, 826, 806, 829, 820, 832, 866, 848, 855, 875, 882, - 920, 890, 856, 911, 928, 894, 900, 956, 904, 962, - 917, 968, 974, 979, 1015, 1053, 992, 997, 1031, 1947, - 1016, 1026, 1034, 1039, 1044, 1050, 555, 0, 528, 0, - - 214, 1947, 998, 1087, 1005, 1947, 540, 533, 540, 1947, - 1947, 545, 1947, 543, 526, 1073, 595, 1087, 1090, 1110, - 1100, 1114, 1128, 1137, 1148, 1166, 1174, 1140, 1156, 1177, - 1075, 1061, 1182, 1163, 1185, 1190, 1225, 1184, 1235, 1211, - 1231, 1235, 1252, 0, 1255, 0, 0, 215, 1261, 520, - 1947, 519, 1947, 1947, 527, 1269, 1947, 574, 1240, 1282, - 1265, 1290, 1293, 1299, 1316, 1319, 1307, 1327, 1336, 1342, - 1361, 1345, 1379, 1365, 0, 0, 0, 0, 223, 1271, - 1947, 486, 1371, 1384, 1408, 1413, 1422, 1425, 1438, 1432, - 1387, 1435, 1443, 1478, 1484, 1481, 1495, 0, 0, 221, - - 1524, 1947, 1507, 1518, 1499, 1504, 1542, 1548, 1552, 1556, - 1567, 1577, 1561, 1603, 1606, 1613, 0, 0, 223, 1423, - 1616, 1621, 1591, 1595, 1629, 1632, 1673, 1667, 1678, 1670, - 1658, 1681, 1715, 0, 0, 1947, 1727, 1687, 1707, 1728, - 1734, 1742, 1721, 1724, 1786, 0, 0, 1554, 1749, 1763, - 1792, 1769, 1776, 0, 0, 1845, 1803, 1822, 0, 0, - 1592, 1827, 1838, 0, 0, 1709, 1843, 1798, 0, 536, - 1832, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 916, 911, 856, 894, 928, 900, 904, 924, 919, 953, + 940, 945, 965, 974, 1014, 1048, 969, 988, 992, 1933, + 1011, 1022, 1018, 1026, 1031, 1034, 555, 0, 528, 0, + + 214, 1933, 1080, 1103, 1107, 1933, 540, 533, 540, 1933, + 1933, 545, 1933, 543, 526, 1089, 595, 1092, 1083, 1103, + 1100, 1117, 1113, 1140, 1151, 1110, 1155, 1164, 1174, 1178, + 1119, 1004, 1167, 1130, 1159, 1170, 1214, 1128, 1250, 1193, + 1223, 1230, 1235, 0, 1220, 0, 0, 215, 1266, 520, + 1933, 519, 1933, 1933, 527, 1244, 1933, 574, 1257, 1265, + 1282, 1271, 1292, 1299, 1311, 1316, 1286, 1335, 1306, 1328, + 1340, 1345, 1358, 1362, 0, 0, 0, 0, 223, 1418, + 1933, 486, 1370, 1379, 1400, 1405, 1419, 1422, 1413, 1397, + 1408, 1442, 1458, 1462, 1466, 1476, 1470, 0, 0, 221, + + 1513, 1933, 1484, 1510, 1487, 1513, 1522, 1530, 1525, 1559, + 1551, 1567, 1578, 1570, 1581, 1587, 0, 0, 223, 1561, + 1595, 1622, 1540, 1607, 1616, 1625, 1629, 1643, 1646, 1664, + 1636, 1652, 1659, 0, 0, 1933, 1733, 1672, 1706, 1709, + 1712, 1682, 1720, 1693, 1754, 0, 0, 1665, 1717, 1729, + 1758, 1733, 1772, 0, 0, 1802, 1781, 1795, 0, 0, + 1811, 1799, 1802, 0, 0, 1857, 1829, 1806, 0, 536, + 1810, 0, 0, 0, 0, 0, 0, 0, 0, 0, 534, 0, 0, 0, 0, 0, 0, 0, 0, 0, 526, 0, 0, 0, 0, 0, 0, 0, 0, 526, - 512, 1947, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 498, 1947, 1947, 1929, 1932, 1937, - 484, 481, 480, 478, 469, 1941, 468, 466, 465, 461, + 512, 1933, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 498, 1933, 1933, 1915, 1918, 1923, + 484, 481, 480, 478, 469, 1927, 468, 466, 465, 461, 445, 444, 425, 421, 420, 418, 417, 415, 408, 407, 398, 393, 392, 383, 381, 380, 378, 377, 376, 371, 370, 369, 367, 351, 350, 344, 343, 322, 321, 315, @@ -719,7 +719,7 @@ static const flex_int16_t yy_def[487] = 417, 417, 417, 417, 417, 417 } ; -static const flex_int16_t yy_nxt[2034] = +static const flex_int16_t yy_nxt[2020] = { 0, 4, 5, 6, 5, 7, 8, 9, 10, 11, 12, 12, 13, 14, 12, 14, 15, 13, 16, 17, 17, @@ -822,132 +822,130 @@ static const flex_int16_t yy_nxt[2034] = 59, 417, 417, 60, 61, 62, 220, 59, 417, 60, 61, 62, 60, 61, 62, 60, 61, 62, 59, 224, - 417, 417, 222, 221, 230, 417, 225, 59, 223, 417, + 417, 417, 222, 221, 232, 417, 225, 59, 223, 417, 417, 60, 61, 62, 59, 417, 417, 417, 60, 61, - 62, 232, 59, 417, 417, 417, 59, 226, 225, 60, - 61, 62, 59, 230, 417, 225, 59, 417, 60, 61, - 62, 227, 233, 59, 228, 60, 61, 62, 229, 59, - 232, 417, 59, 60, 61, 62, 226, 60, 61, 62, - 59, 417, 417, 60, 61, 62, 234, 60, 61, 62, - 227, 233, 417, 228, 60, 61, 62, 229, 160, 235, - - 60, 61, 62, 60, 61, 62, 417, 417, 59, 417, - 417, 60, 61, 62, 59, 204, 204, 204, 204, 236, - 59, 237, 249, 249, 249, 249, 59, 241, 235, 417, - 417, 59, 185, 185, 185, 185, 186, 240, 417, 60, - 61, 62, 417, 244, 59, 60, 61, 62, 236, 59, - 237, 60, 61, 62, 417, 417, 242, 60, 61, 62, - 225, 242, 60, 61, 62, 238, 240, 238, 59, 417, - 239, 239, 239, 239, 242, 60, 61, 62, 59, 417, - 60, 61, 62, 59, 417, 234, 59, 417, 417, 243, - 242, 59, 231, 231, 231, 231, 59, 417, 160, 60, - - 61, 62, 59, 241, 204, 204, 204, 204, 245, 60, - 61, 62, 256, 59, 60, 61, 62, 60, 61, 62, - 260, 417, 60, 61, 62, 59, 259, 60, 61, 62, - 262, 417, 417, 60, 61, 62, 261, 417, 417, 59, - 417, 256, 59, 417, 60, 61, 62, 142, 263, 260, - 417, 417, 59, 417, 417, 259, 60, 61, 62, 262, - 417, 417, 59, 417, 417, 261, 59, 265, 417, 417, - 60, 61, 62, 60, 61, 62, 264, 263, 266, 417, - 59, 269, 417, 60, 61, 62, 417, 417, 417, 59, - 270, 417, 59, 60, 61, 62, 266, 60, 61, 62, - - 59, 239, 239, 239, 239, 264, 267, 266, 59, 417, - 269, 60, 61, 62, 268, 59, 271, 417, 59, 270, - 60, 61, 62, 60, 61, 62, 59, 272, 417, 59, - 417, 60, 61, 62, 59, 267, 417, 59, 417, 60, - 61, 62, 59, 268, 417, 271, 60, 61, 62, 60, - 61, 62, 239, 239, 239, 239, 272, 60, 61, 62, - 60, 61, 62, 59, 276, 60, 61, 62, 60, 61, - 62, 273, 274, 60, 61, 62, 274, 59, 249, 249, - 249, 249, 266, 59, 284, 417, 280, 59, 301, 301, - 301, 301, 59, 417, 60, 61, 62, 417, 283, 286, - - 273, 274, 417, 417, 59, 274, 280, 59, 60, 61, - 62, 265, 285, 284, 60, 61, 62, 59, 60, 61, - 62, 59, 417, 60, 61, 62, 417, 283, 286, 287, - 417, 417, 288, 289, 59, 60, 61, 62, 60, 61, - 62, 285, 59, 417, 417, 59, 417, 417, 60, 61, - 62, 59, 60, 61, 62, 417, 291, 290, 287, 59, - 290, 288, 289, 417, 292, 60, 61, 62, 59, 293, - 417, 59, 417, 60, 61, 62, 60, 61, 62, 59, - 417, 417, 60, 61, 62, 291, 290, 294, 59, 290, - 60, 61, 62, 292, 59, 295, 417, 59, 293, 60, - - 61, 62, 60, 61, 62, 303, 417, 417, 297, 296, - 60, 61, 62, 59, 417, 417, 294, 59, 417, 60, - 61, 62, 417, 59, 295, 60, 61, 62, 60, 61, - 62, 59, 304, 417, 303, 417, 59, 297, 296, 59, - 337, 337, 337, 337, 60, 61, 62, 305, 60, 61, - 62, 307, 306, 308, 60, 61, 62, 417, 417, 417, - 59, 304, 60, 61, 62, 59, 417, 60, 61, 62, - 60, 61, 62, 312, 59, 310, 305, 59, 417, 417, - 307, 306, 308, 309, 59, 311, 417, 59, 417, 417, - 59, 60, 61, 62, 417, 59, 60, 61, 62, 417, - - 417, 417, 312, 417, 310, 60, 61, 62, 60, 61, - 62, 313, 309, 417, 311, 60, 61, 62, 60, 61, - 62, 60, 61, 62, 315, 316, 60, 61, 62, 314, - 59, 417, 417, 59, 417, 417, 59, 417, 417, 417, - 313, 301, 301, 301, 301, 320, 321, 59, 323, 417, - 417, 59, 322, 315, 316, 417, 59, 417, 314, 59, - 417, 60, 61, 62, 60, 61, 62, 60, 61, 62, - 59, 356, 356, 356, 356, 321, 324, 323, 60, 61, - 62, 322, 60, 61, 62, 326, 327, 60, 61, 62, - 60, 61, 62, 325, 59, 330, 417, 417, 417, 328, - - 59, 60, 61, 62, 59, 324, 417, 417, 59, 366, - 366, 366, 366, 59, 326, 327, 417, 417, 417, 59, - 329, 417, 325, 417, 330, 60, 61, 62, 328, 59, - 417, 60, 61, 62, 340, 60, 61, 62, 417, 60, - 61, 62, 333, 59, 60, 61, 62, 59, 338, 329, - 60, 61, 62, 331, 417, 59, 332, 417, 59, 417, - 60, 61, 62, 340, 417, 59, 339, 417, 59, 417, - 417, 333, 417, 59, 60, 61, 62, 338, 60, 61, - 62, 59, 331, 417, 59, 332, 60, 61, 62, 60, - 61, 62, 417, 417, 417, 339, 60, 61, 62, 60, - - 61, 62, 341, 417, 60, 61, 62, 342, 343, 344, - 59, 417, 60, 61, 62, 60, 61, 62, 417, 59, - 417, 417, 59, 417, 417, 59, 366, 366, 366, 366, - 59, 341, 417, 59, 417, 417, 342, 343, 344, 59, - 349, 60, 61, 62, 337, 337, 337, 337, 348, 345, - 60, 61, 62, 60, 61, 62, 60, 61, 62, 59, - 350, 60, 61, 62, 60, 61, 62, 59, 351, 349, - 60, 61, 62, 59, 417, 417, 59, 417, 345, 417, - 59, 352, 417, 417, 417, 417, 59, 417, 417, 350, - 60, 61, 62, 417, 59, 417, 417, 351, 60, 61, - - 62, 59, 358, 417, 60, 61, 62, 60, 61, 62, - 352, 60, 61, 62, 353, 59, 417, 60, 61, 62, - 357, 59, 417, 417, 417, 60, 61, 62, 59, 362, - 417, 358, 60, 61, 62, 417, 417, 417, 59, 417, - 417, 417, 417, 353, 59, 417, 60, 61, 62, 357, - 59, 417, 60, 61, 62, 59, 417, 417, 362, 60, - 61, 62, 356, 356, 356, 356, 361, 363, 368, 60, - 61, 62, 367, 371, 59, 60, 61, 62, 417, 59, - 417, 60, 61, 62, 59, 417, 60, 61, 62, 417, - 59, 417, 417, 417, 417, 59, 363, 368, 417, 417, - - 417, 367, 371, 417, 417, 60, 61, 62, 417, 417, - 60, 61, 62, 417, 417, 60, 61, 62, 417, 417, - 417, 60, 61, 62, 417, 417, 60, 61, 62, 54, - 54, 54, 54, 54, 57, 57, 57, 64, 64, 64, - 64, 64, 258, 417, 258, 258, 3, 417, 417, 417, + 62, 417, 417, 226, 227, 230, 59, 417, 225, 60, + 61, 62, 59, 232, 234, 225, 59, 228, 60, 61, + 62, 417, 233, 59, 229, 60, 61, 62, 59, 417, + 417, 59, 226, 227, 230, 160, 59, 60, 61, 62, + 59, 417, 417, 60, 61, 62, 228, 60, 61, 62, + 235, 233, 59, 229, 60, 61, 62, 59, 417, 60, + + 61, 62, 60, 61, 62, 59, 417, 60, 61, 62, + 236, 60, 61, 62, 240, 417, 237, 59, 241, 235, + 417, 59, 242, 60, 61, 62, 59, 244, 60, 61, + 62, 185, 185, 185, 185, 186, 60, 61, 62, 236, + 59, 417, 417, 240, 59, 237, 417, 242, 60, 61, + 62, 242, 60, 61, 62, 225, 59, 60, 61, 62, + 238, 242, 238, 59, 417, 239, 239, 239, 239, 417, + 59, 60, 61, 62, 59, 60, 61, 62, 59, 417, + 417, 234, 417, 59, 243, 160, 59, 60, 61, 62, + 241, 417, 245, 417, 60, 61, 62, 204, 204, 204, + + 204, 60, 61, 62, 417, 60, 61, 62, 417, 60, + 61, 62, 417, 260, 60, 61, 62, 60, 61, 62, + 204, 204, 204, 204, 249, 249, 249, 249, 256, 261, + 262, 259, 417, 417, 417, 59, 231, 231, 231, 231, + 417, 59, 260, 417, 59, 239, 239, 239, 239, 417, + 267, 263, 59, 417, 417, 59, 417, 256, 261, 262, + 259, 264, 59, 142, 417, 59, 60, 61, 62, 59, + 265, 417, 60, 61, 62, 60, 61, 62, 417, 267, + 263, 266, 59, 60, 61, 62, 60, 61, 62, 268, + 264, 417, 59, 60, 61, 62, 60, 61, 62, 266, + + 60, 61, 62, 59, 269, 417, 417, 59, 270, 417, + 266, 59, 272, 60, 61, 62, 59, 271, 268, 59, + 417, 417, 59, 60, 61, 62, 59, 417, 417, 276, + 59, 417, 417, 269, 60, 61, 62, 270, 60, 61, + 62, 272, 60, 61, 62, 59, 271, 60, 61, 62, + 60, 61, 62, 60, 61, 62, 417, 60, 61, 62, + 273, 60, 61, 62, 274, 266, 59, 239, 239, 239, + 239, 274, 59, 283, 417, 59, 60, 61, 62, 417, + 417, 417, 59, 249, 249, 249, 249, 59, 417, 273, + 417, 280, 417, 274, 265, 285, 59, 60, 61, 62, + + 274, 284, 283, 60, 61, 62, 60, 61, 62, 59, + 287, 280, 417, 60, 61, 62, 286, 59, 60, 61, + 62, 417, 417, 59, 285, 417, 417, 60, 61, 62, + 284, 288, 417, 289, 59, 291, 417, 417, 59, 287, + 60, 61, 62, 293, 59, 286, 417, 417, 60, 61, + 62, 59, 290, 417, 60, 61, 62, 290, 59, 417, + 288, 417, 289, 59, 291, 60, 61, 62, 59, 60, + 61, 62, 293, 294, 295, 60, 61, 62, 292, 417, + 59, 290, 60, 61, 62, 417, 290, 59, 296, 60, + 61, 62, 59, 417, 60, 61, 62, 59, 417, 60, + + 61, 62, 294, 295, 303, 297, 417, 292, 417, 417, + 59, 60, 61, 62, 59, 417, 417, 296, 60, 61, + 62, 417, 59, 60, 61, 62, 417, 304, 60, 61, + 62, 59, 417, 303, 297, 301, 301, 301, 301, 305, + 310, 60, 61, 62, 306, 60, 61, 62, 307, 59, + 308, 417, 59, 60, 61, 62, 304, 59, 309, 417, + 59, 417, 60, 61, 62, 59, 417, 417, 305, 310, + 311, 59, 417, 306, 59, 417, 417, 307, 417, 308, + 60, 61, 62, 60, 61, 62, 417, 309, 60, 61, + 62, 60, 61, 62, 59, 313, 60, 61, 62, 311, + + 316, 417, 60, 61, 62, 60, 61, 62, 312, 417, + 59, 314, 417, 417, 59, 417, 417, 417, 59, 315, + 417, 417, 59, 321, 313, 60, 61, 62, 59, 316, + 301, 301, 301, 301, 320, 417, 59, 312, 417, 59, + 314, 60, 61, 62, 322, 60, 61, 62, 315, 60, + 61, 62, 321, 60, 61, 62, 324, 323, 326, 60, + 61, 62, 59, 417, 417, 59, 417, 60, 61, 62, + 60, 61, 62, 322, 59, 325, 417, 59, 337, 337, + 337, 337, 59, 417, 417, 324, 323, 326, 328, 327, + 417, 417, 59, 60, 61, 62, 60, 61, 62, 329, + + 417, 417, 417, 59, 325, 60, 61, 62, 60, 61, + 62, 59, 330, 60, 61, 62, 333, 328, 327, 59, + 331, 417, 59, 60, 61, 62, 417, 338, 329, 417, + 59, 332, 417, 59, 60, 61, 62, 417, 417, 59, + 417, 330, 60, 61, 62, 333, 340, 59, 417, 331, + 60, 61, 62, 60, 61, 62, 338, 417, 341, 59, + 332, 60, 61, 62, 60, 61, 62, 339, 59, 417, + 60, 61, 62, 342, 59, 340, 417, 59, 60, 61, + 62, 59, 356, 356, 356, 356, 343, 341, 59, 417, + 60, 61, 62, 345, 417, 59, 339, 417, 59, 60, + + 61, 62, 342, 344, 59, 60, 61, 62, 60, 61, + 62, 59, 60, 61, 62, 343, 59, 417, 417, 60, + 61, 62, 345, 417, 59, 417, 60, 61, 62, 60, + 61, 62, 344, 417, 59, 60, 61, 62, 417, 349, + 417, 350, 60, 61, 62, 59, 351, 60, 61, 62, + 337, 337, 337, 337, 348, 60, 61, 62, 59, 352, + 417, 59, 417, 417, 59, 60, 61, 62, 349, 59, + 350, 417, 59, 417, 417, 351, 60, 61, 62, 417, + 417, 59, 353, 417, 417, 59, 357, 417, 352, 60, + 61, 62, 60, 61, 62, 60, 61, 62, 358, 417, + + 60, 61, 62, 60, 61, 62, 59, 362, 417, 417, + 59, 353, 60, 61, 62, 357, 60, 61, 62, 356, + 356, 356, 356, 361, 59, 417, 417, 358, 366, 366, + 366, 366, 368, 59, 417, 417, 362, 60, 61, 62, + 363, 60, 61, 62, 367, 417, 417, 59, 417, 417, + 417, 59, 417, 417, 59, 60, 61, 62, 59, 371, + 417, 368, 59, 417, 60, 61, 62, 417, 417, 363, + 417, 417, 417, 367, 366, 366, 366, 366, 60, 61, + 62, 59, 60, 61, 62, 60, 61, 62, 371, 60, + 61, 62, 417, 60, 61, 62, 417, 417, 417, 417, + 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 417, 417, 60, 61, 62, 54, 54, 54, 54, 54, + 57, 57, 57, 64, 64, 64, 64, 64, 258, 417, + 258, 258, 3, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417 + 417, 417, 417, 417, 417, 417, 417, 417, 417 } ; -static const flex_int16_t yy_chk[2034] = +static const flex_int16_t yy_chk[2020] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1050,129 +1048,127 @@ static const flex_int16_t yy_chk[2034] = 167, 0, 0, 164, 164, 164, 165, 168, 0, 161, 161, 161, 163, 163, 163, 165, 165, 165, 166, 169, - 0, 0, 167, 166, 172, 0, 170, 169, 168, 0, + 0, 0, 167, 166, 174, 0, 170, 169, 168, 0, 0, 167, 167, 167, 170, 0, 0, 0, 168, 168, - 168, 174, 172, 0, 0, 0, 176, 171, 169, 166, - 166, 166, 177, 172, 0, 170, 179, 0, 169, 169, - 169, 171, 175, 174, 171, 170, 170, 170, 171, 181, - 174, 0, 171, 172, 172, 172, 171, 176, 176, 176, - 175, 0, 0, 177, 177, 177, 178, 179, 179, 179, - 171, 175, 0, 171, 174, 174, 174, 171, 182, 180, - - 181, 181, 181, 171, 171, 171, 0, 0, 178, 0, - 0, 175, 175, 175, 180, 203, 203, 203, 203, 183, - 182, 184, 205, 205, 205, 205, 183, 188, 180, 0, - 0, 184, 185, 185, 185, 185, 185, 187, 0, 178, - 178, 178, 0, 193, 187, 180, 180, 180, 183, 188, - 184, 182, 182, 182, 0, 0, 188, 183, 183, 183, - 191, 189, 184, 184, 184, 186, 187, 186, 191, 0, - 186, 186, 186, 186, 195, 187, 187, 187, 192, 0, - 188, 188, 188, 189, 0, 192, 193, 0, 0, 191, - 189, 194, 231, 231, 231, 231, 195, 0, 194, 191, - - 191, 191, 196, 195, 204, 204, 204, 204, 196, 192, - 192, 192, 216, 232, 189, 189, 189, 193, 193, 193, - 219, 0, 194, 194, 194, 216, 218, 195, 195, 195, - 221, 0, 0, 196, 196, 196, 220, 0, 0, 218, - 0, 216, 219, 0, 232, 232, 232, 204, 222, 219, - 0, 0, 221, 0, 0, 218, 216, 216, 216, 221, - 0, 0, 220, 0, 0, 220, 222, 224, 0, 0, - 218, 218, 218, 219, 219, 219, 223, 222, 225, 0, - 223, 228, 0, 221, 221, 221, 0, 0, 0, 224, - 229, 0, 228, 220, 220, 220, 224, 222, 222, 222, - - 225, 238, 238, 238, 238, 223, 226, 225, 229, 0, - 228, 223, 223, 223, 227, 234, 230, 0, 226, 229, - 224, 224, 224, 228, 228, 228, 227, 233, 0, 230, - 0, 225, 225, 225, 233, 226, 0, 235, 0, 229, - 229, 229, 236, 227, 0, 230, 234, 234, 234, 226, - 226, 226, 239, 239, 239, 239, 233, 227, 227, 227, - 230, 230, 230, 240, 245, 233, 233, 233, 235, 235, - 235, 237, 241, 236, 236, 236, 242, 237, 249, 249, - 249, 249, 243, 241, 259, 0, 249, 242, 280, 280, - 280, 280, 259, 0, 240, 240, 240, 0, 256, 261, - - 237, 241, 0, 0, 243, 242, 249, 245, 237, 237, - 237, 243, 260, 259, 241, 241, 241, 261, 242, 242, - 242, 256, 0, 259, 259, 259, 0, 256, 261, 262, - 0, 0, 263, 264, 260, 243, 243, 243, 245, 245, - 245, 260, 262, 0, 0, 263, 0, 0, 261, 261, - 261, 264, 256, 256, 256, 0, 267, 265, 262, 267, - 266, 263, 264, 0, 268, 260, 260, 260, 265, 269, - 0, 266, 0, 262, 262, 262, 263, 263, 263, 268, - 0, 0, 264, 264, 264, 267, 265, 270, 269, 266, - 267, 267, 267, 268, 270, 271, 0, 272, 269, 265, - - 265, 265, 266, 266, 266, 283, 0, 0, 274, 273, - 268, 268, 268, 271, 0, 0, 270, 274, 0, 269, - 269, 269, 0, 283, 271, 270, 270, 270, 272, 272, - 272, 273, 284, 0, 283, 0, 284, 274, 273, 291, - 320, 320, 320, 320, 271, 271, 271, 285, 274, 274, - 274, 287, 286, 288, 283, 283, 283, 0, 0, 0, - 285, 284, 273, 273, 273, 286, 0, 284, 284, 284, - 291, 291, 291, 293, 287, 290, 285, 288, 0, 0, - 287, 286, 288, 289, 290, 292, 0, 292, 0, 0, - 289, 285, 285, 285, 0, 293, 286, 286, 286, 0, - - 0, 0, 293, 0, 290, 287, 287, 287, 288, 288, - 288, 294, 289, 0, 292, 290, 290, 290, 292, 292, - 292, 289, 289, 289, 296, 297, 293, 293, 293, 295, - 294, 0, 0, 296, 0, 0, 295, 0, 0, 0, - 294, 301, 301, 301, 301, 301, 303, 297, 306, 0, - 0, 305, 304, 296, 297, 0, 306, 0, 295, 303, - 0, 294, 294, 294, 296, 296, 296, 295, 295, 295, - 304, 348, 348, 348, 348, 303, 307, 306, 297, 297, - 297, 304, 305, 305, 305, 309, 310, 306, 306, 306, - 303, 303, 303, 308, 307, 313, 0, 0, 0, 311, - - 308, 304, 304, 304, 309, 307, 0, 0, 310, 361, - 361, 361, 361, 313, 309, 310, 0, 0, 0, 311, - 312, 0, 308, 0, 313, 307, 307, 307, 311, 312, - 0, 308, 308, 308, 324, 309, 309, 309, 0, 310, - 310, 310, 316, 323, 313, 313, 313, 324, 321, 312, - 311, 311, 311, 314, 0, 314, 315, 0, 315, 0, - 312, 312, 312, 324, 0, 316, 322, 0, 321, 0, - 0, 316, 0, 322, 323, 323, 323, 321, 324, 324, - 324, 325, 314, 0, 326, 315, 314, 314, 314, 315, - 315, 315, 0, 0, 0, 322, 316, 316, 316, 321, - - 321, 321, 327, 0, 322, 322, 322, 328, 329, 330, - 331, 0, 325, 325, 325, 326, 326, 326, 0, 328, - 0, 0, 330, 0, 0, 327, 366, 366, 366, 366, - 329, 327, 0, 332, 0, 0, 328, 329, 330, 338, - 339, 331, 331, 331, 337, 337, 337, 337, 337, 333, - 328, 328, 328, 330, 330, 330, 327, 327, 327, 339, - 340, 329, 329, 329, 332, 332, 332, 333, 341, 339, - 338, 338, 338, 343, 0, 0, 344, 0, 333, 0, - 340, 342, 0, 0, 0, 0, 341, 0, 0, 340, - 339, 339, 339, 0, 342, 0, 0, 341, 333, 333, - - 333, 349, 353, 0, 343, 343, 343, 344, 344, 344, - 342, 340, 340, 340, 345, 350, 0, 341, 341, 341, - 351, 352, 0, 0, 0, 342, 342, 342, 353, 357, - 0, 353, 349, 349, 349, 0, 0, 0, 345, 0, - 0, 0, 0, 345, 351, 0, 350, 350, 350, 351, - 368, 0, 352, 352, 352, 357, 0, 0, 357, 353, - 353, 353, 356, 356, 356, 356, 356, 358, 363, 345, - 345, 345, 362, 367, 358, 351, 351, 351, 0, 362, - 0, 368, 368, 368, 371, 0, 357, 357, 357, 0, - 363, 0, 0, 0, 0, 367, 358, 363, 0, 0, - - 0, 362, 367, 0, 0, 358, 358, 358, 0, 0, - 362, 362, 362, 0, 0, 371, 371, 371, 0, 0, - 0, 363, 363, 363, 0, 0, 367, 367, 367, 418, - 418, 418, 418, 418, 419, 419, 419, 420, 420, 420, - 420, 420, 426, 0, 426, 426, 417, 417, 417, 417, - 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 168, 0, 0, 171, 171, 172, 174, 0, 169, 166, + 166, 166, 176, 174, 178, 170, 177, 171, 169, 169, + 169, 0, 175, 172, 171, 170, 170, 170, 171, 0, + 0, 179, 171, 171, 172, 182, 178, 174, 174, 174, + 175, 0, 0, 176, 176, 176, 171, 177, 177, 177, + 180, 175, 181, 171, 172, 172, 172, 182, 0, 171, + + 171, 171, 179, 179, 179, 180, 0, 178, 178, 178, + 183, 175, 175, 175, 187, 0, 184, 183, 188, 180, + 0, 187, 189, 181, 181, 181, 184, 193, 182, 182, + 182, 185, 185, 185, 185, 185, 180, 180, 180, 183, + 188, 0, 0, 187, 189, 184, 0, 188, 183, 183, + 183, 189, 187, 187, 187, 191, 232, 184, 184, 184, + 186, 195, 186, 191, 0, 186, 186, 186, 186, 0, + 193, 188, 188, 188, 192, 189, 189, 189, 194, 0, + 0, 192, 0, 195, 191, 194, 196, 232, 232, 232, + 195, 0, 196, 0, 191, 191, 191, 203, 203, 203, + + 203, 193, 193, 193, 0, 192, 192, 192, 0, 194, + 194, 194, 0, 219, 195, 195, 195, 196, 196, 196, + 204, 204, 204, 204, 205, 205, 205, 205, 216, 220, + 221, 218, 0, 0, 0, 219, 231, 231, 231, 231, + 0, 216, 219, 0, 218, 238, 238, 238, 238, 0, + 226, 222, 221, 0, 0, 220, 0, 216, 220, 221, + 218, 223, 226, 204, 0, 223, 219, 219, 219, 222, + 224, 0, 216, 216, 216, 218, 218, 218, 0, 226, + 222, 225, 234, 221, 221, 221, 220, 220, 220, 227, + 223, 0, 224, 226, 226, 226, 223, 223, 223, 224, + + 222, 222, 222, 225, 228, 0, 0, 227, 229, 0, + 225, 235, 233, 234, 234, 234, 228, 230, 227, 233, + 0, 0, 236, 224, 224, 224, 229, 0, 0, 245, + 230, 0, 0, 228, 225, 225, 225, 229, 227, 227, + 227, 233, 235, 235, 235, 240, 230, 228, 228, 228, + 233, 233, 233, 236, 236, 236, 0, 229, 229, 229, + 237, 230, 230, 230, 241, 243, 237, 239, 239, 239, + 239, 242, 245, 256, 0, 241, 240, 240, 240, 0, + 0, 0, 242, 249, 249, 249, 249, 243, 0, 237, + 0, 249, 0, 241, 243, 260, 256, 237, 237, 237, + + 242, 259, 256, 245, 245, 245, 241, 241, 241, 259, + 262, 249, 0, 242, 242, 242, 261, 260, 243, 243, + 243, 0, 0, 262, 260, 0, 0, 256, 256, 256, + 259, 263, 0, 264, 261, 267, 0, 0, 267, 262, + 259, 259, 259, 269, 263, 261, 0, 0, 260, 260, + 260, 264, 265, 0, 262, 262, 262, 266, 269, 0, + 263, 0, 264, 265, 267, 261, 261, 261, 266, 267, + 267, 267, 269, 270, 271, 263, 263, 263, 268, 0, + 270, 265, 264, 264, 264, 0, 266, 268, 273, 269, + 269, 269, 271, 0, 265, 265, 265, 272, 0, 266, + + 266, 266, 270, 271, 283, 274, 0, 268, 0, 0, + 273, 270, 270, 270, 274, 0, 0, 273, 268, 268, + 268, 0, 283, 271, 271, 271, 0, 284, 272, 272, + 272, 284, 0, 283, 274, 280, 280, 280, 280, 285, + 290, 273, 273, 273, 286, 274, 274, 274, 287, 290, + 288, 0, 285, 283, 283, 283, 284, 286, 289, 0, + 291, 0, 284, 284, 284, 289, 0, 0, 285, 290, + 292, 287, 0, 286, 288, 0, 0, 287, 0, 288, + 290, 290, 290, 285, 285, 285, 0, 289, 286, 286, + 286, 291, 291, 291, 292, 294, 289, 289, 289, 292, + + 297, 0, 287, 287, 287, 288, 288, 288, 293, 0, + 293, 295, 0, 0, 294, 0, 0, 0, 295, 296, + 0, 0, 297, 303, 294, 292, 292, 292, 296, 297, + 301, 301, 301, 301, 301, 0, 303, 293, 0, 305, + 295, 293, 293, 293, 304, 294, 294, 294, 296, 295, + 295, 295, 303, 297, 297, 297, 307, 306, 309, 296, + 296, 296, 304, 0, 0, 306, 0, 303, 303, 303, + 305, 305, 305, 304, 307, 308, 0, 309, 320, 320, + 320, 320, 308, 0, 0, 307, 306, 309, 311, 310, + 0, 0, 323, 304, 304, 304, 306, 306, 306, 312, + + 0, 0, 0, 311, 308, 307, 307, 307, 309, 309, + 309, 310, 313, 308, 308, 308, 316, 311, 310, 312, + 314, 0, 314, 323, 323, 323, 0, 321, 312, 0, + 313, 315, 0, 315, 311, 311, 311, 0, 0, 316, + 0, 313, 310, 310, 310, 316, 324, 321, 0, 314, + 312, 312, 312, 314, 314, 314, 321, 0, 327, 324, + 315, 313, 313, 313, 315, 315, 315, 322, 325, 0, + 316, 316, 316, 328, 322, 324, 0, 326, 321, 321, + 321, 327, 348, 348, 348, 348, 329, 327, 331, 0, + 324, 324, 324, 333, 0, 328, 322, 0, 329, 325, + + 325, 325, 328, 330, 332, 322, 322, 322, 326, 326, + 326, 333, 327, 327, 327, 329, 330, 0, 0, 331, + 331, 331, 333, 0, 338, 0, 328, 328, 328, 329, + 329, 329, 330, 0, 342, 332, 332, 332, 0, 339, + 0, 340, 333, 333, 333, 344, 341, 330, 330, 330, + 337, 337, 337, 337, 337, 338, 338, 338, 339, 343, + 0, 340, 0, 0, 341, 342, 342, 342, 339, 349, + 340, 0, 343, 0, 0, 341, 344, 344, 344, 0, + 0, 350, 345, 0, 0, 352, 351, 0, 343, 339, + 339, 339, 340, 340, 340, 341, 341, 341, 353, 0, + + 349, 349, 349, 343, 343, 343, 345, 357, 0, 0, + 351, 345, 350, 350, 350, 351, 352, 352, 352, 356, + 356, 356, 356, 356, 353, 0, 0, 353, 361, 361, + 361, 361, 363, 357, 0, 0, 357, 345, 345, 345, + 358, 351, 351, 351, 362, 0, 0, 358, 0, 0, + 0, 362, 0, 0, 363, 353, 353, 353, 368, 367, + 0, 363, 371, 0, 357, 357, 357, 0, 0, 358, + 0, 0, 0, 362, 366, 366, 366, 366, 358, 358, + 358, 367, 362, 362, 362, 363, 363, 363, 367, 368, + 368, 368, 0, 371, 371, 371, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 367, 367, 367, 418, 418, 418, 418, 418, + 419, 419, 419, 420, 420, 420, 420, 420, 426, 0, + 426, 426, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, + 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, - 417, 417, 417 + 417, 417, 417, 417, 417, 417, 417, 417, 417 } ; static const flex_int16_t yy_rule_linenum[68] = @@ -1842,7 +1838,7 @@ return yy::parser::make_GEOPOLYGON(); YY_BREAK case 36: YY_RULE_SETUP -return yy::parser::make_GEOSPHERE(); +return yy::parser::make_GEOCIRCLE(); YY_BREAK case 37: YY_RULE_SETUP diff --git a/src/realm/parser/query_bison.yy b/src/realm/parser/query_bison.yy index a10a3fe2ccd..9957f13425d 100644 --- a/src/realm/parser/query_bison.yy +++ b/src/realm/parser/query_bison.yy @@ -101,7 +101,7 @@ using namespace realm::query_parser; NOT "!" GEOBOX "geobox" GEOPOLYGON "geopolygon" - GEOSPHERE "geosphere" + GEOCIRCLE "geocircle" ; %token ID "identifier" @@ -268,7 +268,7 @@ geopoly_content geospatial : GEOBOX '(' geopoint ',' geopoint ')' { $$ = drv.m_parse_nodes.create(GeospatialNode::Box{}, *$3, *$5); } - | GEOSPHERE '(' geopoint ',' coordinate ')' { $$ = drv.m_parse_nodes.create(GeospatialNode::Sphere{}, *$3, $5); } + | GEOCIRCLE '(' geopoint ',' coordinate ')' { $$ = drv.m_parse_nodes.create(GeospatialNode::Circle{}, *$3, $5); } | GEOPOLYGON '(' geopoly_content ')' { $$ = $3; } post_query diff --git a/src/realm/parser/query_flex.ll b/src/realm/parser/query_flex.ll index f5bb595cc8c..313d0c10ae0 100644 --- a/src/realm/parser/query_flex.ll +++ b/src/realm/parser/query_flex.ll @@ -65,7 +65,7 @@ blank [ \t\r] (?i:subquery) return yy::parser::make_SUBQUERY(); (?i:geobox) return yy::parser::make_GEOBOX(); (?i:geopolygon) return yy::parser::make_GEOPOLYGON(); -(?i:geosphere) return yy::parser::make_GEOSPHERE(); +(?i:geocircle) return yy::parser::make_GEOCIRCLE(); ("@size"|"@count") return yy::parser::make_SIZE(yytext); "@max" return yy::parser::make_MAX (); "@min" return yy::parser::make_MIN (); diff --git a/src/realm/query_expression.hpp b/src/realm/query_expression.hpp index a31a1badb52..9777b784008 100644 --- a/src/realm/query_expression.hpp +++ b/src/realm/query_expression.hpp @@ -1166,7 +1166,6 @@ class Subexpr2 : public Subexpr, public Overloads class Subexpr2 : public Subexpr, public Overloads { public: - // FIXME: Query geoWithin(const Geospatial& other); DataType get_type() const final { return type_Geospatial; @@ -2355,10 +2354,11 @@ class BacklinkCount : public Subexpr2 { #if REALM_ENABLE_GEOSPATIAL class GeoWithinCompare : public Expression { public: - GeoWithinCompare(const LinkMap& lm, Geospatial bounds) + GeoWithinCompare(const LinkMap& lm, Geospatial bounds, util::Optional comp_type) : m_link_map(lm) , m_bounds(bounds) , m_region(m_bounds) + , m_comp_type(comp_type) { Status status = m_region.get_conversion_status(); if (!status.is_ok()) { @@ -2372,6 +2372,7 @@ class GeoWithinCompare : public Expression { : m_link_map(other.m_link_map) , m_bounds(other.m_bounds) , m_region(m_bounds) + , m_comp_type(other.m_comp_type) { } @@ -2387,6 +2388,11 @@ class GeoWithinCompare : public Expression { throw std::runtime_error(util::format( "Query '%1' links to data in the wrong format for a geoWithin query", this->description(none))); } + if (!m_link_map.get_target_table()->is_embedded()) { + throw std::runtime_error(util::format( + "A GEOWITHIN query can only operate on a link to an embedded class but '%1' is at the top level", + m_link_map.get_target_table()->get_class_name())); + } } void set_cluster(const Cluster* cluster) override @@ -2408,17 +2414,58 @@ class GeoWithinCompare : public Expression { size_t find_first(size_t start, size_t end) const override { - bool found = false; auto table = m_link_map.get_target_table(); while (start < end) { - m_link_map.map_links(start, [&](ObjKey key) { - found = m_region.contains( - Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get()); - return found; - }); - if (found) - return start; + bool found = false; + // TODO: the map_links short circuit is not working, it is being fixed in a separate PR + // and when that lands the logic below can be simplified by removing the following counters + size_t num_matches = 0; + size_t num_misses = 0; + switch (m_comp_type.value_or(ExpressionComparisonType::Any)) { + case ExpressionComparisonType::Any: { + m_link_map.map_links(start, [&](ObjKey key) { + found = m_region.contains( + Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get()); + if (found) + num_matches++; + else + num_misses++; + return !found; // keep searching if not found, stop searching on first match + }); + if (num_matches > 0) + return start; + break; + } + case ExpressionComparisonType::All: { + m_link_map.map_links(start, [&](ObjKey key) { + found = m_region.contains( + Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get()); + if (found) + num_matches++; + else + num_misses++; + return found; // keep searching until one the first non-match + }); + if (num_matches > 0 && num_misses == 0) // all matched + return start; + break; + } + case ExpressionComparisonType::None: { + m_link_map.map_links(start, [&](ObjKey key) { + found = m_region.contains( + Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get()); + if (found) + num_matches++; + else + num_misses++; + return !found; // keep searching until the first match + }); + if (num_matches == 0) // none matched + return start; + break; + } + } start++; } @@ -2427,7 +2474,8 @@ class GeoWithinCompare : public Expression { virtual std::string description(util::serializer::SerialisationState& state) const override { - return state.describe_columns(m_link_map, ColKey()) + " GEOWITHIN " + util::serializer::print_value(m_bounds); + return state.describe_expression_type(m_comp_type) + state.describe_columns(m_link_map, ColKey()) + + " GEOWITHIN " + util::serializer::print_value(m_bounds); } std::unique_ptr clone() const override @@ -2441,6 +2489,7 @@ class GeoWithinCompare : public Expression { GeoRegion m_region; ColKey m_type_col; ColKey m_coords_col; + util::Optional m_comp_type; }; #endif @@ -2667,7 +2716,7 @@ class Columns : public Subexpr2 { #if REALM_ENABLE_GEOSPATIAL Query geo_within(Geospatial bounds) const { - return make_expression(m_link_map, bounds); + return make_expression(m_link_map, bounds, m_comparison_type); } #endif diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 6dd7b2152a4..51100551509 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -30,6 +30,10 @@ #include "realm/object-store/impl/realm_coordinator.hpp" #include "realm/object-store/schema.hpp" #include "realm/object-store/sync/generic_network_transport.hpp" +#include +#include +#include +#include #include "realm/object-store/sync/sync_session.hpp" #include "realm/object_id.hpp" #include "realm/query_expression.hpp" @@ -1281,35 +1285,124 @@ TEST_CASE("flx: geospatial", "[sync][flx][app]") { auto subs = create_subscription(realm, "class_restaurant", "queryable_str_field", [](Query q, ColKey c) { return q.equal(c, "synced"); }); + auto make_polygon_filter = [&](const GeoPolygon& polygon) -> bson::BsonDocument { + bson::BsonArray inner{}; + REALM_ASSERT_3(polygon.points.size(), ==, 1); + for (auto& point : polygon.points[0]) { + inner.push_back(bson::BsonArray{point.longitude, point.latitude}); + } + bson::BsonArray coords; + coords.push_back(inner); + bson::BsonDocument geo_bson{{{"type", "Polygon"}, {"coordinates", coords}}}; + bson::BsonDocument filter{ + {"location", bson::BsonDocument{{"$geoWithin", bson::BsonDocument{{"$geometry", geo_bson}}}}}}; + return filter; + }; + auto make_circle_filter = [&](const GeoCircle& circle) -> bson::BsonDocument { + bson::BsonArray coords{circle.center.longitude, circle.center.latitude}; + bson::BsonArray inner; + inner.push_back(coords); + inner.push_back(circle.radius_radians); + bson::BsonDocument filter{ + {"location", bson::BsonDocument{{"$geoWithin", bson::BsonDocument{{"$centerSphere", inner}}}}}}; + return filter; + }; + auto run_query_on_server = [&](const bson::BsonDocument& filter, + std::optional expected_error = {}) -> size_t { + auto remote_client = harness->app()->current_user()->mongo_client("BackingDB"); + auto db = remote_client.db(harness->session().app_session().config.mongo_dbname); + auto restaurant_collection = db["restaurant"]; + bool processed = false; + constexpr int64_t limit = 1000; + size_t matches = 0; + restaurant_collection.count(filter, limit, [&](uint64_t count, util::Optional error) { + processed = true; + if (error) { + if (!expected_error) { + util::format(std::cout, "query error: %1\n", error->reason()); + FAIL(error); + } + else { + std::string_view reason = error->reason(); + auto pos = reason.find(*expected_error); + if (pos == std::string::npos) { + util::format(std::cout, "mismatch error: '%1' and '%2'\n", reason, *expected_error); + FAIL(reason); + } + } + } + matches = size_t(count); + }); + REQUIRE(processed); + return matches; + }; auto sub_res = subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get_no_throw(); CHECK(sub_res.is_ok()); CHECK(realm->get_active_subscription_set().version() == 1); CHECK(realm->get_latest_subscription_set().version() == 1); realm->begin_transaction(); + CppContext c(realm); - Object::create( - c, realm, "restaurant", - std::any(AnyDict{{"_id", INT64_C(1)}, - {"queryable_str_field", "synced"s}, - {"location", AnyDict{{"type", "Point"s}, - {"coordinates", std::vector{1.1, 2.2, 3.3}}}}})); + int64_t pk = 0; + auto add_point = [&](GeoPoint p) { + Object::create( + c, realm, "restaurant", + std::any(AnyDict{ + {"_id", ++pk}, + {"queryable_str_field", "synced"s}, + {"location", AnyDict{{"type", "Point"s}, + {"coordinates", std::vector{p.longitude, p.latitude}}}}})); + }; + std::vector points = { + GeoPoint{-74.006, 40.712800000000001}, GeoPoint{12.568300000000001, 55.676099999999998}, + GeoPoint{12.082599999999999, 55.628}, GeoPoint{-180.1, -90.1}, // invalid + }; + for (auto& point : points) { + add_point(point); + } realm->commit_transaction(); wait_for_upload(*realm); { auto table = realm->read_group().get_table("class_restaurant"); - CHECK(table->size() == 1); + CHECK(table->size() == points.size()); Obj obj = table->get_object_with_primary_key(Mixed{1}); REQUIRE(obj); Geospatial geo = obj.get("location"); REQUIRE(geo.get_type_string() == "Point"); REQUIRE(geo.get_type() == Geospatial::Type::Point); GeoPoint point = geo.get(); - REQUIRE(point.longitude == 1.1); - REQUIRE(point.latitude == 2.2); - REQUIRE(point.get_altitude()); - REQUIRE(*point.get_altitude() == 3.3); + REQUIRE(point.longitude == points[0].longitude); + REQUIRE(point.latitude == points[0].latitude); + REQUIRE(!point.get_altitude()); + ColKey location_col = table->get_column_key("location"); + GeoPolygon bounds{ + {GeoPoint{-80, 40.7128}, GeoPoint{20, 60}, GeoPoint{20, 20}, GeoPoint{-80, 40.7128}}}; + Query query = table->column(location_col).geo_within(Geospatial(bounds)); + size_t local_matches = query.find_all().size(); + REQUIRE(local_matches == 2); + + reset_utils::wait_for_object_to_persist_to_atlas( + harness->app()->current_user(), harness->session().app_session(), "restaurant", {{"_id", pk}}); + + bson::BsonDocument filter = make_polygon_filter(bounds); + size_t server_results = run_query_on_server(filter); + CHECK(server_results == local_matches); + + GeoCircle circle = GeoCircle::from_kms(10, GeoPoint{-180.1, -90.1}); + CHECK_THROWS_WITH(table->column(location_col).geo_within(circle).count(), + "Invalid region in GEOWITHIN query for parameter 'GeoCircle([-180.1, -90.1], " + "0.00156787)': 'Longitude/latitude is out of bounds, lng: -180.1 lat: -90.1'"); + filter = make_circle_filter(circle); + run_query_on_server(filter, "(BadValue) longitude/latitude is out of bounds"); + + circle = GeoCircle::from_kms(-1, GeoPoint{0, 0}); + CHECK_THROWS_WITH(table->column(location_col).geo_within(circle).count(), + "Invalid region in GEOWITHIN query for parameter 'GeoCircle([0, 0], " + "-0.000156787)': 'The radius of a circle must be a non-negative number'"); + filter = make_circle_filter(circle); + run_query_on_server(filter, "(BadValue) radius must be a non-negative number"); } }); } diff --git a/test/test_parser.cpp b/test/test_parser.cpp index 3ccc22e004b..949f74bea8a 100644 --- a/test/test_parser.cpp +++ b/test/test_parser.cpp @@ -5657,99 +5657,132 @@ TEST(Parser_Geospatial) auto geo_table = g.add_table("Position", Table::Type::Embedded); geo_table->add_column(type_String, "type"); geo_table->add_column_list(type_Double, "coordinates"); - table->add_column_list(*geo_table, "links"); - table->add_column(*table, "self_link"); + ColKey self_col = table->add_column(*table, "self_link"); + ColKey list_col = table->add_column_list(*table, "partners"); + ColKey name_col = table->add_column(type_String, "name"); + ColKey col_link = table->add_column(*geo_table, "location"); #if !REALM_ENABLE_GEOSPATIAL auto error = "Support for Geospatial queries is not enabled"; + static_cast(self_col); + static_cast(list_col); + static_cast(name_col); + static_cast(col_link); #define CHECK_QUERY(query) \ do { \ CHECK_THROW_EX(verify_query(test_context, table, query, 1), realm::LogicError, \ CHECK(std::string(e.what()).find(error) != std::string::npos)); \ } while (false) - CHECK_QUERY("link geoWithin geoBox([0.2, 0.2], [0.7, 0.7])"); - CHECK_QUERY("link geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])"); - CHECK_QUERY("link geoWithin geoSphere([0.3, 0.3], 1000.0)"); - CHECK_QUERY("link geoWithin geoSphere([0.3, 0.3, 0.3], 1000.0)"); - CHECK_QUERY("link geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]})"); + CHECK_QUERY("location geoWithin geoBox([0.2, 0.2], [0.7, 0.7])"); + CHECK_QUERY("location geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])"); + CHECK_QUERY("location geoWithin geoCircle([0.3, 0.3], 1000.0)"); + CHECK_QUERY("location geoWithin geoCircle([0.3, 0.3, 0.3], 1000.0)"); + CHECK_QUERY("location geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]})"); - CHECK_THROW_EX(verify_query_sub(test_context, table, "link GEOWITHIN $0", {}, 1), realm::LogicError, + CHECK_THROW_EX(verify_query_sub(test_context, table, "location GEOWITHIN $0", {}, 1), realm::LogicError, CHECK(std::string(e.what()).find(error) != std::string::npos)); #else - auto col_link = table->add_column(*geo_table, "link"); - std::vector point_data = {GeoPoint{0, 0}, GeoPoint{0.5, 0.5}, GeoPoint{1, 1}, GeoPoint{2, 2}}; - for (auto& geo : point_data) { - table->create_object_with_primary_key(ObjectId::gen()).set(col_link, geo); + struct Restaurant { + std::string name; + GeoPoint location; + ObjectId pk; + }; + std::vector data = {{"one", GeoPoint{0, 0}}, + {"two", GeoPoint{0.5, 0.5}}, + {"three", GeoPoint{1, 1}}, + {"four", GeoPoint{2, 2}}, + {"Red Fish Blue Fish", GeoPoint{-123.37039, 48.42437}}, + {"Foo", GeoPoint{-123.36253, 48.42566}}, + {"Superbaba", GeoPoint{-123.3615, 48.4267}}, + {"Sen Zushi", GeoPoint{-123.3579, 48.42398}}}; + for (size_t i = 0; i < data.size(); ++i) { + Restaurant& r = data[i]; + r.pk = ObjectId::gen(); + Obj obj = + table->create_object_with_primary_key(r.pk).set(col_link, Geospatial(r.location)).set(name_col, r.name); + obj.set(self_col, obj.get_key()); + LnkLst lst = obj.get_linklist(list_col); + for (auto it = table->begin(); it != table->end(); ++it) { + if (it->get_key() != obj.get_key()) { + lst.add(it->get_key()); + } + } } // add one object with a null link - table->create_object_with_primary_key(ObjectId::gen()); + table->create_object_with_primary_key(ObjectId::gen()).set(name_col, "empty"); - verify_query(test_context, table, "link geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 1); - verify_query(test_context, table, "link geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])", 1); - verify_query(test_context, table, "link geoWithin geoSphere([0.3, 0.3], 1000.0)", 4); - verify_query(test_context, table, "link geoWithin geoSphere([0.3, 0.3, 0.3], 1000.0)", 4); + verify_query(test_context, table, "location geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 1); + verify_query(test_context, table, "location geoWithin geoBox([0.2, 0.2, 0.2], [0.7, 0.7, 0.7])", 1); + verify_query(test_context, table, "location geoWithin geoCircle([0.3, 0.3], 1)", 4); + verify_query(test_context, table, "location geoWithin geoCircle([0.3, 0.3, 0.3], 1)", 4); verify_query(test_context, table, - "link geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]})", 1); + "location geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]})", 1); verify_query(test_context, table, - "link geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]}, " + "location geoWithin geoPolygon({[0.0, 0.0], [1.0, 0.0], [1, 1], [0, 1], [0.0, 0.0]}, " "{[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75], [0.25, 0.25]})", 0); // polygon with hole - verify_query(test_context, table, "link == NULL", 1); + verify_query(test_context, table, "location == NULL", 1); + // this circle contains "superbaba" and "foo" + Geospatial area = GeoCircle{0.000021950110534, GeoPoint{-123.36197, 48.42626}}; + verify_query(test_context, table, "ANY partners.location GEOWITHIN " + area.to_string(), 2); + verify_query(test_context, table, "ALL partners.location GEOWITHIN " + area.to_string(), 0); + verify_query(test_context, table, "NONE partners.location GEOWITHIN " + area.to_string(), 7); Geospatial box{GeoBox{GeoPoint{0.2, 0.2}, GeoPoint{0.7, 0.7}}}; - Geospatial sphere{GeoCenterSphere{1000, GeoPoint{0.3, 0.3}}}; + Geospatial circle{GeoCircle{1, GeoPoint{0.3, 0.3}}}; Geospatial polygon{GeoPolygon{{GeoPoint{0, 0}, GeoPoint{1, 0}, GeoPoint{1, 1}, GeoPoint{0, 1}, GeoPoint{0, 0}}}}; Geospatial invalid; Geospatial point{GeoPoint{0, 0}}; - std::vector args = {Mixed{&box}, Mixed{&sphere}, Mixed{&polygon}, Mixed{&invalid}, + std::vector args = {Mixed{&box}, Mixed{&circle}, Mixed{&polygon}, Mixed{&invalid}, Mixed{realm::null()}, Mixed{1.2}, Mixed{1000}, Mixed{"string value"}}; - verify_query_sub(test_context, table, "link GEOWITHIN $0", args, 1); - verify_query_sub(test_context, table, "link GEOWITHIN $1", args, 4); - verify_query_sub(test_context, table, "link GEOWITHIN $2", args, 1); + verify_query_sub(test_context, table, "location GEOWITHIN $0", args, 1); + verify_query_sub(test_context, table, "location GEOWITHIN $1", args, 4); + verify_query_sub(test_context, table, "location GEOWITHIN $2", args, 1); CHECK_THROW_EX( verify_query(test_context, table, "_id geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The left hand side of 'geoWithin' must be a link to geoJSON formatted " "data. But the provided type is 'objectId'") != std::string::npos)); - CHECK_THROW_ANY(verify_query(test_context, table, "link geoWithin _id", 0)); - CHECK_THROW_ANY(verify_query(test_context, table, "link geoWithin link", 0)); + CHECK_THROW_ANY(verify_query(test_context, table, "location geoWithin _id", 0)); + CHECK_THROW_ANY(verify_query(test_context, table, "location geoWithin location", 0)); CHECK_THROW_EX( verify_query(test_context, table, "self_link geoWithin geoBox([0.2, 0.2], [0.7, 0.7])", 0), std::runtime_error, CHECK(std::string(e.what()).find("Query 'self_link GEOWITHIN GeoBox([0.2, 0.2], [0.7, 0.7])' links to data " "in the wrong format for a geoWithin query") != std::string::npos)); - CHECK_THROW_EX( - verify_query(test_context, table, "link geoWithin NULL", 1), query_parser::SyntaxError, - CHECK(std::string(e.what()).find("Invalid predicate: 'link geoWithin NULL': syntax error, unexpected null") != - std::string::npos)); - CHECK_THROW_EX( - verify_query(test_context, table, "link geoWithin 1.2", 1), query_parser::SyntaxError, - CHECK(std::string(e.what()).find("Invalid predicate: 'link geoWithin 1.2': syntax error, unexpected float") != - std::string::npos)); - CHECK_THROW_EX(verify_query(test_context, table, "link geoWithin 'test string'", 1), query_parser::SyntaxError, + CHECK_THROW_EX(verify_query(test_context, table, "location geoWithin NULL", 1), query_parser::SyntaxError, + CHECK(std::string(e.what()).find( + "Invalid predicate: 'location geoWithin NULL': syntax error, unexpected null") != + std::string::npos)); + CHECK_THROW_EX(verify_query(test_context, table, "location geoWithin 1.2", 1), query_parser::SyntaxError, CHECK(std::string(e.what()).find( - "Invalid predicate: 'link geoWithin 'test string'': syntax error, unexpected string") != + "Invalid predicate: 'location geoWithin 1.2': syntax error, unexpected float") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $4", args, 1), query_parser::InvalidQueryError, + verify_query(test_context, table, "location geoWithin 'test string'", 1), query_parser::SyntaxError, + CHECK(std::string(e.what()).find( + "Invalid predicate: 'location geoWithin 'test string'': syntax error, unexpected string") != + std::string::npos)); + CHECK_THROW_EX( + verify_query_sub(test_context, table, "location GEOWITHIN $4", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'null'") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $5", args, 1), query_parser::InvalidQueryError, + verify_query_sub(test_context, table, "location GEOWITHIN $5", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'double'") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $6", args, 1), query_parser::InvalidQueryError, + verify_query_sub(test_context, table, "location GEOWITHIN $6", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'int'") != std::string::npos)); CHECK_THROW_EX( - verify_query_sub(test_context, table, "link GEOWITHIN $7", args, 1), query_parser::InvalidQueryError, + verify_query_sub(test_context, table, "location GEOWITHIN $7", args, 1), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a geospatial constant value. " "But the provided type is 'string'") != std::string::npos)); - CHECK_THROW_EX(verify_query_sub(test_context, table, "link GEOWITHIN $3", args, 0), + CHECK_THROW_EX(verify_query_sub(test_context, table, "location GEOWITHIN $3", args, 0), query_parser::InvalidQueryError, CHECK(std::string(e.what()).find("The right hand side of 'geoWithin' must be a valid " "Geospatial value, got 'NULL'") != std::string::npos)); diff --git a/test/test_query_geo.cpp b/test/test_query_geo.cpp index d6ef2802a6b..6cb1cfa402f 100644 --- a/test/test_query_geo.cpp +++ b/test/test_query_geo.cpp @@ -112,8 +112,24 @@ TEST(Geospatial_Assignment) Geospatial geo_box(GeoBox{GeoPoint{1.1, 2.2}, GeoPoint{3.3, 4.4}}); std::string_view err_msg = "The only Geospatial type currently supported for storage is 'point'"; CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_box), err_msg); - Geospatial geo_sphere(GeoCenterSphere{10, GeoPoint{1.1, 2.2}}); - CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_sphere), err_msg); + Geospatial geo_circle(GeoCircle{10, GeoPoint{1.1, 2.2}}); + CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_circle), err_msg); +} + +TEST(Geospatial_invalid_format) +{ + Group g; + TableRef table = setup_with_points(g, {}); + ColKey location_column_key = table->get_column_key("location"); + TableRef location_table = g.get_table("Location"); + CHECK(location_table); + location_table->set_table_type(Table::Type::TopLevel); + auto&& location = table->column(location_column_key); + + Geospatial bounds{GeoBox{GeoPoint{0.2, 0.2}, GeoPoint{0.7, 0.7}}}; + CHECK_THROW_CONTAINING_MESSAGE( + location.geo_within(bounds), + "A GEOWITHIN query can only operate on a link to an embedded class but 'Location' is at the top level"); } TEST(Query_GeoWithinBasics) @@ -140,8 +156,8 @@ TEST(Query_GeoWithinBasics) p = {{{-3.0, -1.0}, {-2.0, -2.0}, {-1.0, -1.0}, {1.5, -1.0}, {-1.0, 1.5}, {-3.0, -1.0}}}; CHECK_EQUAL(location.geo_within(p).count(), 2); - CHECK_EQUAL(location.geo_within(GeoCenterSphere::from_kms(150.0, GeoPoint{1.0, 0.5})).count(), 3); - CHECK_EQUAL(location.geo_within(GeoCenterSphere::from_kms(90.0, GeoPoint{-1.5, -1.5})).count(), 2); + CHECK_EQUAL(location.geo_within(GeoCircle::from_kms(150.0, GeoPoint{1.0, 0.5})).count(), 3); + CHECK_EQUAL(location.geo_within(GeoCircle::from_kms(90.0, GeoPoint{-1.5, -1.5})).count(), 2); CHECK_THROW_CONTAINING_MESSAGE(location.geo_within(Geospatial{GeoPoint{0.0, 0.0}}), "Invalid region in GEOWITHIN query for parameter 'GeoPoint([0, 0])': 'A point " @@ -151,6 +167,55 @@ TEST(Query_GeoWithinBasics) "the right hand side of a GEOWITHIN query"); } +TEST(Geospatial_ListOfPrimitives) +{ + auto make_list_with_points = [](Obj obj, const std::vector& points) { + ColKey list_col = obj.get_table()->get_column_key("locations"); + LnkLst list = obj.get_linklist(list_col); + for (const GeoPoint& point : points) { + Obj location = list.create_and_insert_linked_object(0); + Geospatial{point}.assign_to(location); + } + }; + Group g; + std::vector data = {GeoPoint{0, 0}, GeoPoint{0, 0}, GeoPoint{0, 0}, GeoPoint{0, 0}}; + TableRef table = setup_with_points(g, data); + TableRef location_table = g.get_table("Location"); + ColKey list_col = table->add_column_list(*location_table, "locations"); + CHECK(table->size() == 4); + auto obj_it = table->begin(); + make_list_with_points(*obj_it, {GeoPoint{1, 1}, GeoPoint{2, 2}}); + make_list_with_points(*++obj_it, {GeoPoint{2, 2}, GeoPoint{3, 3}}); + make_list_with_points(*++obj_it, {GeoPoint{1, 1}, GeoPoint{1, 1}, GeoPoint{1, 1}}); + // the fourth object has no elements in the list + + using GC = GeoCircle; + const double r = 0.00872665; + util::Optional ect; + + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 1); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 0); + ect = ExpressionComparisonType::Any; + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 2); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 1); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 0); + + ect = ExpressionComparisonType::All; + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 1); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 0); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 0); + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 0); + + ect = ExpressionComparisonType::None; + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {1, 1}}).count(), 2); // 1, 3 + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {2, 2}}).count(), 2); // 2, 3 + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {3, 3}}).count(), 3); // 0, 2, 3 + CHECK_EQUAL(table->column(list_col, ect).geo_within(GC{r, {4, 4}}).count(), 4); // 0, 1, 2, 3 +} + TEST(Geospatial_MeridianQuery) { // Check that geoWithin works across the meridian. We insert points @@ -179,7 +244,7 @@ TEST(Geospatial_EquatorQuery) CHECK_EQUAL(num_results, 1); } -TEST(Geospatial_CenterSphere) +TEST(Geospatial_Circle) { Group g; std::vector points = {GeoPoint{-118.2400013, 34.073893}, GeoPoint{-118.2400012, 34.073894}, @@ -187,9 +252,9 @@ TEST(Geospatial_CenterSphere) TableRef table = setup_with_points(g, points); ColKey location_column_key = table->get_column_key("location"); ColKey id_col = table->get_primary_key_column(); - Geospatial geo_sphere{GeoCenterSphere{0.44915760491198753, GeoPoint{-118.240013, 34.073893}}}; + Geospatial geo_circle{GeoCircle{0.44915760491198753, GeoPoint{-118.240013, 34.073893}}}; - Query query = table->column(location_column_key).geo_within(geo_sphere); + Query query = table->column(location_column_key).geo_within(geo_circle); CHECK_EQUAL(query.count(), 2); CHECK_EQUAL((query && table->column(id_col) == 0).count(), 1); CHECK_EQUAL((query && table->column(id_col) == 1).count(), 1); @@ -204,7 +269,7 @@ TEST(Geospatial_GeoWithinShapes) ColKey location_column_key = table->get_column_key("location"); std::vector shapes = { - Geospatial{GeoCenterSphere{1, GeoPoint{0, 0}}}, + Geospatial{GeoCircle{1, GeoPoint{0, 0}}}, Geospatial{GeoBox{GeoPoint{-5, -5}, GeoPoint{5, 5}}}, Geospatial{GeoPolygon{{{-5, -5}, {5, -5}, {5, 5}, {-5, 5}, {-5, -5}}}}, };