diff --git a/src/clients/meta/MetaClient.cpp b/src/clients/meta/MetaClient.cpp index d4766213d32..1d56abdce57 100644 --- a/src/clients/meta/MetaClient.cpp +++ b/src/clients/meta/MetaClient.cpp @@ -346,6 +346,8 @@ bool MetaClient::loadSchemas(GraphSpaceID spaceId, bool hasDef = col.default_value_ref().has_value(); auto& colType = col.get_type(); size_t len = colType.type_length_ref().has_value() ? *colType.get_type_length() : 0; + cpp2::GeoShape geoShape = + colType.geo_shape_ref().has_value() ? *colType.get_geo_shape() : cpp2::GeoShape::ANY; bool nullable = col.nullable_ref().has_value() ? *col.get_nullable() : false; Expression* defaultValueExpr = nullptr; if (hasDef) { @@ -359,8 +361,12 @@ bool MetaClient::loadSchemas(GraphSpaceID spaceId, } } - schema->addField( - col.get_name(), colType.get_type(), len, nullable, hasDef ? defaultValueExpr : nullptr); + schema->addField(col.get_name(), + colType.get_type(), + len, + nullable, + hasDef ? defaultValueExpr : nullptr, + geoShape); }; for (auto& tagIt : tagItemVec) { diff --git a/src/codec/RowReaderV2.cpp b/src/codec/RowReaderV2.cpp index 0e8bb247c75..39442ac30ca 100644 --- a/src/codec/RowReaderV2.cpp +++ b/src/codec/RowReaderV2.cpp @@ -176,8 +176,22 @@ Value RowReaderV2::getValueByIndex(const int64_t index) const noexcept { return dt; } case meta::cpp2::PropertyType::GEOGRAPHY: { - // TODO(jie) - return Geography(); + int32_t strOffset; + int32_t strLen; + memcpy(reinterpret_cast(&strOffset), &data_[offset], sizeof(int32_t)); + memcpy(reinterpret_cast(&strLen), &data_[offset + sizeof(int32_t)], sizeof(int32_t)); + if (static_cast(strOffset) == data_.size() && strLen == 0) { + return Value::kEmpty; // Is it ok to return Value::kEmpty? + } + CHECK_LT(strOffset, data_.size()); + auto wkb = std::string(&data_[strOffset], strLen); + // Parse a geography from the wkb, normalize it and then verify its validity. + auto geogRet = Geography::fromWKB(wkb, true, true); + if (!geogRet.ok()) { + LOG(ERROR) << "Geography::fromWKB failed: " << geogRet.status(); + return Value::kNullBadData; // Is it ok to return Value::kNullBadData? + } + return std::move(geogRet).value(); } case meta::cpp2::PropertyType::UNKNOWN: break; diff --git a/src/codec/RowWriterV2.cpp b/src/codec/RowWriterV2.cpp index 1953e63d29a..a7073e41393 100644 --- a/src/codec/RowWriterV2.cpp +++ b/src/codec/RowWriterV2.cpp @@ -127,7 +127,7 @@ RowWriterV2::RowWriterV2(RowReader& reader) : RowWriterV2(reader.getSchema()) { set(i, v.moveDateTime()); break; case Value::Type::GEOGRAPHY: - // TODO(jie) + set(i, v.moveGeography()); break; default: LOG(FATAL) << "Invalid data: " << v << ", type: " << v.typeName(); @@ -207,8 +207,7 @@ WriteResult RowWriterV2::setValue(ssize_t index, const Value& val) noexcept { case Value::Type::DATETIME: return write(index, val.getDateTime()); case Value::Type::GEOGRAPHY: - // TODO(jie) - return WriteResult::TYPE_MISMATCH; + return write(index, val.getGeography()); default: return WriteResult::TYPE_MISMATCH; } @@ -762,6 +761,17 @@ WriteResult RowWriterV2::write(ssize_t index, const DateTime& v) noexcept { return WriteResult::SUCCEEDED; } +WriteResult RowWriterV2::write(ssize_t index, const Geography& v) noexcept { + auto field = schema_->field(index); + auto geoShape = field->geoShape(); + if (geoShape != meta::cpp2::GeoShape::ANY && + folly::to(geoShape) != folly::to(v.shape())) { + return WriteResult::TYPE_MISMATCH; + } + std::string wkb = v.asWKB(); + return write(index, folly::StringPiece(wkb)); +} + WriteResult RowWriterV2::checkUnsetFields() noexcept { DefaultValueContext expCtx; for (size_t i = 0; i < schema_->getNumFields(); i++) { @@ -802,7 +812,7 @@ WriteResult RowWriterV2::checkUnsetFields() noexcept { r = write(i, defVal.getDateTime()); break; case Value::Type::GEOGRAPHY: - // TODO(jie) + r = write(i, defVal.getGeography()); break; default: LOG(FATAL) << "Unsupported default value type: " << defVal.typeName() diff --git a/src/codec/RowWriterV2.h b/src/codec/RowWriterV2.h index d3bec72064f..16030a00afd 100644 --- a/src/codec/RowWriterV2.h +++ b/src/codec/RowWriterV2.h @@ -63,6 +63,7 @@ enum class WriteResult { TIMESTAMP (8 bytes) DATE (4 bytes) DATETIME (15 bytes) + GEOGRAPHY (8 bytes) * All except STRING typed properties are stored in-place. The STRING property stored the offset of the string content in the first 4 bytes and the length @@ -188,6 +189,8 @@ class RowWriterV2 { WriteResult write(ssize_t index, const Date& v) noexcept; WriteResult write(ssize_t index, const Time& v) noexcept; WriteResult write(ssize_t index, const DateTime& v) noexcept; + + WriteResult write(ssize_t index, const Geography& v) noexcept; }; } // namespace nebula diff --git a/src/codec/test/ResultSchemaProvider.cpp b/src/codec/test/ResultSchemaProvider.cpp index 359ca2bc80f..80b23ae1b26 100644 --- a/src/codec/test/ResultSchemaProvider.cpp +++ b/src/codec/test/ResultSchemaProvider.cpp @@ -23,14 +23,16 @@ ResultSchemaProvider::ResultSchemaField::ResultSchemaField(std::string name, bool nullable, int32_t offset, size_t nullFlagPos, - Expression* defaultValue) + Expression* defaultValue, + meta::cpp2::GeoShape geoShape) : name_(std::move(name)), type_(type), size_(size), nullable_(nullable), offset_(offset), nullFlagPos_(nullFlagPos), - defaultValue_(defaultValue) {} + defaultValue_(defaultValue), + geoShape_(geoShape) {} const char* ResultSchemaProvider::ResultSchemaField::name() const { return name_.c_str(); } @@ -50,6 +52,8 @@ size_t ResultSchemaProvider::ResultSchemaField::offset() const { return offset_; size_t ResultSchemaProvider::ResultSchemaField::nullFlagPos() const { return nullFlagPos_; } +meta::cpp2::GeoShape ResultSchemaProvider::ResultSchemaField::geoShape() const { return geoShape_; } + /*********************************** * * ResultSchemaProvider diff --git a/src/codec/test/ResultSchemaProvider.h b/src/codec/test/ResultSchemaProvider.h index f0a6bff44ca..4c51288163e 100644 --- a/src/codec/test/ResultSchemaProvider.h +++ b/src/codec/test/ResultSchemaProvider.h @@ -22,7 +22,8 @@ class ResultSchemaProvider : public meta::SchemaProviderIf { bool nullable, int32_t offset, size_t nullFlagPos, - Expression* defaultValue = nullptr); + Expression* defaultValue = nullptr, + meta::cpp2::GeoShape = meta::cpp2::GeoShape::ANY); const char* name() const override; meta::cpp2::PropertyType type() const override; @@ -32,6 +33,7 @@ class ResultSchemaProvider : public meta::SchemaProviderIf { size_t size() const override; size_t offset() const override; size_t nullFlagPos() const override; + meta::cpp2::GeoShape geoShape() const override; private: std::string name_; @@ -41,6 +43,7 @@ class ResultSchemaProvider : public meta::SchemaProviderIf { int32_t offset_; size_t nullFlagPos_; Expression* defaultValue_; + meta::cpp2::GeoShape geoShape_; }; public: diff --git a/src/codec/test/SchemaWriter.cpp b/src/codec/test/SchemaWriter.cpp index d7c11ebe1eb..41e56bd22cd 100644 --- a/src/codec/test/SchemaWriter.cpp +++ b/src/codec/test/SchemaWriter.cpp @@ -17,7 +17,8 @@ SchemaWriter& SchemaWriter::appendCol(folly::StringPiece name, PropertyType type, int32_t fixedStrLen, bool nullable, - Expression* defaultValue) noexcept { + Expression* defaultValue, + meta::cpp2::GeoShape geoShape) noexcept { using folly::hash::SpookyHashV2; uint64_t hash = SpookyHashV2::Hash64(name.data(), name.size(), 0); DCHECK(nameIndex_.find(hash) == nameIndex_.end()); @@ -87,7 +88,8 @@ SchemaWriter& SchemaWriter::appendCol(folly::StringPiece name, nullFlagPos = numNullableFields_++; } - columns_.emplace_back(name.toString(), type, size, nullable, offset, nullFlagPos, defaultValue); + columns_.emplace_back( + name.toString(), type, size, nullable, offset, nullFlagPos, defaultValue, geoShape); nameIndex_.emplace(std::make_pair(hash, columns_.size() - 1)); return *this; diff --git a/src/codec/test/SchemaWriter.h b/src/codec/test/SchemaWriter.h index 516d120f519..55c5dfb356d 100644 --- a/src/codec/test/SchemaWriter.h +++ b/src/codec/test/SchemaWriter.h @@ -21,7 +21,8 @@ class SchemaWriter : public ResultSchemaProvider { meta::cpp2::PropertyType type, int32_t fixedStrLen = 0, bool nullable = false, - Expression* defaultValue = nullptr) noexcept; + Expression* defaultValue = nullptr, + meta::cpp2::GeoShape geoShape = meta::cpp2::GeoShape::ANY) noexcept; private: }; diff --git a/src/common/datatypes/Geography.cpp b/src/common/datatypes/Geography.cpp index 922e2525301..aef9a457599 100644 --- a/src/common/datatypes/Geography.cpp +++ b/src/common/datatypes/Geography.cpp @@ -19,20 +19,25 @@ namespace nebula { +constexpr double kMaxLongitude = 180.0; +constexpr double kMaxLatitude = 90.0; + void Coordinate::normalize() { // Reduce the x(longitude) to the range [-180, 180] degrees x = std::remainder(x, 360.0); // Reduce the y(latitude) to the range [-90, 90] degrees double tmp = remainder(y, 360.0); - if (tmp > 90.0) { + if (tmp > kMaxLatitude) { y = 180.0 - tmp; - } else if (tmp < -90.0) { + } else if (tmp < -kMaxLatitude) { y = -180.0 - tmp; } } -bool Coordinate::isValid() const { return std::abs(x) <= 180.0 && std::abs(y) <= 90.0; } +bool Coordinate::isValid() const { + return std::abs(x) <= kMaxLongitude && std::abs(y) <= kMaxLatitude; +} void Point::normalize() {} @@ -46,6 +51,7 @@ bool LineString::isValid() const { return false; } auto s2Region = geo::GeoUtils::s2RegionFromGeography(*this); + CHECK_NOTNULL(s2Region); return static_cast(s2Region.get())->IsValid(); } @@ -67,6 +73,7 @@ bool Polygon::isValid() const { } } auto s2Region = geo::GeoUtils::s2RegionFromGeography(*this); + CHECK_NOTNULL(s2Region); return static_cast(s2Region.get())->IsValid(); } @@ -91,6 +98,27 @@ StatusOr Geography::fromWKT(const std::string& wkt, return geog; } +StatusOr Geography::fromWKB(const std::string& wkb, + bool needNormalize, + bool verifyValidity) { + auto geogRet = geo::WKBReader().read(wkb); + if (!geogRet.ok()) { + return geogRet; + } + auto geog = std::move(geogRet).value(); + if (needNormalize) { + geog.normalize(); + } + if (verifyValidity) { + if (!geog.isValid()) { + return Status::Error("Failed to parse an valid Geography instance from the wkb `%s'", + wkb.c_str()); + } + } + + return geog; +} + GeoShape Geography::shape() const { switch (geo_.index()) { case 0: @@ -184,6 +212,32 @@ bool Geography::isValid() const { } } +Point Geography::centroid() const { + switch (shape()) { + case GeoShape::POINT: { + return this->point(); + } + case GeoShape::LINESTRING: { + auto s2Region = geo::GeoUtils::s2RegionFromGeography(*this); + CHECK_NOTNULL(s2Region); + S2Point s2Point = static_cast(s2Region.get())->GetCentroid(); + return Point(geo::GeoUtils::coordinateFromS2Point(s2Point)); + } + case GeoShape::POLYGON: { + auto s2Region = geo::GeoUtils::s2RegionFromGeography(*this); + CHECK_NOTNULL(s2Region); + S2Point s2Point = static_cast(s2Region.get())->GetCentroid(); + return Point(geo::GeoUtils::coordinateFromS2Point(s2Point)); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return Point(); + } + } +} + std::string Geography::asWKT() const { return geo::WKTWriter().write(*this); } std::string Geography::asWKB() const { return geo::WKBWriter().write(*this); } diff --git a/src/common/datatypes/Geography.h b/src/common/datatypes/Geography.h index 5f2ecd31790..5e9b92de31c 100644 --- a/src/common/datatypes/Geography.h +++ b/src/common/datatypes/Geography.h @@ -58,8 +58,9 @@ struct Coordinate { } void __clear() { clear(); } - // TODO(jie) compare double correctly - bool operator==(const Coordinate& rhs) const { return x == rhs.x && y == rhs.y; } + bool operator==(const Coordinate& rhs) const { + return std::abs(x - rhs.x) < kEpsilon && std::abs(y - rhs.y) < kEpsilon; + } bool operator!=(const Coordinate& rhs) const { return !(*this == rhs); } bool operator<(const Coordinate& rhs) const { if (x != rhs.x) { @@ -135,6 +136,10 @@ struct Geography { bool needNormalize = false, bool verifyValidity = false); + static StatusOr fromWKB(const std::string& wkb, + bool needNormalize = false, + bool verifyValidity = false); + Geography() {} Geography(const Point& v) : geo_(v) {} // NOLINT Geography(Point&& v) : geo_(std::move(v)) {} // NOLINT @@ -156,6 +161,8 @@ struct Geography { void normalize(); bool isValid() const; + Point centroid() const; + std::string asWKT() const; std::string asWKB() const; diff --git a/src/common/function/FunctionManager.cpp b/src/common/function/FunctionManager.cpp index a25c38fdcf2..07b402e74bb 100644 --- a/src/common/function/FunctionManager.cpp +++ b/src/common/function/FunctionManager.cpp @@ -334,9 +334,15 @@ std::unordered_map> FunctionManager::typ { TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::STRING), }}, - {"st_asbinary", + // This function requires binary data to be support first. + // {"st_asbinary", + // { + // TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::STRING), + // }}, + // geo transformations + {"st_centroid", { - TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::STRING), + TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::GEOGRAPHY), }}, // geo accessors {"st_isvalid", @@ -362,6 +368,11 @@ std::unordered_map> FunctionManager::typ { TypeSignature({Value::Type::GEOGRAPHY, Value::Type::GEOGRAPHY, Value::Type::FLOAT}, Value::Type::BOOL), + TypeSignature({Value::Type::GEOGRAPHY, + Value::Type::GEOGRAPHY, + Value::Type::FLOAT, + Value::Type::BOOL}, + Value::Type::BOOL), }}, // geo measures {"st_distance", @@ -2401,8 +2412,22 @@ FunctionManager::FunctionManager() { return g.asWKT(); }; } + // { + // auto &attr = functions_["st_asbinary"]; + // attr.minArity_ = 1; + // attr.maxArity_ = 1; + // attr.isPure_ = true; + // attr.body_ = [](const auto &args) -> Value { + // if (!args[0].get().isGeography()) { + // return Value::kNullBadType; + // } + // const Geography &g = args[0].get().getGeography(); + // return g.asWKBHex(); + // }; + // } + // geo transformations { - auto &attr = functions_["st_asbinary"]; + auto &attr = functions_["st_centroid"]; attr.minArity_ = 1; attr.maxArity_ = 1; attr.isPure_ = true; @@ -2410,8 +2435,7 @@ FunctionManager::FunctionManager() { if (!args[0].get().isGeography()) { return Value::kNullBadType; } - const Geography &g = args[0].get().getGeography(); - return g.asWKBHex(); + return Geography(args[0].get().getGeography().centroid()); }; } // geo accessors @@ -2469,17 +2493,24 @@ FunctionManager::FunctionManager() { { auto &attr = functions_["st_dwithin"]; attr.minArity_ = 3; - attr.maxArity_ = 3; + attr.maxArity_ = 4; attr.isPure_ = true; attr.body_ = [](const auto &args) -> Value { if (!args[0].get().isGeography() || !args[1].get().isGeography() || !args[2].get().isFloat()) { return Value::kNullBadType; } + bool exclusive = false; + if (args.size() == 4) { + if (!args[3].get().isBool()) { + return Value::kNullBadType; + } + exclusive = args[3].get().getBool(); + } return geo::GeoFunction::dWithin(args[0].get().getGeography(), args[1].get().getGeography(), args[2].get().getFloat(), - true); + exclusive); }; } // geo measures @@ -2515,6 +2546,11 @@ FunctionManager::FunctionManager() { return Value::kNullBadData; } } + const auto &geog = args[0].get().getGeography(); + if (geog.shape() != GeoShape::POINT) { + LOG(ERROR) << "S2_CellIdFromPoint only accepts point argument"; + return Value::kNullBadData; + } // TODO(jie) Should return uint64_t Value uint64_t cellId = geo::GeoFunction::s2CellIdFromPoint(args[0].get().getGeography(), level); const char *tmp = reinterpret_cast(&cellId); diff --git a/src/common/geo/CMakeLists.txt b/src/common/geo/CMakeLists.txt index f7eafd34bb0..a9645de0833 100644 --- a/src/common/geo/CMakeLists.txt +++ b/src/common/geo/CMakeLists.txt @@ -3,5 +3,10 @@ # This source code is licensed under Apache 2.0 License, # attached with Common Clause Condition 1.0, found in the LICENSES directory. +nebula_add_library( + geo_index_obj OBJECT + GeoIndex.cpp +) + nebula_add_subdirectory(io) nebula_add_subdirectory(test) diff --git a/src/common/geo/GeoFunction.cpp b/src/common/geo/GeoFunction.cpp index 84bdc7ecb6d..5ceedb47b54 100644 --- a/src/common/geo/GeoFunction.cpp +++ b/src/common/geo/GeoFunction.cpp @@ -23,7 +23,7 @@ namespace geo { bool GeoFunction::intersects(const Geography& a, const Geography& b) { auto aRegion = a.asS2(); auto bRegion = b.asS2(); - if (!aRegion || !bRegion) { + if (UNLIKELY(!aRegion || !bRegion)) { return false; } @@ -99,7 +99,7 @@ bool GeoFunction::intersects(const Geography& a, const Geography& b) { bool GeoFunction::covers(const Geography& a, const Geography& b) { auto aRegion = a.asS2(); auto bRegion = b.asS2(); - if (!aRegion || !bRegion) { + if (UNLIKELY(!aRegion || !bRegion)) { return false; } @@ -178,10 +178,10 @@ bool GeoFunction::covers(const Geography& a, const Geography& b) { bool GeoFunction::coveredBy(const Geography& a, const Geography& b) { return covers(b, a); } -bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distance, bool inclusive) { +bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distance, bool exclusive) { auto aRegion = a.asS2(); auto bRegion = b.asS2(); - if (!aRegion || !bRegion) { + if (UNLIKELY(!aRegion || !bRegion)) { return false; } @@ -192,15 +192,15 @@ bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distanc case GeoShape::POINT: { const S2Point& bPoint = static_cast(bRegion.get())->point(); double closestDistance = S2Earth::GetDistanceMeters(aPoint, bPoint); - return inclusive ? closestDistance <= distance : closestDistance < distance; + return exclusive ? closestDistance < distance : closestDistance <= distance; } case GeoShape::LINESTRING: { S2Polyline* bLine = static_cast(bRegion.get()); - return s2PointAndS2PolylineAreWithinDistance(aPoint, bLine, distance, inclusive); + return s2PointAndS2PolylineAreWithinDistance(aPoint, bLine, distance, exclusive); } case GeoShape::POLYGON: { S2Polygon* bPolygon = static_cast(bRegion.get()); - return s2PointAndS2PolygonAreWithinDistance(aPoint, bPolygon, distance, inclusive); + return s2PointAndS2PolygonAreWithinDistance(aPoint, bPolygon, distance, exclusive); } case GeoShape::UNKNOWN: default: { @@ -215,7 +215,7 @@ bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distanc switch (b.shape()) { case GeoShape::POINT: { const S2Point& bPoint = static_cast(bRegion.get())->point(); - return s2PointAndS2PolylineAreWithinDistance(bPoint, aLine, distance, inclusive); + return s2PointAndS2PolylineAreWithinDistance(bPoint, aLine, distance, exclusive); } case GeoShape::LINESTRING: { S2Polyline* bLine = static_cast(bRegion.get()); @@ -224,16 +224,16 @@ bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distanc bIndex.Add(std::make_unique(bLine)); S2ClosestEdgeQuery query(&aIndex); S2ClosestEdgeQuery::ShapeIndexTarget target(&bIndex); - if (inclusive) { - return query.IsDistanceLessOrEqual( - &target, S2Earth::ToChordAngle(util::units::Meters(distance))); + if (exclusive) { + return query.IsDistanceLess(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); } - return query.IsDistanceLess(&target, - S2Earth::ToChordAngle(util::units::Meters(distance))); + return query.IsDistanceLessOrEqual(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); } case GeoShape::POLYGON: { S2Polygon* bPolygon = static_cast(bRegion.get()); - return s2PolylineAndS2PolygonAreWithinDistance(aLine, bPolygon, distance, inclusive); + return s2PolylineAndS2PolygonAreWithinDistance(aLine, bPolygon, distance, exclusive); } case GeoShape::UNKNOWN: default: { @@ -248,22 +248,22 @@ bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distanc switch (b.shape()) { case GeoShape::POINT: { const S2Point& bPoint = static_cast(bRegion.get())->point(); - return s2PointAndS2PolygonAreWithinDistance(bPoint, aPolygon, distance, inclusive); + return s2PointAndS2PolygonAreWithinDistance(bPoint, aPolygon, distance, exclusive); } case GeoShape::LINESTRING: { S2Polyline* bLine = static_cast(bRegion.get()); - return s2PolylineAndS2PolygonAreWithinDistance(bLine, aPolygon, distance, inclusive); + return s2PolylineAndS2PolygonAreWithinDistance(bLine, aPolygon, distance, exclusive); } case GeoShape::POLYGON: { S2Polygon* bPolygon = static_cast(bRegion.get()); S2ClosestEdgeQuery query(&aPolygon->index()); S2ClosestEdgeQuery::ShapeIndexTarget target(&bPolygon->index()); - if (inclusive) { - return query.IsDistanceLessOrEqual( - &target, S2Earth::ToChordAngle(util::units::Meters(distance))); + if (exclusive) { + return query.IsDistanceLess(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); } - return query.IsDistanceLess(&target, - S2Earth::ToChordAngle(util::units::Meters(distance))); + return query.IsDistanceLessOrEqual(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); } case GeoShape::UNKNOWN: default: { @@ -287,7 +287,7 @@ bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distanc double GeoFunction::distance(const Geography& a, const Geography& b) { auto aRegion = a.asS2(); auto bRegion = b.asS2(); - if (!aRegion || !bRegion) { + if (UNLIKELY(!aRegion || !bRegion)) { return -1.0; } @@ -381,7 +381,7 @@ double GeoFunction::distance(const Geography& a, const Geography& b) { uint64_t GeoFunction::s2CellIdFromPoint(const Geography& a, int level) { auto aRegion = a.asS2(); - if (!aRegion) { + if (UNLIKELY(!aRegion)) { return -1; } if (level < 0 || level > 30) { @@ -410,7 +410,7 @@ uint64_t GeoFunction::s2CellIdFromPoint(const Geography& a, int level) { std::vector GeoFunction::s2CoveringCellIds( const Geography& a, int minLevel, int maxLevel, int maxCells, double bufferInMeters) { auto aRegion = a.asS2(); - if (!aRegion) { + if (UNLIKELY(!aRegion)) { return {}; } if (minLevel < 0 || minLevel > 30) { @@ -507,47 +507,41 @@ double GeoFunction::distanceOfS2PolygonWithS2Point(const S2Polygon* aPolygon, bool GeoFunction::s2PointAndS2PolylineAreWithinDistance(const S2Point& aPoint, const S2Polyline* bLine, double distance, - bool inclusive) { + bool exclusive) { MutableS2ShapeIndex bIndex; bIndex.Add(std::make_unique(bLine)); S2ClosestEdgeQuery query(&bIndex); S2ClosestEdgeQuery::PointTarget target(aPoint); - if (inclusive) { - return query.IsDistanceLessOrEqual(&target, - S2Earth::ToChordAngle(util::units::Meters(distance))); - } else { + if (exclusive) { return query.IsDistanceLess(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); } + return query.IsDistanceLessOrEqual(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); } bool GeoFunction::s2PointAndS2PolygonAreWithinDistance(const S2Point& aPoint, const S2Polygon* bPolygon, double distance, - bool inclusive) { + bool exclusive) { S2ClosestEdgeQuery query(&bPolygon->index()); S2ClosestEdgeQuery::PointTarget target(aPoint); - if (inclusive) { - return query.IsDistanceLessOrEqual(&target, - S2Earth::ToChordAngle(util::units::Meters(distance))); - } else { + if (exclusive) { return query.IsDistanceLess(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); } + return query.IsDistanceLessOrEqual(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); } bool GeoFunction::s2PolylineAndS2PolygonAreWithinDistance(const S2Polyline* aLine, const S2Polygon* bPolygon, double distance, - bool inclusive) { + bool exclusive) { MutableS2ShapeIndex aIndex; aIndex.Add(std::make_unique(aLine)); S2ClosestEdgeQuery::ShapeIndexTarget target(&aIndex); S2ClosestEdgeQuery query(&bPolygon->index()); - if (inclusive) { - return query.IsDistanceLessOrEqual(&target, - S2Earth::ToChordAngle(util::units::Meters(distance))); - } else { + if (exclusive) { return query.IsDistanceLess(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); } + return query.IsDistanceLessOrEqual(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); } } // namespace geo diff --git a/src/common/geo/GeoFunction.h b/src/common/geo/GeoFunction.h index 5b37a998c15..7480a3187e4 100644 --- a/src/common/geo/GeoFunction.h +++ b/src/common/geo/GeoFunction.h @@ -29,7 +29,10 @@ class GeoFunction { // Returns true if any of a is within distance meters of b. // We don't need to find the closest points. We just need to find the first point pair whose // distance is less than or less equal than the given distance. (Early quit) - static bool dWithin(const Geography& a, const Geography& b, double distance, bool inclusive); + static bool dWithin(const Geography& a, + const Geography& b, + double distance, + bool exclusive = false); // Return the closest distance in meters of a and b. static double distance(const Geography& a, const Geography& b); @@ -56,17 +59,17 @@ class GeoFunction { static bool s2PointAndS2PolylineAreWithinDistance(const S2Point& aPoint, const S2Polyline* bLine, double distance, - bool inclusive); + bool exclusive); static bool s2PointAndS2PolygonAreWithinDistance(const S2Point& aPoint, const S2Polygon* bPolygon, double distance, - bool inclusive); + bool exclusive); static bool s2PolylineAndS2PolygonAreWithinDistance(const S2Polyline* aLine, const S2Polygon* bPolygon, double distance, - bool inclusive); + bool exclusive); }; } // namespace geo diff --git a/src/common/geo/GeoIndex.cpp b/src/common/geo/GeoIndex.cpp new file mode 100644 index 00000000000..9de3b2d25d7 --- /dev/null +++ b/src/common/geo/GeoIndex.cpp @@ -0,0 +1,182 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This sourc_e code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "common/geo/GeoIndex.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/datatypes/Geography.h" +#include "common/utils/IndexKeyUtils.h" +#include "interface/gen-cpp2/storage_types.h" + +namespace nebula { +namespace geo { + +nebula::storage::cpp2::IndexColumnHint ScanRange::toIndexColumnHint() { + nebula::storage::cpp2::IndexColumnHint hint; + // column_name should be set by the caller + if (isRangeScan) { + hint.set_scan_type(nebula::storage::cpp2::ScanType::RANGE); + hint.set_begin_value( + IndexKeyUtils::encodeUint64(rangeMin)); // Encode uint64_t as string in advance + hint.set_end_value(IndexKeyUtils::encodeUint64(rangeMax)); + } else { + hint.set_scan_type(nebula::storage::cpp2::ScanType::PREFIX); + hint.set_begin_value(IndexKeyUtils::encodeUint64(rangeMin)); + } + return hint; +} + +std::vector GeoIndex::indexCells(const Geography& g) const noexcept { + auto r = g.asS2(); + if (UNLIKELY(!r)) { + return {}; + } + + auto cells = coveringCells(*r, g.shape() == GeoShape::POINT); + std::vector cellIds; + cellIds.reserve(cells.size()); + for (auto& cell : cells) { + cellIds.push_back(cell.id()); + } + return cellIds; +} + +std::vector GeoIndex::intersects(const Geography& g) const noexcept { + auto r = g.asS2(); + if (UNLIKELY(!r)) { + return {}; + } + + return intersects(*r, g.shape() == GeoShape::POINT); +} + +// covers degenerates to intersects currently +std::vector GeoIndex::covers(const Geography& g) const noexcept { return intersects(g); } + +// coveredBy degenerates to intersects currently +std::vector GeoIndex::coveredBy(const Geography& g) const noexcept { + return intersects(g); +} + +std::vector GeoIndex::dWithin(const Geography& g, double distance) const noexcept { + auto r = g.asS2(); + if (UNLIKELY(!r)) { + return {}; + } + + S1Angle radius = S2Earth::ToAngle(util::units::Meters(distance)); + // First expand the region, then build the covering + switch (g.shape()) { + case GeoShape::POINT: { + const S2Point& gPoint = static_cast(r.get())->point(); + S2Cap gCap(gPoint, radius); + return intersects(gCap); + } + case GeoShape::LINESTRING: { + S2Polyline* gLine = static_cast(r.get()); + MutableS2ShapeIndex index; + index.Add(std::make_unique(gLine)); + S2ShapeIndexBufferedRegion gBuffer(&index, radius); + return intersects(gBuffer); + } + case GeoShape::POLYGON: { + S2Polygon* gPolygon = static_cast(r.get()); + S2ShapeIndexBufferedRegion gBuffer(&gPolygon->index(), radius); + return intersects(gBuffer); + } + default: + LOG(FATAL) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return {}; + } +} + +std::vector GeoIndex::intersects(const S2Region& r, bool isPoint) const noexcept { + auto cells = coveringCells(r, isPoint); + std::vector scanRanges; + for (const S2CellId& cellId : cells) { + if (cellId.is_leaf()) { + scanRanges.emplace_back(cellId.id()); + } else { + scanRanges.emplace_back(cellId.range_min().id(), cellId.range_max().id()); + } + } + + // For the indexed column which only contains point, we don't need to get the ancestor cells, + // because the point is at max level(30). + if (!pointsOnly_) { + auto ancestors = ancestorCells(cells); + for (const S2CellId& cellId : ancestors) { + scanRanges.emplace_back(cellId.id()); + } + } + + return scanRanges; +} + +std::vector GeoIndex::coveringCells(const S2Region& r, bool isPoint) const noexcept { + // Currently we don't apply region coverer params to point, because it's useless. + // Point always use level 30. + if (isPoint) { + const S2Point& gPoint = static_cast(&r)->point(); + return {S2CellId(gPoint)}; + } + S2RegionCoverer rc(rcParams_.s2RegionCovererOpts()); + std::vector covering; + rc.GetCovering(r, &covering); + // 1. NO NEED TO CALL S2RegionCoverer::CanonicalizeCovering(covering), because the covering is + // already canonical, which means that is sorted, non-overlapping and satisfy the desired + // constraints min_level, max_level. + // 2. DO NOT CALL S2CellUnion::Normalize(covering), it will replacing groups of 4 child cells by + // their parent cell, In this case, it may cause the covering don't satisfy the desired + // constraints min_level. + return covering; +} + +std::vector GeoIndex::ancestorCells(const std::vector& cells) const noexcept { + // DCHECK(rc.IsCanonical(cells)); + std::vector ancestors; + std::unordered_set seen; + for (const auto& cellId : cells) { + for (auto l = cellId.level() - 1; l >= rcParams_.minCellLevel_; --l) { + S2CellId parentCellId = cellId.parent(l); + if (seen.find(parentCellId) != seen.end()) { + break; + } + seen.emplace(parentCellId); + ancestors.push_back(parentCellId); + } + } + // The ancestors here is non-overlapping but unsorted. Do we need to sort it? + // May need to call S2RegionCoverer::CanonicalizeCovering(&ancestors)? + return ancestors; +} + +} // namespace geo +} // namespace nebula + +namespace std { + +// Inject a customized hash function +std::size_t hash::operator()(const S2CellId& c) const noexcept { + return hash{}(c.id()); +} + +} // namespace std diff --git a/src/common/geo/GeoIndex.h b/src/common/geo/GeoIndex.h new file mode 100644 index 00000000000..86883fc8adb --- /dev/null +++ b/src/common/geo/GeoIndex.h @@ -0,0 +1,101 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#pragma once + +#include + +#include +#include + +#include "common/datatypes/Geography.h" + +namespace nebula { + +namespace storage { +namespace cpp2 { +class IndexColumnHint; +} // namespace cpp2 +} // namespace storage + +namespace geo { + +struct RegionCoverParams { + // TODO(jie): Find the best default params + int minCellLevel_ = 4; + int maxCellLevel_ = 23; // About 1m + int maxCellNum_ = 8; + + RegionCoverParams() = default; + + RegionCoverParams(int minLevl, int maxLevl, int maxCells) + : minCellLevel_(minLevl), maxCellLevel_(maxLevl), maxCellNum_(maxCells) {} + + S2RegionCoverer::Options s2RegionCovererOpts() const { + S2RegionCoverer::Options opts; + opts.set_min_level(minCellLevel_); + opts.set_max_level(maxCellLevel_); + opts.set_max_cells(maxCellNum_); + return opts; + } +}; + +// scan type: PREFIX or RANGE +struct ScanRange { + uint64_t rangeMin; + uint64_t rangeMax; + bool isRangeScan; + + ScanRange(uint64_t min, uint64_t max) : rangeMin(min), rangeMax(max), isRangeScan(true) {} + + explicit ScanRange(uint64_t v) : rangeMin(v), isRangeScan(false) {} + + nebula::storage::cpp2::IndexColumnHint toIndexColumnHint(); +}; + +class GeoIndex { + public: + explicit GeoIndex(const RegionCoverParams& params, bool pointsOnly = false) + : rcParams_(params), pointsOnly_(pointsOnly) {} + + // Build geo index for the geography g + std::vector indexCells(const Geography& g) const noexcept; + + // Query the geo index + // ST_Intersects(g, x), x is the indexed geography column + std::vector intersects(const Geography& g) const noexcept; + // ST_Covers(g, x), x is the indexed geography column + std::vector covers(const Geography& g) const noexcept; + // ST_CoveredBy(g, x), x is the indexed geography column + std::vector coveredBy(const Geography& g) const noexcept; + // ST_Distance(g, x, distance), x is the indexed geography column + std::vector dWithin(const Geography& g, double distance) const noexcept; + + private: + std::vector intersects(const S2Region& r, bool isPoint = false) const noexcept; + + std::vector coveringCells(const S2Region& r, bool isPoint = false) const noexcept; + + std::vector ancestorCells(const std::vector& cells) const noexcept; + + private: + RegionCoverParams rcParams_; + bool pointsOnly_{ + false}; // For the column Geography(Point), we don't need to build ancestor cells +}; + +} // namespace geo +} // namespace nebula + +namespace std { + +// Inject a customized hash function +template <> +struct hash { + std::size_t operator()(const S2CellId& h) const noexcept; +}; + +} // namespace std diff --git a/src/common/geo/GeoUtils.h b/src/common/geo/GeoUtils.h index 7424399f76c..c3b289fb66d 100644 --- a/src/common/geo/GeoUtils.h +++ b/src/common/geo/GeoUtils.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -26,28 +27,23 @@ class GeoUtils final { } case GeoShape::LINESTRING: { const auto& lineString = geog.lineString(); - auto coordList = lineString.coordList; + const auto& coordList = lineString.coordList; auto s2Points = s2PointsFromCoordinateList(coordList); - auto s2Polyline = std::make_unique(s2Points, S2Debug::DISABLE); - return s2Polyline; + return std::make_unique(s2Points, S2Debug::DISABLE); } case GeoShape::POLYGON: { const auto& polygon = geog.polygon(); uint32_t numCoordList = polygon.numCoordList(); std::vector> s2Loops; s2Loops.reserve(numCoordList); - for (size_t i = 0; i < numCoordList; ++i) { - auto coordList = polygon.coordListList[i]; - if (!coordList.empty()) { - coordList.pop_back(); // Remove redundant last coordinate - } - auto s2Points = s2PointsFromCoordinateList(coordList); + for (const auto& coordList : polygon.coordListList) { + auto s2Points = s2PointsFromCoordinateList( + coordList, true); // S2 doesn't need the redundant last point auto s2Loop = std::make_unique(std::move(s2Points), S2Debug::DISABLE); s2Loop->Normalize(); // All loops must be oriented CCW(counterclockwise) for S2 s2Loops.emplace_back(std::move(s2Loop)); } - auto s2Polygon = std::make_unique(std::move(s2Loops), S2Debug::DISABLE); - return s2Polygon; + return std::make_unique(std::move(s2Loops), S2Debug::DISABLE); } default: LOG(FATAL) @@ -62,9 +58,22 @@ class GeoUtils final { return latlng.ToPoint(); } - static std::vector s2PointsFromCoordinateList(const std::vector& coordList) { + static Coordinate coordinateFromS2Point(const S2Point& s2Point) { + S2LatLng s2Latlng(s2Point); + return Coordinate(s2Latlng.lng().degrees(), s2Latlng.lat().degrees()); + } + + static std::vector s2PointsFromCoordinateList(const std::vector& coordList, + bool excludeTheLast = false) { std::vector s2Points; uint32_t numCoords = coordList.size(); + if (excludeTheLast) { + numCoords -= 1; + } + if (numCoords == 0) { + return {}; + } + s2Points.reserve(numCoords); for (size_t i = 0; i < numCoords; ++i) { auto coord = coordList[i]; diff --git a/src/common/geo/test/GeoFunctionTest.cpp b/src/common/geo/test/GeoFunctionTest.cpp index d525536691e..5bfc5ba489d 100644 --- a/src/common/geo/test/GeoFunctionTest.cpp +++ b/src/common/geo/test/GeoFunctionTest.cpp @@ -546,43 +546,43 @@ TEST(DWithin, point2Point) { { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); - bool b = GeoFunction::dWithin(point1, point2, 0.0, true); + bool b = GeoFunction::dWithin(point1, point2, 0.0, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); - bool b = GeoFunction::dWithin(point1, point2, 0.0, false); + bool b = GeoFunction::dWithin(point1, point2, 0.0, true); EXPECT_EQ(false, b); } { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); - bool b = GeoFunction::dWithin(point1, point2, 0.1, false); + bool b = GeoFunction::dWithin(point1, point2, 0.1, true); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); - bool b = GeoFunction::dWithin(point1, point2, 22239.020235496788, true); + bool b = GeoFunction::dWithin(point1, point2, 22239.020235496788, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); - bool b = GeoFunction::dWithin(point1, point2, 22239.020235496788, false); + bool b = GeoFunction::dWithin(point1, point2, 22239.020235496788, true); EXPECT_EQ(false, b); } { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); - bool b = GeoFunction::dWithin(point1, point2, 22240, true); + bool b = GeoFunction::dWithin(point1, point2, 22240, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(-118.4079 33.9434)").value(); auto point2 = Geography::fromWKT("POINT(2.5559 49.0083)").value(); - bool b = GeoFunction::dWithin(point1, point2, 10000, false); + bool b = GeoFunction::dWithin(point1, point2, 10000, true); EXPECT_EQ(false, b); } } @@ -591,25 +591,25 @@ TEST(DWithin, point2LineString) { { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); - bool b = GeoFunction::dWithin(point1, line2, 0.0, true); + bool b = GeoFunction::dWithin(point1, line2, 0.0, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); - bool b = GeoFunction::dWithin(point1, line2, 0.0, true); + bool b = GeoFunction::dWithin(point1, line2, 0.0, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.2, 1.5 1.6)").value(); - bool b = GeoFunction::dWithin(point1, line2, 0.0, true); + bool b = GeoFunction::dWithin(point1, line2, 0.0, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(1.2 3.8)").value(); auto point2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); - bool b = GeoFunction::dWithin(point1, point2, 1.0, false); + bool b = GeoFunction::dWithin(point1, point2, 1.0, true); EXPECT_EQ(false, b); } } @@ -618,28 +618,28 @@ TEST(DWithin, point2Polygon) { { auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); - bool b = GeoFunction::dWithin(point1, polygon2, 0.0, true); + bool b = GeoFunction::dWithin(point1, polygon2, 0.0, false); EXPECT_EQ(true, b); } // Point lies on the interior of the polygon { auto point1 = Geography::fromWKT("POINT(1.5 1.9)").value(); auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); - bool b = GeoFunction::dWithin(point1, polygon2, 0.0, true); + bool b = GeoFunction::dWithin(point1, polygon2, 0.0, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(0.5 0.5)").value(); auto polygon2 = Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); - bool b = GeoFunction::dWithin(point1, polygon2, 0.0, true); + bool b = GeoFunction::dWithin(point1, polygon2, 0.0, false); EXPECT_EQ(true, b); } { auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); auto polygon2 = Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); - bool b = GeoFunction::dWithin(point1, polygon2, 99999, false); + bool b = GeoFunction::dWithin(point1, polygon2, 99999, true); EXPECT_EQ(true, b); } { @@ -648,7 +648,7 @@ TEST(DWithin, point2Polygon) { "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " "0.4 0.4, 0.4 0.2, 0.2 0.2))") .value(); - bool b = GeoFunction::dWithin(point1, polygon2, 10000, true); + bool b = GeoFunction::dWithin(point1, polygon2, 10000, false); EXPECT_EQ(false, b); } } @@ -657,31 +657,31 @@ TEST(DWithin, lineString2LineString) { { auto line1 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); - bool b = GeoFunction::dWithin(line1, line2, 0.0, true); + bool b = GeoFunction::dWithin(line1, line2, 0.0, false); EXPECT_EQ(true, b); } { auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); - bool b = GeoFunction::dWithin(line1, line2, 0.0, true); + bool b = GeoFunction::dWithin(line1, line2, 0.0, false); EXPECT_EQ(true, b); } { auto line1 = Geography::fromWKT("LINESTRING(0.0 0.0, 1.0 1.0)").value(); auto line2 = Geography::fromWKT("LINESTRING(5.0 5.0, 6.0 6.0)").value(); - bool b = GeoFunction::dWithin(line1, line2, 628519, true); + bool b = GeoFunction::dWithin(line1, line2, 628519, false); EXPECT_EQ(false, b); } { auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 2.0 1.0)").value(); - bool b = GeoFunction::dWithin(line1, line2, 1.0, false); + bool b = GeoFunction::dWithin(line1, line2, 1.0, true); EXPECT_EQ(true, b); } { auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 1.0 4.0)").value(); - bool b = GeoFunction::dWithin(line1, line2, 78609, false); + bool b = GeoFunction::dWithin(line1, line2, 78609, true); EXPECT_EQ(true, b); } } @@ -691,21 +691,21 @@ TEST(DWithin, lineString2Polygon) { auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); auto polygon2 = Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); - bool b = GeoFunction::dWithin(line1, polygon2, 0.0, true); + bool b = GeoFunction::dWithin(line1, polygon2, 0.0, false); EXPECT_EQ(true, b); } { auto line1 = Geography::fromWKT("LINESTRING(0.2 0.2, 0.4 0.4)").value(); auto polygon2 = Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); - bool b = GeoFunction::dWithin(line1, polygon2, 0.0, false); + bool b = GeoFunction::dWithin(line1, polygon2, 0.0, true); EXPECT_EQ(false, b); } { auto line1 = Geography::fromWKT("LINESTRING(-0.5 0.5, 0.5 0.5)").value(); auto polygon2 = Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); - bool b = GeoFunction::dWithin(line1, polygon2, 9999, false); + bool b = GeoFunction::dWithin(line1, polygon2, 9999, true); EXPECT_EQ(true, b); } { @@ -714,7 +714,7 @@ TEST(DWithin, lineString2Polygon) { "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " "0.4 0.4, 0.4 0.2, 0.2 0.2))") .value(); - bool b = GeoFunction::dWithin(line1, polygon2, 5555, true); + bool b = GeoFunction::dWithin(line1, polygon2, 5555, false); EXPECT_EQ(false, b); } { @@ -723,7 +723,7 @@ TEST(DWithin, lineString2Polygon) { "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " "0.4 0.4, 0.4 0.2, 0.2 0.2))") .value(); - bool b = GeoFunction::dWithin(line1, polygon2, 0.1, false); + bool b = GeoFunction::dWithin(line1, polygon2, 0.1, true); EXPECT_EQ(true, b); } { @@ -732,7 +732,7 @@ TEST(DWithin, lineString2Polygon) { "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " "0.4 0.4, 0.4 0.2, 0.2 0.2))") .value(); - bool b = GeoFunction::dWithin(line1, polygon2, 628520, false); + bool b = GeoFunction::dWithin(line1, polygon2, 628520, true); EXPECT_EQ(true, b); } } @@ -743,7 +743,7 @@ TEST(DWithin, polygon2Polygon) { Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); auto polygon2 = Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); - bool b = GeoFunction::dWithin(polygon1, polygon2, 0.0, true); + bool b = GeoFunction::dWithin(polygon1, polygon2, 0.0, false); EXPECT_EQ(true, b); } { @@ -751,7 +751,7 @@ TEST(DWithin, polygon2Polygon) { Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); auto polygon2 = Geography::fromWKT("POLYGON((0.2 0.2, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.2 0.2))").value(); - bool b = GeoFunction::dWithin(polygon1, polygon2, 1.1, false); + bool b = GeoFunction::dWithin(polygon1, polygon2, 1.1, true); EXPECT_EQ(true, b); } { @@ -759,7 +759,7 @@ TEST(DWithin, polygon2Polygon) { Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); auto polygon2 = Geography::fromWKT("POLYGON((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 4.0, 3.0 3.0))").value(); - bool b = GeoFunction::dWithin(polygon1, polygon2, 314402, true); + bool b = GeoFunction::dWithin(polygon1, polygon2, 314402, false); EXPECT_EQ(false, b); } { @@ -770,7 +770,7 @@ TEST(DWithin, polygon2Polygon) { "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " "0.4 0.4, 0.4 0.2, 0.2 0.2))") .value(); - bool b = GeoFunction::dWithin(polygon1, polygon2, 5560, false); + bool b = GeoFunction::dWithin(polygon1, polygon2, 5560, true); EXPECT_EQ(true, b); } { @@ -779,7 +779,7 @@ TEST(DWithin, polygon2Polygon) { auto polygon2 = Geography::fromWKT("POLYGON((-8.0 -8.0, -4.0 -8.0, -4.0 -4.0, -8.0 -4.0, -8.0 -8.0))") .value(); - bool b = GeoFunction::dWithin(polygon1, polygon2, 628750, false); + bool b = GeoFunction::dWithin(polygon1, polygon2, 628750, true); EXPECT_EQ(false, b); } } diff --git a/src/common/meta/NebulaSchemaProvider.cpp b/src/common/meta/NebulaSchemaProvider.cpp index dc5aab4a2c5..a9bf2f5e66f 100644 --- a/src/common/meta/NebulaSchemaProvider.cpp +++ b/src/common/meta/NebulaSchemaProvider.cpp @@ -91,7 +91,8 @@ void NebulaSchemaProvider::addField(folly::StringPiece name, cpp2::PropertyType type, size_t fixedStrLen, bool nullable, - Expression* defaultValue) { + Expression* defaultValue, + cpp2::GeoShape geoShape) { size_t size = fieldSize(type, fixedStrLen); size_t offset = 0; @@ -112,7 +113,8 @@ void NebulaSchemaProvider::addField(folly::StringPiece name, defaultValue, size, offset, - nullFlagPos); + nullFlagPos, + geoShape); fieldNameIndex_.emplace(name.toString(), static_cast(fields_.size() - 1)); } diff --git a/src/common/meta/NebulaSchemaProvider.h b/src/common/meta/NebulaSchemaProvider.h index 0b185c7ed3a..31a381a4a83 100644 --- a/src/common/meta/NebulaSchemaProvider.h +++ b/src/common/meta/NebulaSchemaProvider.h @@ -29,7 +29,8 @@ class NebulaSchemaProvider : public SchemaProviderIf { Expression* defaultValue, size_t size, size_t offset, - size_t nullFlagPos) + size_t nullFlagPos, + cpp2::GeoShape geoShape) : name_(std::move(name)), type_(std::move(type)), nullable_(nullable), @@ -37,7 +38,8 @@ class NebulaSchemaProvider : public SchemaProviderIf { defaultValue_(defaultValue), size_(size), offset_(offset), - nullFlagPos_(nullFlagPos) {} + nullFlagPos_(nullFlagPos), + geoShape_(geoShape) {} const char* name() const override { return name_.c_str(); } @@ -58,6 +60,8 @@ class NebulaSchemaProvider : public SchemaProviderIf { return nullFlagPos_; } + cpp2::GeoShape geoShape() const override { return geoShape_; } + private: std::string name_; cpp2::PropertyType type_; @@ -67,6 +71,7 @@ class NebulaSchemaProvider : public SchemaProviderIf { size_t size_; size_t offset_; size_t nullFlagPos_; + cpp2::GeoShape geoShape_; }; public: @@ -93,7 +98,8 @@ class NebulaSchemaProvider : public SchemaProviderIf { cpp2::PropertyType type, size_t fixedStrLen = 0, bool nullable = false, - Expression* defaultValue = nullptr); + Expression* defaultValue = nullptr, + cpp2::GeoShape geoShape = cpp2::GeoShape::ANY); static std::size_t fieldSize(cpp2::PropertyType type, std::size_t fixedStrLimit); diff --git a/src/common/meta/SchemaProviderIf.h b/src/common/meta/SchemaProviderIf.h index b880c7f01cd..f643aab28cb 100644 --- a/src/common/meta/SchemaProviderIf.h +++ b/src/common/meta/SchemaProviderIf.h @@ -40,6 +40,10 @@ class SchemaProviderIf { // In v2, if the field is nullable, it returns the position of // the null flag bit, otherwise, it returns 0 virtual size_t nullFlagPos() const = 0; + // In v1, this always returns cpp2::GeoShape::ANY + // In v2, if the field type is cpp2::PropertyType::Geography, + // it returns the specified geo shape type + virtual cpp2::GeoShape geoShape() const = 0; }; // Inherited classes do not need to implement the Iterator diff --git a/src/common/utils/IndexKeyUtils.cpp b/src/common/utils/IndexKeyUtils.cpp index 864e6c7c99c..9e38a574db4 100644 --- a/src/common/utils/IndexKeyUtils.cpp +++ b/src/common/utils/IndexKeyUtils.cpp @@ -45,6 +45,33 @@ std::string IndexKeyUtils::encodeValues(std::vector&& values, return index; } +std::vector IndexKeyUtils::encodeValueForGeography( + Value&& value, const nebula::meta::cpp2::ColumnDef& col) { + DCHECK(col.get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY); + u_short nullableBitSet = 0; + std::vector indexes; + + auto hasNullCol = col.nullable_ref().value_or(false); + + if (!value.isNull()) { + DCHECK(value.type() == Value::Type::GEOGRAPHY); + indexes = encodeGeography(value.getGeography()); + } else { + nullableBitSet |= 0x8000; + auto type = IndexKeyUtils::toValueType(col.type.get_type()); + indexes.emplace_back(encodeNullValue(type, nullptr)); + } + + if (hasNullCol) { + // if has nullable field, append nullableBitSet to the end + for (auto& index : indexes) { + index.append(reinterpret_cast(&nullableBitSet), sizeof(u_short)); + } + } + + return indexes; +} + // static std::string IndexKeyUtils::vertexIndexKey( size_t vIdLen, PartitionID partId, IndexID indexId, const VertexID& vId, std::string&& values) { @@ -59,6 +86,28 @@ std::string IndexKeyUtils::vertexIndexKey( return key; } +// static +std::vector IndexKeyUtils::vertexIndexKeysForGeography( + size_t vIdLen, + PartitionID partId, + IndexID indexId, + const VertexID& vId, + std::vector&& values) { + int32_t item = (partId << kPartitionOffset) | static_cast(NebulaKeyType::kIndex); + std::vector keys; + for (const auto& value : values) { + std::string key; + key.reserve(256); + key.append(reinterpret_cast(&item), sizeof(int32_t)) + .append(reinterpret_cast(&indexId), sizeof(IndexID)) + .append(value) + .append(vId.data(), vId.size()) + .append(vIdLen - vId.size(), '\0'); + keys.emplace_back(key); + } + return keys; +} + // static std::string IndexKeyUtils::edgeIndexKey(size_t vIdLen, PartitionID partId, @@ -81,6 +130,33 @@ std::string IndexKeyUtils::edgeIndexKey(size_t vIdLen, return key; } +// static +std::vector IndexKeyUtils::edgeIndexKeysForGeography( + size_t vIdLen, + PartitionID partId, + IndexID indexId, + const VertexID& srcId, + EdgeRanking rank, + const VertexID& dstId, + std::vector&& values) { + int32_t item = (partId << kPartitionOffset) | static_cast(NebulaKeyType::kIndex); + std::vector keys; + for (const auto& value : values) { + std::string key; + key.reserve(256); + key.append(reinterpret_cast(&item), sizeof(int32_t)) + .append(reinterpret_cast(&indexId), sizeof(IndexID)) + .append(value) + .append(srcId.data(), srcId.size()) + .append(vIdLen - srcId.size(), '\0') + .append(IndexKeyUtils::encodeRank(rank)) + .append(dstId.data(), dstId.size()) + .append(vIdLen - dstId.size(), '\0'); + keys.emplace_back(key); + } + return keys; +} + // static std::string IndexKeyUtils::indexPrefix(PartitionID partId, IndexID indexId) { PartitionID item = (partId << kPartitionOffset) | static_cast(NebulaKeyType::kIndex); @@ -139,6 +215,25 @@ StatusOr IndexKeyUtils::collectIndexValues( return encodeValues(std::move(values), cols); } +// TODO(jie) Should be refactored +// static +StatusOr> IndexKeyUtils::collectIndexValueForGeography( + RowReader* reader, const nebula::meta::cpp2::ColumnDef& col) { + if (reader == nullptr) { + return Status::Error("Invalid row reader"); + } + DCHECK(col.get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY); + + Value value = reader->getValueByName(col.get_name()); + auto isNullable = col.nullable_ref().value_or(false); + auto ret = checkValue(value, isNullable); + if (!ret.ok()) { + LOG(ERROR) << "prop error by : " << col.get_name() << ". status : " << ret; + return ret; + } + return encodeValueForGeography(std::move(value), col); +} + // static Status IndexKeyUtils::checkValue(const Value& v, bool isNullable) { if (!v.isNull()) { diff --git a/src/common/utils/IndexKeyUtils.h b/src/common/utils/IndexKeyUtils.h index 23d699abce8..c689b64f7f3 100644 --- a/src/common/utils/IndexKeyUtils.h +++ b/src/common/utils/IndexKeyUtils.h @@ -10,6 +10,7 @@ #include "codec/RowReader.h" #include "common/base/Base.h" #include "common/base/StatusOr.h" +#include "common/geo/GeoIndex.h" #include "common/utils/Types.h" #include "interface/gen-cpp2/meta_types.h" @@ -138,7 +139,7 @@ class IndexKeyUtils final { return encodeDateTime(v.getDateTime()); } case Value::Type::GEOGRAPHY: { - // TODO(jie) + LOG(FATAL) << "Should call encodeValueForGeography separately"; return ""; } default: @@ -299,6 +300,18 @@ class IndexKeyUtils final { return buf; } + static std::vector encodeGeography(const nebula::Geography& gg) { + geo::RegionCoverParams + rc; // TODO(jie): Get index params from meta to construct RegionCoverParams + geo::GeoIndex geoIndex(rc, false); // TODO(jie): Get schema meta to know if it's point only + auto cellIds = geoIndex.indexCells(gg); + std::vector bufs; + for (auto cellId : cellIds) { + bufs.emplace_back(encodeUint64(cellId)); + } + return bufs; // just support index point here. + } + static nebula::DateTime decodeDateTime(const folly::StringPiece& raw) { int16_t year = *reinterpret_cast(raw.data()); int8_t month = *reinterpret_cast(raw.data() + sizeof(int16_t)); @@ -482,6 +495,8 @@ class IndexKeyUtils final { **/ static std::string encodeValues(std::vector&& values, const std::vector& cols); + static std::vector encodeValueForGeography(Value&& value, + const nebula::meta::cpp2::ColumnDef& col); /** * param valueTypes : column type of each index column. If there are no @@ -492,6 +507,11 @@ class IndexKeyUtils final { IndexID indexId, const VertexID& vId, std::string&& values); + static std::vector vertexIndexKeysForGeography(size_t vIdLen, + PartitionID partId, + IndexID indexId, + const VertexID& vId, + std::vector&& values); /** * param valueTypes : column type of each index column. If there are no @@ -504,6 +524,13 @@ class IndexKeyUtils final { EdgeRanking rank, const VertexID& dstId, std::string&& values); + static std::vector edgeIndexKeysForGeography(size_t vIdLen, + PartitionID partId, + IndexID indexId, + const VertexID& srcId, + EdgeRanking rank, + const VertexID& dstId, + std::vector&& values); static std::string indexPrefix(PartitionID partId, IndexID indexId); @@ -515,6 +542,8 @@ class IndexKeyUtils final { static StatusOr collectIndexValues( RowReader* reader, const std::vector& cols); + static StatusOr> collectIndexValueForGeography( + RowReader* reader, const nebula::meta::cpp2::ColumnDef& col); private: IndexKeyUtils() = delete; diff --git a/src/common/utils/test/CMakeLists.txt b/src/common/utils/test/CMakeLists.txt index 7e9d59739ff..d02df8575dc 100644 --- a/src/common/utils/test/CMakeLists.txt +++ b/src/common/utils/test/CMakeLists.txt @@ -11,6 +11,7 @@ nebula_add_test( $ $ $ + $ LIBRARIES gtest ${THRIFT_LIBRARIES} @@ -29,6 +30,7 @@ nebula_add_test( $ $ $ + $ LIBRARIES gtest ${THRIFT_LIBRARIES} @@ -47,6 +49,7 @@ nebula_add_test( $ $ $ + $ LIBRARIES gtest ${THRIFT_LIBRARIES} diff --git a/src/daemons/CMakeLists.txt b/src/daemons/CMakeLists.txt index 13eaa0e673e..565c2717f54 100644 --- a/src/daemons/CMakeLists.txt +++ b/src/daemons/CMakeLists.txt @@ -45,6 +45,7 @@ set(storage_meta_deps $ $ $ + $ ) nebula_add_executable( @@ -118,7 +119,6 @@ nebula_add_executable( $ $ $ - $ $ $ $ @@ -128,6 +128,7 @@ nebula_add_executable( $ $ $ + $ ${common_deps} LIBRARIES ${PROXYGEN_LIBRARIES} diff --git a/src/graph/optimizer/CMakeLists.txt b/src/graph/optimizer/CMakeLists.txt index 06484cba4d7..361463cd882 100644 --- a/src/graph/optimizer/CMakeLists.txt +++ b/src/graph/optimizer/CMakeLists.txt @@ -32,6 +32,9 @@ nebula_add_library( rule/UnionAllIndexScanBaseRule.cpp rule/UnionAllTagIndexScanRule.cpp rule/UnionAllEdgeIndexScanRule.cpp + rule/GeoPredicateIndexScanBaseRule.cpp + rule/GeoPredicateTagIndexScanRule.cpp + rule/GeoPredicateEdgeIndexScanRule.cpp rule/IndexFullScanBaseRule.cpp rule/TagIndexFullScanRule.cpp rule/EdgeIndexFullScanRule.cpp diff --git a/src/graph/optimizer/rule/GeoPredicateEdgeIndexScanRule.cpp b/src/graph/optimizer/rule/GeoPredicateEdgeIndexScanRule.cpp new file mode 100644 index 00000000000..758cc457016 --- /dev/null +++ b/src/graph/optimizer/rule/GeoPredicateEdgeIndexScanRule.cpp @@ -0,0 +1,32 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "graph/optimizer/rule/GeoPredicateEdgeIndexScanRule.h" + +using Kind = nebula::graph::PlanNode::Kind; + +namespace nebula { +namespace opt { + +std::unique_ptr GeoPredicateEdgeIndexScanRule::kInstance = + std::unique_ptr(new GeoPredicateEdgeIndexScanRule()); + +GeoPredicateEdgeIndexScanRule::GeoPredicateEdgeIndexScanRule() { + RuleSet::DefaultRules().addRule(this); +} + +const Pattern& GeoPredicateEdgeIndexScanRule::pattern() const { + static Pattern pattern = + Pattern::create(Kind::kFilter, {Pattern::create(Kind::kEdgeIndexFullScan)}); + return pattern; +} + +std::string GeoPredicateEdgeIndexScanRule::toString() const { + return "GeoPredicateEdgeIndexScanRule"; +} + +} // namespace opt +} // namespace nebula diff --git a/src/graph/optimizer/rule/GeoPredicateEdgeIndexScanRule.h b/src/graph/optimizer/rule/GeoPredicateEdgeIndexScanRule.h new file mode 100644 index 00000000000..91a1dd28adc --- /dev/null +++ b/src/graph/optimizer/rule/GeoPredicateEdgeIndexScanRule.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#pragma once + +#include "graph/optimizer/rule/GeoPredicateIndexScanBaseRule.h" + +namespace nebula { +namespace opt { + +class GeoPredicateEdgeIndexScanRule final : public GeoPredicateIndexScanBaseRule { + public: + const Pattern &pattern() const override; + std::string toString() const override; + + private: + GeoPredicateEdgeIndexScanRule(); + + static std::unique_ptr kInstance; +}; + +} // namespace opt +} // namespace nebula diff --git a/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.cpp b/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.cpp new file mode 100644 index 00000000000..d4894e58e0e --- /dev/null +++ b/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.cpp @@ -0,0 +1,141 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "graph/optimizer/rule/GeoPredicateIndexScanBaseRule.h" + +#include "common/expression/Expression.h" +#include "common/expression/LogicalExpression.h" +#include "common/geo/GeoIndex.h" +#include "graph/optimizer/OptContext.h" +#include "graph/optimizer/OptGroup.h" +#include "graph/optimizer/OptRule.h" +#include "graph/optimizer/OptimizerUtils.h" +#include "graph/planner/plan/PlanNode.h" +#include "graph/planner/plan/Query.h" +#include "graph/planner/plan/Scan.h" +#include "graph/util/ExpressionUtils.h" +#include "interface/gen-cpp2/storage_types.h" + +using nebula::graph::Filter; +using nebula::graph::IndexScan; +using nebula::graph::OptimizerUtils; +using nebula::graph::TagIndexFullScan; +using nebula::storage::cpp2::IndexColumnHint; +using nebula::storage::cpp2::IndexQueryContext; + +using Kind = nebula::graph::PlanNode::Kind; +using ExprKind = nebula::Expression::Kind; +using TransformResult = nebula::opt::OptRule::TransformResult; + +namespace nebula { +namespace opt { + +bool GeoPredicateIndexScanBaseRule::match(OptContext* ctx, const MatchedResult& matched) const { + if (!OptRule::match(ctx, matched)) { + return false; + } + auto filter = static_cast(matched.planNode()); + auto scan = static_cast(matched.planNode({0, 0})); + for (auto& ictx : scan->queryContext()) { + if (ictx.column_hints_ref().is_set()) { + return false; + } + } + auto condition = filter->condition(); + return graph::ExpressionUtils::isGeoIndexAcceleratedPredicate(condition); +} + +StatusOr GeoPredicateIndexScanBaseRule::transform( + OptContext* ctx, const MatchedResult& matched) const { + auto filter = static_cast(matched.planNode()); + auto node = matched.planNode({0, 0}); + auto scan = static_cast(node); + + auto metaClient = ctx->qctx()->getMetaClient(); + auto status = node->kind() == graph::PlanNode::Kind::kTagIndexFullScan + ? metaClient->getTagIndexesFromCache(scan->space()) + : metaClient->getEdgeIndexesFromCache(scan->space()); + + NG_RETURN_IF_ERROR(status); + auto indexItems = std::move(status).value(); + + OptimizerUtils::eraseInvalidIndexItems(scan->schemaId(), &indexItems); + + if (indexItems.empty()) { + return TransformResult::noTransform(); + } + + auto condition = filter->condition(); + DCHECK(graph::ExpressionUtils::isGeoIndexAcceleratedPredicate(condition)); + + auto* geoPredicate = static_cast(condition); + DCHECK_GE(geoPredicate->args()->numArgs(), 2); + std::string geoPredicateName = geoPredicate->name(); + folly::toLowerAscii(geoPredicateName); + auto* first = geoPredicate->args()->args()[0]; + auto* second = geoPredicate->args()->args()[1]; + DCHECK(first->kind() == Expression::Kind::kTagProperty || + first->kind() == Expression::Kind::kEdgeProperty); + DCHECK(second->kind() == Expression::Kind::kConstant); + const auto& secondVal = static_cast(second)->value(); + DCHECK(secondVal.type() == Value::Type::GEOGRAPHY); + const auto& geog = secondVal.getGeography(); + + geo::RegionCoverParams + rc; // TODO(jie): Get index params from meta to construct RegionCoverParams + geo::GeoIndex geoIndex(rc, false); // TODO(jie): Get schema meta to know if it's point only + std::vector scanRanges; + if (geoPredicateName == "st_intersects") { + scanRanges = geoIndex.intersects(geog); + } else if (geoPredicateName == "st_covers") { + scanRanges = geoIndex.coveredBy(geog); + } else if (geoPredicateName == "st_coveredby") { + scanRanges = geoIndex.covers(geog); + } else if (geoPredicateName == "st_dwithin") { + DCHECK_GE(geoPredicate->args()->numArgs(), 3); + auto* third = geoPredicate->args()->args()[2]; + DCHECK_EQ(third->kind(), Expression::Kind::kConstant); + const auto& thirdVal = static_cast(third)->value(); + DCHECK_EQ(thirdVal.type(), Value::Type::FLOAT); + double distanceInMeters = thirdVal.getFloat(); + scanRanges = geoIndex.dWithin(geog, distanceInMeters); + } + std::vector idxCtxs; + idxCtxs.reserve(scanRanges.size()); + auto indexItem = indexItems.back(); + const auto& fields = indexItem->get_fields(); + DCHECK_EQ(fields.size(), 1); // geo field + auto fieldName = fields.back().get_name(); + for (auto& scanRange : scanRanges) { + IndexQueryContext ictx; + auto indexColumnHint = scanRange.toIndexColumnHint(); + indexColumnHint.set_column_name(fieldName); + ictx.set_filter(condition->encode()); + ictx.set_index_id(indexItem->get_index_id()); + ictx.set_column_hints({indexColumnHint}); + idxCtxs.emplace_back(std::move(ictx)); + } + + auto scanNode = IndexScan::make(ctx->qctx(), nullptr); + OptimizerUtils::copyIndexScanData(scan, scanNode); + scanNode->setIndexQueryContext(std::move(idxCtxs)); + // TODO(jie): geo predicate's caculation is a little heavy, + // which is not suitable to push down to the storage + scanNode->setOutputVar(filter->outputVar()); + scanNode->setColNames(filter->colNames()); + auto filterGroup = matched.node->group(); + auto optScanNode = OptGroupNode::create(ctx, scanNode, filterGroup); + for (auto group : matched.dependencies[0].node->dependencies()) { + optScanNode->dependsOn(group); + } + TransformResult result; + result.newGroupNodes.emplace_back(optScanNode); + result.eraseCurr = true; + return result; +} + +} // namespace opt +} // namespace nebula diff --git a/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.h b/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.h new file mode 100644 index 00000000000..f5abfe62592 --- /dev/null +++ b/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#pragma once + +#include "graph/optimizer/OptRule.h" + +namespace nebula { +namespace opt { + +class GeoPredicateIndexScanBaseRule : public OptRule { + public: + bool match(OptContext *ctx, const MatchedResult &matched) const override; + StatusOr transform(OptContext *ctx, const MatchedResult &matched) const override; +}; + +} // namespace opt +} // namespace nebula diff --git a/src/graph/optimizer/rule/GeoPredicateTagIndexScanRule.cpp b/src/graph/optimizer/rule/GeoPredicateTagIndexScanRule.cpp new file mode 100644 index 00000000000..c115e48ce5b --- /dev/null +++ b/src/graph/optimizer/rule/GeoPredicateTagIndexScanRule.cpp @@ -0,0 +1,32 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "graph/optimizer/rule/GeoPredicateTagIndexScanRule.h" + +using Kind = nebula::graph::PlanNode::Kind; + +namespace nebula { +namespace opt { + +std::unique_ptr GeoPredicateTagIndexScanRule::kInstance = + std::unique_ptr(new GeoPredicateTagIndexScanRule()); + +GeoPredicateTagIndexScanRule::GeoPredicateTagIndexScanRule() { + RuleSet::DefaultRules().addRule(this); +} + +const Pattern& GeoPredicateTagIndexScanRule::pattern() const { + static Pattern pattern = + Pattern::create(Kind::kFilter, {Pattern::create(Kind::kTagIndexFullScan)}); + return pattern; +} + +std::string GeoPredicateTagIndexScanRule::toString() const { + return "GeoPredicateTagIndexScanRule"; +} + +} // namespace opt +} // namespace nebula diff --git a/src/graph/optimizer/rule/GeoPredicateTagIndexScanRule.h b/src/graph/optimizer/rule/GeoPredicateTagIndexScanRule.h new file mode 100644 index 00000000000..e2dfc82cf14 --- /dev/null +++ b/src/graph/optimizer/rule/GeoPredicateTagIndexScanRule.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#pragma once + +#include "graph/optimizer/rule/GeoPredicateIndexScanBaseRule.h" + +namespace nebula { +namespace opt { + +class GeoPredicateTagIndexScanRule final : public GeoPredicateIndexScanBaseRule { + public: + const Pattern &pattern() const override; + std::string toString() const override; + + private: + GeoPredicateTagIndexScanRule(); + + static std::unique_ptr kInstance; +}; + +} // namespace opt +} // namespace nebula diff --git a/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp b/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp index 42021d4422d..7433c9af986 100644 --- a/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp +++ b/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp @@ -70,6 +70,13 @@ bool OptimizeEdgeIndexScanByFilterRule::match(OptContext* ctx, const MatchedResu auto condition = filter->condition(); if (condition->isRelExpr()) { auto relExpr = static_cast(condition); + // If the container in the IN expr has only 1 element, it will be converted to an relEQ + // expr. If more than 1 element found in the container, UnionAllIndexScanBaseRule will be + // applied. + if (relExpr->kind() == ExprKind::kRelIn && relExpr->right()->isContainerExpr()) { + auto ContainerOperands = graph::ExpressionUtils::getContainerExprOperands(relExpr->right()); + return ContainerOperands.size() == 1; + } return relExpr->left()->kind() == ExprKind::kEdgeProperty && relExpr->right()->kind() == ExprKind::kConstant; } @@ -103,13 +110,43 @@ StatusOr OptimizeEdgeIndexScanByFilterRule::transform( OptimizerUtils::eraseInvalidIndexItems(scan->schemaId(), &indexItems); + auto condition = filter->condition(); + auto conditionType = condition->kind(); + Expression* transformedExpr = condition->clone(); + + // Stand alone IN expr with only 1 element in the list, no need to check index + if (conditionType == ExprKind::kRelIn) { + transformedExpr = graph::ExpressionUtils::rewriteInExpr(condition); + DCHECK(transformedExpr->kind() == ExprKind::kRelEQ); + } + + // case2: logical AND expr + if (condition->kind() == ExprKind::kLogicalAnd) { + for (auto& operand : static_cast(condition)->operands()) { + if (operand->kind() == ExprKind::kRelIn) { + auto inExpr = static_cast(operand); + // Do not apply this rule if the IN expr has a valid index or it has only 1 element in the + // list + if (static_cast(inExpr->right())->size() > 1) { + return TransformResult::noTransform(); + } else { + transformedExpr = graph::ExpressionUtils::rewriteInExpr(condition); + } + if (OptimizerUtils::relExprHasIndex(inExpr, indexItems)) { + return TransformResult::noTransform(); + } + } + } + } + IndexQueryContext ictx; bool isPrefixScan = false; - if (!OptimizerUtils::findOptimalIndex(filter->condition(), indexItems, &isPrefixScan, &ictx)) { + if (!OptimizerUtils::findOptimalIndex(transformedExpr, indexItems, &isPrefixScan, &ictx)) { return TransformResult::noTransform(); } + std::vector idxCtxs = {ictx}; - EdgeIndexScan* scanNode = makeEdgeIndexScan(ctx->qctx(), scan, isPrefixScan); + auto scanNode = makeEdgeIndexScan(ctx->qctx(), scan, isPrefixScan); scanNode->setIndexQueryContext(std::move(idxCtxs)); scanNode->setOutputVar(filter->outputVar()); scanNode->setColNames(filter->colNames()); diff --git a/src/graph/optimizer/test/CMakeLists.txt b/src/graph/optimizer/test/CMakeLists.txt index 1d99b18f5cb..c9a3d8245b1 100644 --- a/src/graph/optimizer/test/CMakeLists.txt +++ b/src/graph/optimizer/test/CMakeLists.txt @@ -46,6 +46,7 @@ set(OPTIMIZER_TEST_LIB $ $ $ + $ ) nebula_add_test( diff --git a/src/graph/util/ExpressionUtils.cpp b/src/graph/util/ExpressionUtils.cpp index 932a638d561..28bb5d3ff42 100644 --- a/src/graph/util/ExpressionUtils.cpp +++ b/src/graph/util/ExpressionUtils.cpp @@ -899,5 +899,32 @@ Expression *ExpressionUtils::equalCondition(ObjectPool *pool, return RelationalExpression::makeEQ( pool, VariableExpression::make(pool, var), ConstantExpression::make(pool, value)); } + +bool ExpressionUtils::isGeoIndexAcceleratedPredicate(const Expression *expr) { + static std::unordered_set geoIndexAcceleratedPredicates{ + "st_intersects", "st_covers", "st_coveredby", "st_dwithin"}; + + if (expr->isRelExpr()) { + auto *relExpr = static_cast(expr); + auto isSt_Distance = [](const Expression *e) { + if (e->kind() == Expression::Kind::kFunctionCall) { + auto *funcExpr = static_cast(e); + return boost::algorithm::iequals(funcExpr->name(), "st_distance"); + } + return false; + }; + return isSt_Distance(relExpr->left()) || isSt_Distance(relExpr->right()); + } else if (expr->kind() == Expression::Kind::kFunctionCall) { + auto *funcExpr = static_cast(expr); + std::string funcName = funcExpr->name(); + folly::toLowerAscii(funcName); + if (geoIndexAcceleratedPredicates.count(funcName) != 0) { + return true; + } + } + + return false; +} + } // namespace graph } // namespace nebula diff --git a/src/graph/util/ExpressionUtils.h b/src/graph/util/ExpressionUtils.h index a4faeaf5777..7af0522a09b 100644 --- a/src/graph/util/ExpressionUtils.h +++ b/src/graph/util/ExpressionUtils.h @@ -156,6 +156,9 @@ class ExpressionUtils { // var == value static Expression* equalCondition(ObjectPool* pool, const std::string& var, const Value& value); + + // TODO(jie) Move it to a better place + static bool isGeoIndexAcceleratedPredicate(const Expression* expr); }; } // namespace graph diff --git a/src/graph/util/SchemaUtil.cpp b/src/graph/util/SchemaUtil.cpp index ccbcb9ca028..c638efa5365 100644 --- a/src/graph/util/SchemaUtil.cpp +++ b/src/graph/util/SchemaUtil.cpp @@ -67,7 +67,8 @@ std::shared_ptr SchemaUtil::generateSchemaProv col.get_type().get_type(), col.type.type_length_ref().value_or(0), col.nullable_ref().value_or(false), - hasDef ? defaultValueExpr : nullptr); + hasDef ? defaultValueExpr : nullptr, + col.type.geo_shape_ref().value_or(meta::cpp2::GeoShape::ANY)); } return schemaPtr; } @@ -259,6 +260,13 @@ std::string SchemaUtil::typeToString(const meta::cpp2::ColumnTypeDef &col) { auto type = apache::thrift::util::enumNameSafe(col.get_type()); if (col.get_type() == meta::cpp2::PropertyType::FIXED_STRING) { return folly::stringPrintf("%s(%d)", type.c_str(), *col.get_type_length()); + } else if (col.get_type() == meta::cpp2::PropertyType::GEOGRAPHY) { + auto geoShape = *col.get_geo_shape(); + if (geoShape == meta::cpp2::GeoShape::ANY) { + return folly::stringPrintf("%s", type.c_str()); + } + return folly::stringPrintf( + "%s(%s)", type.c_str(), apache::thrift::util::enumNameSafe(geoShape).c_str()); } return type; } diff --git a/src/graph/validator/LookupValidator.cpp b/src/graph/validator/LookupValidator.cpp index 4f7df3c1cef..45ebd91330d 100644 --- a/src/graph/validator/LookupValidator.cpp +++ b/src/graph/validator/LookupValidator.cpp @@ -259,7 +259,10 @@ StatusOr LookupValidator::handleLogicalExprOperands(LogicalExpressi StatusOr LookupValidator::checkFilter(Expression* expr) { // TODO: Support IN expression push down - if (expr->isRelExpr()) { + if (ExpressionUtils::isGeoIndexAcceleratedPredicate(expr)) { + NG_RETURN_IF_ERROR(checkGeoPredicate(expr)); + return rewriteGeoPredicate(expr); + } else if (expr->isRelExpr()) { // Only starts with can be pushed down as a range scan, so forbid other string-related relExpr if (expr->kind() == ExprKind::kRelREG || expr->kind() == ExprKind::kContains || expr->kind() == ExprKind::kNotContains || expr->kind() == ExprKind::kEndsWith || @@ -298,9 +301,47 @@ Status LookupValidator::checkRelExpr(RelationalExpression* expr) { if (left->kind() == ExprKind::kLabelAttribute || right->kind() == ExprKind::kLabelAttribute) { return Status::OK(); } + return Status::SemanticError("Expression %s not supported yet", expr->toString().c_str()); } +Status LookupValidator::checkGeoPredicate(const Expression* expr) const { + auto checkFunc = [](const FunctionCallExpression* funcExpr) -> Status { + if (funcExpr->args()->numArgs() < 2) { + return Status::SemanticError("Expression %s has not enough arguments", + funcExpr->toString().c_str()); + } + auto* first = funcExpr->args()->args()[0]; + auto* second = funcExpr->args()->args()[1]; + + if (first->kind() == ExprKind::kLabelAttribute && second->kind() == ExprKind::kLabelAttribute) { + return Status::SemanticError("Expression %s not supported yet", funcExpr->toString().c_str()); + } + if (first->kind() == ExprKind::kLabelAttribute || second->kind() == ExprKind::kLabelAttribute) { + return Status::OK(); + } + + return Status::SemanticError("Expression %s not supported yet", funcExpr->toString().c_str()); + }; + if (expr->isRelExpr()) { + auto* relExpr = static_cast(expr); + auto* left = relExpr->left(); + auto* right = relExpr->right(); + if (left->kind() == Expression::Kind::kFunctionCall) { + NG_RETURN_IF_ERROR(checkFunc(static_cast(left))); + } + if (right->kind() == Expression::Kind::kFunctionCall) { + NG_RETURN_IF_ERROR(checkFunc(static_cast(right))); + } + } else if (expr->kind() == Expression::Kind::kFunctionCall) { + NG_RETURN_IF_ERROR(checkFunc(static_cast(expr))); + } else { + return Status::SemanticError("Expression %s not supported yet", expr->toString().c_str()); + } + + return Status::OK(); +} + StatusOr LookupValidator::rewriteRelExpr(RelationalExpression* expr) { // swap LHS and RHS of relExpr if LabelAttributeExpr in on the right, // so that LabelAttributeExpr is always on the left @@ -335,6 +376,72 @@ StatusOr LookupValidator::rewriteRelExpr(RelationalExpression* expr return expr; } +StatusOr LookupValidator::rewriteGeoPredicate(Expression* expr) { + // swap LHS and RHS of relExpr if LabelAttributeExpr in on the right, + // so that LabelAttributeExpr is always on the left + if (expr->isRelExpr()) { + if (static_cast(expr)->right()->kind() == + Expression::Kind::kFunctionCall) { + expr = reverseGeoPredicate(expr); + } + auto* relExpr = static_cast(expr); + auto* left = relExpr->left(); + auto* right = relExpr->right(); + DCHECK(left->kind() == Expression::Kind::kFunctionCall); + auto* funcExpr = static_cast(left); + DCHECK_EQ(boost::to_lower_copy(funcExpr->name()), "st_distance"); + // `ST_Distance(g1, g1) < distanceInMeters` is equivalent to `ST_DWithin(g1, g2, + // distanceInMeters, true), `ST_Distance(g1, g1) <= distanceInMeters` is equivalent to + // `ST_DWithin(g1, g2, distanceInMeters, false) + if (relExpr->kind() == Expression::Kind::kRelLT || + relExpr->kind() == Expression::Kind::kRelLE) { + auto* newArgList = ArgumentList::make(qctx_->objPool(), 3); + if (funcExpr->args()->numArgs() != 2) { + return Status::SemanticError("Expression %s has wrong number arguments", + funcExpr->toString().c_str()); + } + newArgList->addArgument(funcExpr->args()->args()[0]->clone()); + newArgList->addArgument(funcExpr->args()->args()[1]->clone()); + newArgList->addArgument(right->clone()); // distanceInMeters + bool exclusive = relExpr->kind() == Expression::Kind::kRelLT; + newArgList->addArgument(ConstantExpression::make(qctx_->objPool(), exclusive)); + expr = FunctionCallExpression::make(qctx_->objPool(), "st_dwithin", newArgList); + } else { + return Status::SemanticError("Expression %s not supported yet", expr->toString().c_str()); + } + } + + DCHECK(expr->kind() == Expression::Kind::kFunctionCall); + if (static_cast(expr)->args()->args()[1]->kind() == + ExprKind::kLabelAttribute) { + expr = reverseGeoPredicate(expr); + } + + auto* geoFuncExpr = static_cast(expr); + auto* first = geoFuncExpr->args()->args()[0]; + auto* la = static_cast(first); + if (la->left()->name() != sentence()->from()) { + return Status::SemanticError("Schema name error: %s", la->left()->name().c_str()); + } + + // fold constant expression + auto foldRes = ExpressionUtils::foldConstantExpr(expr); + NG_RETURN_IF_ERROR(foldRes); + geoFuncExpr = static_cast(foldRes.value()); + + // Check schema and value type + std::string prop = la->right()->value().getStr(); + auto c = checkConstExpr(geoFuncExpr->args()->args()[1], prop, Expression::Kind::kFunctionCall); + NG_RETURN_IF_ERROR(c); + geoFuncExpr->args()->setArg(1, std::move(c).value()); + + // rewrite PropertyExpression + auto propExpr = lookupCtx_->isEdge ? ExpressionUtils::rewriteLabelAttr2EdgeProp(la) + : ExpressionUtils::rewriteLabelAttr2TagProp(la); + geoFuncExpr->args()->setArg(0, propExpr); + return geoFuncExpr; +} + StatusOr LookupValidator::checkConstExpr(Expression* expr, const std::string& prop, const ExprKind kind) { @@ -436,6 +543,35 @@ Expression* LookupValidator::reverseRelKind(RelationalExpression* expr) { return RelationalExpression::makeKind(pool, reversedKind, right->clone(), left->clone()); } +Expression* LookupValidator::reverseGeoPredicate(Expression* expr) { + if (expr->isRelExpr()) { + auto* relExpr = static_cast(expr); + return reverseRelKind(relExpr); + } else { + DCHECK(expr->kind() == Expression::Kind::kFunctionCall); + auto* funcExpr = static_cast(expr); + auto name = funcExpr->name(); + folly::toLowerAscii(name); + auto newName = name; + + if (name == "st_covers") { + newName = "st_coveredby"; + } else if (name == "st_coveredby") { + newName = "st_covers"; + } else if (name == "st_intersects") { + } else if (name == "st_dwithin") { + } + + auto* newArgList = ArgumentList::make(qctx_->objPool(), funcExpr->args()->numArgs()); + for (auto& arg : funcExpr->args()->args()) { + newArgList->addArgument(arg->clone()); + } + newArgList->setArg(0, funcExpr->args()->args()[1]); + newArgList->setArg(1, funcExpr->args()->args()[0]); + return FunctionCallExpression::make(qctx_->objPool(), newName, newArgList); + } +} + Status LookupValidator::getSchemaProvider(shared_ptr* provider) const { auto from = sentence()->from(); auto schemaMgr = qctx_->schemaMng(); diff --git a/src/graph/validator/LookupValidator.h b/src/graph/validator/LookupValidator.h index 6d6ada4d7ef..5a382b7b05e 100644 --- a/src/graph/validator/LookupValidator.h +++ b/src/graph/validator/LookupValidator.h @@ -37,12 +37,15 @@ class LookupValidator final : public Validator { StatusOr checkFilter(Expression* expr); Status checkRelExpr(RelationalExpression* expr); + Status checkGeoPredicate(const Expression* expr) const; StatusOr checkTSExpr(Expression* expr); StatusOr checkConstExpr(Expression* expr, const std::string& prop, const Expression::Kind kind); StatusOr rewriteRelExpr(RelationalExpression* expr); + StatusOr rewriteGeoPredicate(Expression* expr); Expression* reverseRelKind(RelationalExpression* expr); + Expression* reverseGeoPredicate(Expression* expr); const LookupSentence* sentence() const; int32_t schemaId() const; diff --git a/src/graph/validator/MaintainValidator.cpp b/src/graph/validator/MaintainValidator.cpp index feb7e4f3a4b..f05f7df47af 100644 --- a/src/graph/validator/MaintainValidator.cpp +++ b/src/graph/validator/MaintainValidator.cpp @@ -30,6 +30,8 @@ static Status validateColumns(const std::vector &columnSp column.type.set_type(type); if (meta::cpp2::PropertyType::FIXED_STRING == type) { column.type.set_type_length(spec->typeLen()); + } else if (meta::cpp2::PropertyType::GEOGRAPHY == type) { + column.type.set_geo_shape(spec->geoShape()); } for (const auto &property : spec->properties()->properties()) { if (property->isNullable()) { diff --git a/src/kvstore/test/CMakeLists.txt b/src/kvstore/test/CMakeLists.txt index 6f3db67d2cd..aa039b23854 100644 --- a/src/kvstore/test/CMakeLists.txt +++ b/src/kvstore/test/CMakeLists.txt @@ -33,6 +33,7 @@ set(KVSTORE_TEST_LIBS $ $ $ + $ ) nebula_add_test( diff --git a/src/meta/CMakeLists.txt b/src/meta/CMakeLists.txt index ea9f436d611..90c4835dd0b 100644 --- a/src/meta/CMakeLists.txt +++ b/src/meta/CMakeLists.txt @@ -162,6 +162,7 @@ set(meta_test_deps $ $ $ + $ ) nebula_add_subdirectory(http) diff --git a/src/meta/processors/index/CreateEdgeIndexProcessor.cpp b/src/meta/processors/index/CreateEdgeIndexProcessor.cpp index 21ff62be820..9fa97cda12d 100644 --- a/src/meta/processors/index/CreateEdgeIndexProcessor.cpp +++ b/src/meta/processors/index/CreateEdgeIndexProcessor.cpp @@ -156,6 +156,12 @@ void CreateEdgeIndexProcessor::process(const cpp2::CreateEdgeIndexReq& req) { handleErrorCode(nebula::cpp2::ErrorCode::E_INVALID_PARM); onFinished(); return; + } else if (col.type.get_type() == meta::cpp2::PropertyType::GEOGRAPHY && fields.size() > 1) { + // TODO(jie): Support joint index for geography + LOG(ERROR) << "Only support to create index on a single geography column currently"; + handleErrorCode(nebula::cpp2::ErrorCode::E_UNSUPPORTED); + onFinished(); + return; } columns.emplace_back(col); } diff --git a/src/meta/processors/index/CreateTagIndexProcessor.cpp b/src/meta/processors/index/CreateTagIndexProcessor.cpp index 0e7cdf5b768..2f6a1c0f624 100644 --- a/src/meta/processors/index/CreateTagIndexProcessor.cpp +++ b/src/meta/processors/index/CreateTagIndexProcessor.cpp @@ -154,6 +154,12 @@ void CreateTagIndexProcessor::process(const cpp2::CreateTagIndexReq& req) { handleErrorCode(nebula::cpp2::ErrorCode::E_INVALID_PARM); onFinished(); return; + } else if (col.type.get_type() == meta::cpp2::PropertyType::GEOGRAPHY && fields.size() > 1) { + // TODO(jie): Support joint index for geography + LOG(ERROR) << "Only support to create index on a single geography column currently"; + handleErrorCode(nebula::cpp2::ErrorCode::E_UNSUPPORTED); + onFinished(); + return; } columns.emplace_back(col); } diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index 8ccc3490942..4d647db9960 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -91,6 +91,27 @@ TEST_F(ParserTest, TestSchemaCreation) { auto result = parse(query); ASSERT_TRUE(result.ok()) << result.status(); } + // Geo spatial + { + std::string query = "CREATE TAG any_shape(geo geography)"; + auto result = parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + std::string query = "CREATE TAG any_shape(geo geography(point))"; + auto result = parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + std::string query = "CREATE TAG any_shape(geo geography(linestring))"; + auto result = parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + std::string query = "CREATE TAG any_shape(geo geography(polygon))"; + auto result = parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } } TEST_F(ParserTest, Go) { diff --git a/src/storage/admin/RebuildEdgeIndexTask.cpp b/src/storage/admin/RebuildEdgeIndexTask.cpp index f8ceae17e7e..5ffe839f29d 100644 --- a/src/storage/admin/RebuildEdgeIndexTask.cpp +++ b/src/storage/admin/RebuildEdgeIndexTask.cpp @@ -137,20 +137,41 @@ nebula::cpp2::ErrorCode RebuildEdgeIndexTask::buildIndexGlobal(GraphSpaceID spac for (const auto& item : items) { if (item->get_schema_id().get_edge_type() == edgeType) { - auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), item->get_fields()); - if (!valuesRet.ok()) { - LOG(WARNING) << "Collect index value failed"; - continue; + if (item->get_fields().size() == 1 && item->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto valuesRet = + IndexKeyUtils::collectIndexValueForGeography(reader.get(), item->get_fields().back()); + if (!valuesRet.ok()) { + LOG(WARNING) << "Collect index value failed"; + continue; + } + auto indexKeys = IndexKeyUtils::edgeIndexKeysForGeography(vidSize, + part, + item->get_index_id(), + source.toString(), + ranking, + destination.toString(), + std::move(valuesRet).value()); + for (auto& indexKey : indexKeys) { + batchSize += indexKey.size() + indexVal.size(); + data.emplace_back(std::move(indexKey), indexVal); + } + } else { + auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), item->get_fields()); + if (!valuesRet.ok()) { + LOG(WARNING) << "Collect index value failed"; + continue; + } + auto indexKey = IndexKeyUtils::edgeIndexKey(vidSize, + part, + item->get_index_id(), + source.toString(), + ranking, + destination.toString(), + std::move(valuesRet).value()); + batchSize += indexKey.size() + indexVal.size(); + data.emplace_back(std::move(indexKey), indexVal); } - auto indexKey = IndexKeyUtils::edgeIndexKey(vidSize, - part, - item->get_index_id(), - source.toString(), - ranking, - destination.toString(), - std::move(valuesRet).value()); - batchSize += indexKey.size() + indexVal.size(); - data.emplace_back(std::move(indexKey), indexVal); } } iter->next(); diff --git a/src/storage/admin/RebuildTagIndexTask.cpp b/src/storage/admin/RebuildTagIndexTask.cpp index 3bcdd4879da..15231cd27c0 100644 --- a/src/storage/admin/RebuildTagIndexTask.cpp +++ b/src/storage/admin/RebuildTagIndexTask.cpp @@ -128,15 +128,31 @@ nebula::cpp2::ErrorCode RebuildTagIndexTask::buildIndexGlobal(GraphSpaceID space for (const auto& item : items) { if (item->get_schema_id().get_tag_id() == tagID) { - auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), item->get_fields()); - if (!valuesRet.ok()) { - LOG(WARNING) << "Collect index value failed"; - continue; + if (item->get_fields().size() == 1 && item->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto valuesRet = + IndexKeyUtils::collectIndexValueForGeography(reader.get(), item->get_fields().back()); + if (!valuesRet.ok()) { + LOG(WARNING) << "Collect index value for geography failed"; + continue; + } + auto indexKeys = IndexKeyUtils::vertexIndexKeysForGeography( + vidSize, part, item->get_index_id(), vertex.toString(), std::move(valuesRet).value()); + for (auto& indexKey : indexKeys) { + batchSize += indexKey.size() + indexVal.size(); + data.emplace_back(std::move(indexKey), indexVal); + } + } else { + auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), item->get_fields()); + if (!valuesRet.ok()) { + LOG(WARNING) << "Collect index value failed"; + continue; + } + auto indexKey = IndexKeyUtils::vertexIndexKey( + vidSize, part, item->get_index_id(), vertex.toString(), std::move(valuesRet).value()); + batchSize += indexKey.size() + indexVal.size(); + data.emplace_back(std::move(indexKey), indexVal); } - auto indexKey = IndexKeyUtils::vertexIndexKey( - vidSize, part, item->get_index_id(), vertex.toString(), std::move(valuesRet).value()); - batchSize += indexKey.size() + indexVal.size(); - data.emplace_back(std::move(indexKey), indexVal); } } iter->next(); diff --git a/src/storage/exec/UpdateNode.h b/src/storage/exec/UpdateNode.h index 01c4ac60b50..17bb543e6ca 100644 --- a/src/storage/exec/UpdateNode.h +++ b/src/storage/exec/UpdateNode.h @@ -362,17 +362,39 @@ class UpdateTagNode : public UpdateNode { LOG(ERROR) << "Bad format row"; return folly::none; } - auto oi = indexKey(partId, vId, reader_, index); - if (!oi.empty()) { - auto iState = context_->env()->getIndexState(context_->spaceId(), partId); - if (context_->env()->checkRebuilding(iState)) { - auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); - batchHolder->put(std::move(deleteOpKey), std::move(oi)); - } else if (context_->env()->checkIndexLocked(iState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - return folly::none; - } else { - batchHolder->remove(std::move(oi)); + if (index->get_fields().size() == 1 && + index->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto ois = indexKeysForGeography(partId, vId, reader_, index); + if (!ois.empty()) { + auto iState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(iState)) { + for (auto& oi : ois) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(oi)); + } + } else if (context_->env()->checkIndexLocked(iState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + for (auto& oi : ois) { + batchHolder->remove(std::move(oi)); + } + } + } + } else { + auto oi = indexKey(partId, vId, reader_, index); + if (!oi.empty()) { + auto iState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(iState)) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(oi)); + } else if (context_->env()->checkIndexLocked(iState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + batchHolder->remove(std::move(oi)); + } } } } @@ -386,19 +408,42 @@ class UpdateTagNode : public UpdateNode { LOG(ERROR) << "Bad format row"; return folly::none; } - auto ni = indexKey(partId, vId, nReader.get(), index); - if (!ni.empty()) { - auto v = CommonUtils::ttlValue(schema_, nReader.get()); - auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; - auto indexState = context_->env()->getIndexState(context_->spaceId(), partId); - if (context_->env()->checkRebuilding(indexState)) { - auto modifyKey = OperationKeyUtils::modifyOperationKey(partId, std::move(ni)); - batchHolder->put(std::move(modifyKey), std::move(niv)); - } else if (context_->env()->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - return folly::none; - } else { - batchHolder->put(std::move(ni), std::move(niv)); + if (index->get_fields().size() == 1 && index->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto nis = indexKeysForGeography(partId, vId, nReader.get(), index); + if (!nis.empty()) { + auto v = CommonUtils::ttlValue(schema_, nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + auto indexState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(indexState)) { + for (auto& ni : nis) { + auto modifyKey = OperationKeyUtils::modifyOperationKey(partId, std::move(ni)); + batchHolder->put(std::move(modifyKey), std::string(niv)); + } + } else if (context_->env()->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + for (auto& ni : nis) { + batchHolder->put(std::move(ni), std::string(niv)); + } + } + } + } else { + auto ni = indexKey(partId, vId, nReader.get(), index); + if (!ni.empty()) { + auto v = CommonUtils::ttlValue(schema_, nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + auto indexState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(indexState)) { + auto modifyKey = OperationKeyUtils::modifyOperationKey(partId, std::move(ni)); + batchHolder->put(std::move(modifyKey), std::move(niv)); + } else if (context_->env()->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + batchHolder->put(std::move(ni), std::move(niv)); + } } } } @@ -421,6 +466,21 @@ class UpdateTagNode : public UpdateNode { context_->vIdLen(), partId, index->get_index_id(), vId, std::move(values).value()); } + std::vector indexKeysForGeography( + PartitionID partId, + const VertexID& vId, + RowReader* reader, + std::shared_ptr index) { + DCHECK(index->get_fields().size() == 1 && + index->get_fields().back().get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY); + auto values = IndexKeyUtils::collectIndexValueForGeography(reader, index->get_fields().back()); + if (!values.ok()) { + return {}; + } + return IndexKeyUtils::vertexIndexKeysForGeography( + context_->vIdLen(), partId, index->get_index_id(), vId, std::move(values).value()); + } + private: TagContext* tagContext_; TagID tagId_; @@ -677,17 +737,39 @@ class UpdateEdgeNode : public UpdateNode { LOG(ERROR) << "Bad format row"; return folly::none; } - auto oi = indexKey(partId, reader_, edgeKey, index); - if (!oi.empty()) { - auto iState = context_->env()->getIndexState(context_->spaceId(), partId); - if (context_->env()->checkRebuilding(iState)) { - auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); - batchHolder->put(std::move(deleteOpKey), std::move(oi)); - } else if (context_->env()->checkIndexLocked(iState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - return folly::none; - } else { - batchHolder->remove(std::move(oi)); + if (index->get_fields().size() == 1 && + index->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto ois = indexKeysForGeography(partId, reader_, edgeKey, index); + if (!ois.empty()) { + auto iState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(iState)) { + for (auto& oi : ois) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(oi)); + } + } else if (context_->env()->checkIndexLocked(iState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + for (auto& oi : ois) { + batchHolder->remove(std::move(oi)); + } + } + } + } else { + auto oi = indexKey(partId, reader_, edgeKey, index); + if (!oi.empty()) { + auto iState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(iState)) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(oi)); + } else if (context_->env()->checkIndexLocked(iState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + batchHolder->remove(std::move(oi)); + } } } } @@ -701,19 +783,42 @@ class UpdateEdgeNode : public UpdateNode { LOG(ERROR) << "Bad format row"; return folly::none; } - auto nik = indexKey(partId, nReader.get(), edgeKey, index); - if (!nik.empty()) { - auto v = CommonUtils::ttlValue(schema_, nReader.get()); - auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; - auto indexState = context_->env()->getIndexState(context_->spaceId(), partId); - if (context_->env()->checkRebuilding(indexState)) { - auto modifyKey = OperationKeyUtils::modifyOperationKey(partId, std::move(nik)); - batchHolder->put(std::move(modifyKey), std::move(niv)); - } else if (context_->env()->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - return folly::none; - } else { - batchHolder->put(std::move(nik), std::move(niv)); + if (index->get_fields().size() == 1 && index->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto niks = indexKeysForGeography(partId, nReader.get(), edgeKey, index); + if (!niks.empty()) { + auto v = CommonUtils::ttlValue(schema_, nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + auto indexState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(indexState)) { + for (auto& nik : niks) { + auto modifyKey = OperationKeyUtils::modifyOperationKey(partId, std::move(nik)); + batchHolder->put(std::move(modifyKey), std::string(niv)); + } + } else if (context_->env()->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + for (auto& nik : niks) { + batchHolder->put(std::move(nik), std::string(niv)); + } + } + } + } else { + auto nik = indexKey(partId, nReader.get(), edgeKey, index); + if (!nik.empty()) { + auto v = CommonUtils::ttlValue(schema_, nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + auto indexState = context_->env()->getIndexState(context_->spaceId(), partId); + if (context_->env()->checkRebuilding(indexState)) { + auto modifyKey = OperationKeyUtils::modifyOperationKey(partId, std::move(nik)); + batchHolder->put(std::move(modifyKey), std::move(niv)); + } else if (context_->env()->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return folly::none; + } else { + batchHolder->put(std::move(nik), std::move(niv)); + } } } } @@ -732,6 +837,7 @@ class UpdateEdgeNode : public UpdateNode { return encodeBatchValue(batchHolder->getBatch()); } + // TODO(jie) The indexKey code has been defined many times in many files... std::string indexKey(PartitionID partId, RowReader* reader, const cpp2::EdgeKey& edgeKey, @@ -749,6 +855,26 @@ class UpdateEdgeNode : public UpdateNode { std::move(values).value()); } + std::vector indexKeysForGeography( + PartitionID partId, + RowReader* reader, + const cpp2::EdgeKey& edgeKey, + std::shared_ptr index) { + DCHECK(index->get_fields().size() == 1 && + index->get_fields().back().get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY); + auto values = IndexKeyUtils::collectIndexValueForGeography(reader, index->get_fields().back()); + if (!values.ok()) { + return {}; + } + return IndexKeyUtils::edgeIndexKeysForGeography(context_->vIdLen(), + partId, + index->get_index_id(), + edgeKey.get_src().getStr(), + edgeKey.get_ranking(), + edgeKey.get_dst().getStr(), + std::move(values).value()); + } + private: EdgeContext* edgeContext_; EdgeType edgeType_; diff --git a/src/storage/index/LookupBaseProcessor-inl.h b/src/storage/index/LookupBaseProcessor-inl.h index 5fb46d38a83..6a280f078bb 100644 --- a/src/storage/index/LookupBaseProcessor-inl.h +++ b/src/storage/index/LookupBaseProcessor-inl.h @@ -210,7 +210,11 @@ StatusOr> LookupBaseProcessor::buildPlan( auto it = std::find_if(fields.begin(), fields.end(), [&yieldCol](const auto& columnDef) { return yieldCol == columnDef.get_name(); }); - if (it == fields.end()) { + if (it == fields.end() || + it->get_type().get_type() == + nebula::meta::cpp2::PropertyType::GEOGRAPHY) { // geography index just stores + // S2CellId, so must read the + // original geo data. needData = true; break; } diff --git a/src/storage/mutate/AddEdgesProcessor.cpp b/src/storage/mutate/AddEdgesProcessor.cpp index 2d0b29f5916..f2d9ff8513a 100644 --- a/src/storage/mutate/AddEdgesProcessor.cpp +++ b/src/storage/mutate/AddEdgesProcessor.cpp @@ -245,19 +245,43 @@ void AddEdgesProcessor::doProcessWithIndex(const cpp2::AddEdgesRequest& req) { * step 1 , Delete old version index if exists. */ if (oReader != nullptr) { - auto oi = indexKey(partId, oReader.get(), key, index); - if (!oi.empty()) { - // Check the index is building for the specified partition or not. - auto indexState = env_->getIndexState(spaceId_, partId); - if (env_->checkRebuilding(indexState)) { - auto delOpKey = OperationKeyUtils::deleteOperationKey(partId); - batchHolder->put(std::move(delOpKey), std::move(oi)); - } else if (env_->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; - break; - } else { - batchHolder->remove(std::move(oi)); + if (index->get_fields().size() == 1 && + index->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto ois = indexKeysForGeography(partId, oReader.get(), key, index); + if (!ois.empty()) { + // Check the index is building for the specified partition or not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + for (auto& oi : ois) { + auto delOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(delOpKey), std::move(oi)); + } + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + for (auto& oi : ois) { + batchHolder->remove(std::move(oi)); + } + } + } + } else { + auto oi = indexKey(partId, oReader.get(), key, index); + if (!oi.empty()) { + // Check the index is building for the specified partition or not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + auto delOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(delOpKey), std::move(oi)); + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + batchHolder->remove(std::move(oi)); + } } } } @@ -265,21 +289,47 @@ void AddEdgesProcessor::doProcessWithIndex(const cpp2::AddEdgesRequest& req) { * step 2 , Insert new edge index */ if (nReader != nullptr) { - auto nik = indexKey(partId, nReader.get(), key, index); - if (!nik.empty()) { - auto v = CommonUtils::ttlValue(schema.get(), nReader.get()); - auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; - // Check the index is building for the specified partition or not. - auto indexState = env_->getIndexState(spaceId_, partId); - if (env_->checkRebuilding(indexState)) { - auto opKey = OperationKeyUtils::modifyOperationKey(partId, std::move(nik)); - batchHolder->put(std::move(opKey), std::move(niv)); - } else if (env_->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; - break; - } else { - batchHolder->put(std::move(nik), std::move(niv)); + if (index->get_fields().size() == 1 && + index->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto niks = indexKeysForGeography(partId, nReader.get(), key, index); + if (!niks.empty()) { + auto v = CommonUtils::ttlValue(schema.get(), nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + // Check the index is building for the specified partition or not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + for (auto& nik : niks) { + auto opKey = OperationKeyUtils::modifyOperationKey(partId, std::move(nik)); + batchHolder->put(std::move(opKey), std::string(niv)); + } + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + for (auto& nik : niks) { + batchHolder->put(std::move(nik), std::string(niv)); + } + } + } + } else { + auto nik = indexKey(partId, nReader.get(), key, index); + if (!nik.empty()) { + auto v = CommonUtils::ttlValue(schema.get(), nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + // Check the index is building for the specified partition or not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + auto opKey = OperationKeyUtils::modifyOperationKey(partId, std::move(nik)); + batchHolder->put(std::move(opKey), std::move(niv)); + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + batchHolder->put(std::move(nik), std::move(niv)); + } } } } @@ -462,5 +512,26 @@ std::string AddEdgesProcessor::indexKey(PartitionID partId, std::move(values).value()); } +std::vector AddEdgesProcessor::indexKeysForGeography( + PartitionID partId, + RowReader* reader, + const folly::StringPiece& rawKey, + std::shared_ptr index) { + DCHECK(index->get_fields().size() == 1 && + index->get_fields().back().get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY); + auto values = IndexKeyUtils::collectIndexValueForGeography(reader, index->get_fields().back()); + if (!values.ok()) { + return {}; + } + return IndexKeyUtils::edgeIndexKeysForGeography( + spaceVidLen_, + partId, + index->get_index_id(), + NebulaKeyUtils::getSrcId(spaceVidLen_, rawKey).str(), + NebulaKeyUtils::getRank(spaceVidLen_, rawKey), + NebulaKeyUtils::getDstId(spaceVidLen_, rawKey).str(), + std::move(values).value()); +} + } // namespace storage } // namespace nebula diff --git a/src/storage/mutate/AddEdgesProcessor.h b/src/storage/mutate/AddEdgesProcessor.h index 90596995105..fead9dfdbc1 100644 --- a/src/storage/mutate/AddEdgesProcessor.h +++ b/src/storage/mutate/AddEdgesProcessor.h @@ -48,6 +48,12 @@ class AddEdgesProcessor : public BaseProcessor { const folly::StringPiece& rawKey, std::shared_ptr index); + std::vector indexKeysForGeography( + PartitionID partId, + RowReader* reader, + const folly::StringPiece& rawKey, + std::shared_ptr index); + private: GraphSpaceID spaceId_; std::vector> indexes_; diff --git a/src/storage/mutate/AddVerticesProcessor.cpp b/src/storage/mutate/AddVerticesProcessor.cpp index 1506d024b25..b5950c16b4a 100644 --- a/src/storage/mutate/AddVerticesProcessor.cpp +++ b/src/storage/mutate/AddVerticesProcessor.cpp @@ -218,24 +218,49 @@ void AddVerticesProcessor::doProcessWithIndex(const cpp2::AddVerticesRequest& re } for (auto& index : indexes_) { if (tagId == index->get_schema_id().get_tag_id()) { + auto indexFields = index->get_fields(); /* * step 1 , Delete old version index if exists. */ if (oReader != nullptr) { - auto oi = indexKey(partId, vid, oReader.get(), index); - if (!oi.empty()) { - // Check the index is building for the specified partition or - // not. - auto indexState = env_->getIndexState(spaceId_, partId); - if (env_->checkRebuilding(indexState)) { - auto delOpKey = OperationKeyUtils::deleteOperationKey(partId); - batchHolder->put(std::move(delOpKey), std::move(oi)); - } else if (env_->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; - break; - } else { - batchHolder->remove(std::move(oi)); + if (indexFields.size() == 1 && + indexFields.back().get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY) { + auto ois = indexKeysForGeography(partId, vid, oReader.get(), index); + if (!ois.empty()) { + // Check the index is building for the specified partition or + // not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + for (auto& oi : ois) { + auto delOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(delOpKey), std::move(oi)); + } + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + for (auto& oi : ois) { + batchHolder->remove(std::move(oi)); + } + } + } + } else { + auto oi = indexKey(partId, vid, oReader.get(), index); + if (!oi.empty()) { + // Check the index is building for the specified partition or + // not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + auto delOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(delOpKey), std::move(oi)); + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + batchHolder->remove(std::move(oi)); + } } } } @@ -244,22 +269,48 @@ void AddVerticesProcessor::doProcessWithIndex(const cpp2::AddVerticesRequest& re * step 2 , Insert new vertex index */ if (nReader != nullptr) { - auto nik = indexKey(partId, vid, nReader.get(), index); - if (!nik.empty()) { - auto v = CommonUtils::ttlValue(schema.get(), nReader.get()); - auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; - // Check the index is building for the specified partition or - // not. - auto indexState = env_->getIndexState(spaceId_, partId); - if (env_->checkRebuilding(indexState)) { - auto opKey = OperationKeyUtils::modifyOperationKey(partId, nik); - batchHolder->put(std::move(opKey), std::move(niv)); - } else if (env_->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; - break; - } else { - batchHolder->put(std::move(nik), std::move(niv)); + if (indexFields.size() == 1 && + indexFields.back().get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY) { + auto niks = indexKeysForGeography(partId, vid, nReader.get(), index); + if (!niks.empty()) { + auto v = CommonUtils::ttlValue(schema.get(), nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + // Check the index is building for the specified partition or + // not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + for (const auto& nik : niks) { + auto opKey = OperationKeyUtils::modifyOperationKey(partId, nik); + batchHolder->put(std::move(opKey), std::string(niv)); + } + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + for (auto& nik : niks) { + batchHolder->put(std::move(nik), std::string(niv)); + } + } + } + } else { + auto nik = indexKey(partId, vid, nReader.get(), index); + if (!nik.empty()) { + auto v = CommonUtils::ttlValue(schema.get(), nReader.get()); + auto niv = v.ok() ? IndexKeyUtils::indexVal(std::move(v).value()) : ""; + // Check the index is building for the specified partition or + // not. + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + auto opKey = OperationKeyUtils::modifyOperationKey(partId, nik); + batchHolder->put(std::move(opKey), std::move(niv)); + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + code = nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + break; + } else { + batchHolder->put(std::move(nik), std::move(niv)); + } } } } @@ -295,7 +346,7 @@ void AddVerticesProcessor::doProcessWithIndex(const cpp2::AddVerticesRequest& re handleAsync(spaceId_, partId, retCode); }); } -} +} // namespace storage ErrorOr AddVerticesProcessor::findOldValue( PartitionID partId, const VertexID& vId, TagID tagId) { @@ -326,5 +377,22 @@ std::string AddVerticesProcessor::indexKey(PartitionID partId, spaceVidLen_, partId, index->get_index_id(), vId, std::move(values).value()); } +std::vector AddVerticesProcessor::indexKeysForGeography( + PartitionID partId, + const VertexID& vId, + RowReader* reader, + std::shared_ptr index) { + const auto& fields = index->get_fields(); + DCHECK(fields.size() == 1 && + fields.back().get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY); + auto values = IndexKeyUtils::collectIndexValueForGeography(reader, fields.back()); + if (!values.ok()) { + return {}; + } + + return IndexKeyUtils::vertexIndexKeysForGeography( + spaceVidLen_, partId, index->get_index_id(), vId, std::move(values).value()); +} + } // namespace storage } // namespace nebula diff --git a/src/storage/mutate/AddVerticesProcessor.h b/src/storage/mutate/AddVerticesProcessor.h index 3c8483beb76..0ec041ac212 100644 --- a/src/storage/mutate/AddVerticesProcessor.h +++ b/src/storage/mutate/AddVerticesProcessor.h @@ -44,6 +44,12 @@ class AddVerticesProcessor : public BaseProcessor { RowReader* reader, std::shared_ptr index); + std::vector indexKeysForGeography( + PartitionID partId, + const VertexID& vId, + RowReader* reader, + std::shared_ptr index); + private: GraphSpaceID spaceId_; std::vector> indexes_; diff --git a/src/storage/mutate/DeleteEdgesProcessor.cpp b/src/storage/mutate/DeleteEdgesProcessor.cpp index b8d5dff3c36..ced5e6494a9 100644 --- a/src/storage/mutate/DeleteEdgesProcessor.cpp +++ b/src/storage/mutate/DeleteEdgesProcessor.cpp @@ -162,22 +162,48 @@ ErrorOr DeleteEdgesProcessor::deleteEdges( return nebula::cpp2::ErrorCode::E_INVALID_DATA; } } - auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), index->get_fields()); - if (!valuesRet.ok()) { - continue; - } - auto indexKey = IndexKeyUtils::edgeIndexKey( - spaceVidLen_, partId, indexId, srcId, rank, dstId, std::move(valuesRet).value()); - - auto indexState = env_->getIndexState(spaceId_, partId); - if (env_->checkRebuilding(indexState)) { - auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); - batchHolder->put(std::move(deleteOpKey), std::move(indexKey)); - } else if (env_->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - return nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + if (index->get_fields().size() == 1 && index->get_fields().back().get_type().get_type() == + meta::cpp2::PropertyType::GEOGRAPHY) { + auto valuesRet = IndexKeyUtils::collectIndexValueForGeography( + reader.get(), index->get_fields().back()); + if (!valuesRet.ok()) { + continue; + } + auto indexKeys = IndexKeyUtils::edgeIndexKeysForGeography( + spaceVidLen_, partId, indexId, srcId, rank, dstId, std::move(valuesRet).value()); + + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + for (auto& indexKey : indexKeys) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(indexKey)); + } + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + } else { + for (auto& indexKey : indexKeys) { + batchHolder->remove(std::move(indexKey)); + } + } } else { - batchHolder->remove(std::move(indexKey)); + auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), index->get_fields()); + if (!valuesRet.ok()) { + continue; + } + auto indexKey = IndexKeyUtils::edgeIndexKey( + spaceVidLen_, partId, indexId, srcId, rank, dstId, std::move(valuesRet).value()); + + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(indexKey)); + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + } else { + batchHolder->remove(std::move(indexKey)); + } } } } diff --git a/src/storage/mutate/DeleteVerticesProcessor.cpp b/src/storage/mutate/DeleteVerticesProcessor.cpp index 1a42c70b519..3edf09d95ae 100644 --- a/src/storage/mutate/DeleteVerticesProcessor.cpp +++ b/src/storage/mutate/DeleteVerticesProcessor.cpp @@ -146,23 +146,50 @@ ErrorOr DeleteVerticesProcessor::deleteVer } } const auto& cols = index->get_fields(); - auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), cols); - if (!valuesRet.ok()) { - continue; - } - auto indexKey = IndexKeyUtils::vertexIndexKey( - spaceVidLen_, partId, indexId, vertex.getStr(), std::move(valuesRet).value()); - - // Check the index is building for the specified partition or not - auto indexState = env_->getIndexState(spaceId_, partId); - if (env_->checkRebuilding(indexState)) { - auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); - batchHolder->put(std::move(deleteOpKey), std::move(indexKey)); - } else if (env_->checkIndexLocked(indexState)) { - LOG(ERROR) << "The index has been locked: " << index->get_index_name(); - return nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + if (cols.size() == 1 && + cols.back().get_type().get_type() == meta::cpp2::PropertyType::GEOGRAPHY) { + auto valuesRet = + IndexKeyUtils::collectIndexValueForGeography(reader.get(), cols.back()); + if (!valuesRet.ok()) { + continue; + } + auto indexKeys = IndexKeyUtils::vertexIndexKeysForGeography( + spaceVidLen_, partId, indexId, vertex.getStr(), std::move(valuesRet).value()); + + // Check the index is building for the specified partition or not + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + for (auto& indexKey : indexKeys) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(indexKey)); + } + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + } else { + for (auto& indexKey : indexKeys) { + batchHolder->remove(std::move(indexKey)); + } + } } else { - batchHolder->remove(std::move(indexKey)); + auto valuesRet = IndexKeyUtils::collectIndexValues(reader.get(), cols); + if (!valuesRet.ok()) { + continue; + } + auto indexKey = IndexKeyUtils::vertexIndexKey( + spaceVidLen_, partId, indexId, vertex.getStr(), std::move(valuesRet).value()); + + // Check the index is building for the specified partition or not + auto indexState = env_->getIndexState(spaceId_, partId); + if (env_->checkRebuilding(indexState)) { + auto deleteOpKey = OperationKeyUtils::deleteOperationKey(partId); + batchHolder->put(std::move(deleteOpKey), std::move(indexKey)); + } else if (env_->checkIndexLocked(indexState)) { + LOG(ERROR) << "The index has been locked: " << index->get_index_name(); + return nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR; + } else { + batchHolder->remove(std::move(indexKey)); + } } } } diff --git a/src/storage/test/CMakeLists.txt b/src/storage/test/CMakeLists.txt index 33dee520e63..9ad3b3e7529 100644 --- a/src/storage/test/CMakeLists.txt +++ b/src/storage/test/CMakeLists.txt @@ -49,6 +49,7 @@ set(storage_test_deps $ $ $ + $ ) nebula_add_test( diff --git a/src/tools/db-dump/CMakeLists.txt b/src/tools/db-dump/CMakeLists.txt index d48e0f73575..893bd53e494 100644 --- a/src/tools/db-dump/CMakeLists.txt +++ b/src/tools/db-dump/CMakeLists.txt @@ -45,6 +45,7 @@ set(tools_test_deps $ $ $ + $ ) nebula_add_executable( diff --git a/src/tools/db-upgrade/CMakeLists.txt b/src/tools/db-upgrade/CMakeLists.txt index 7b3670d20ee..4aa3f68be00 100644 --- a/src/tools/db-upgrade/CMakeLists.txt +++ b/src/tools/db-upgrade/CMakeLists.txt @@ -53,6 +53,7 @@ nebula_add_executable( $ $ $ + $ LIBRARIES ${ROCKSDB_LIBRARIES} ${THRIFT_LIBRARIES} diff --git a/src/tools/meta-dump/CMakeLists.txt b/src/tools/meta-dump/CMakeLists.txt index 663e4686366..46c508f3b1f 100644 --- a/src/tools/meta-dump/CMakeLists.txt +++ b/src/tools/meta-dump/CMakeLists.txt @@ -50,6 +50,7 @@ nebula_add_executable( $ $ $ + $ LIBRARIES ${ROCKSDB_LIBRARIES} ${THRIFT_LIBRARIES} diff --git a/src/tools/simple-kv-verify/CMakeLists.txt b/src/tools/simple-kv-verify/CMakeLists.txt index 75c8b9cfb88..17a2592fe13 100644 --- a/src/tools/simple-kv-verify/CMakeLists.txt +++ b/src/tools/simple-kv-verify/CMakeLists.txt @@ -44,6 +44,7 @@ nebula_add_executable( $ $ $ + $ LIBRARIES ${ROCKSDB_LIBRARIES} ${THRIFT_LIBRARIES} diff --git a/src/tools/storage-perf/CMakeLists.txt b/src/tools/storage-perf/CMakeLists.txt index 5a4400210d2..e278053ecb2 100644 --- a/src/tools/storage-perf/CMakeLists.txt +++ b/src/tools/storage-perf/CMakeLists.txt @@ -45,6 +45,7 @@ set(perf_test_deps $ $ $ + $ ) nebula_add_executable( diff --git a/tests/tck/features/geo/GeoBase.feature b/tests/tck/features/geo/GeoBase.feature new file mode 100644 index 00000000000..fc98f316f9e --- /dev/null +++ b/tests/tck/features/geo/GeoBase.feature @@ -0,0 +1,704 @@ +# Copyright (c) 2020 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. +Feature: Geo base + + Background: + Given an empty graph + And create a space with following options: + | partition_num | 9 | + | replica_factor | 1 | + | vid_type | FIXED_STRING(30) | + | charset | utf8 | + | collate | utf8_bin | + And having executed: + """ + CREATE TAG any_shape(geo geography); + CREATE TAG only_point(geo geography(point)); + CREATE TAG only_linestring(geo geography(linestring)); + CREATE TAG only_polygon(geo geography(polygon)); + CREATE EDGE any_shape_edge(geo geography); + """ + And wait 3 seconds + + Scenario: test geo schema + # Desc geo schema + When executing query: + """ + DESC TAG any_shape; + """ + Then the result should be, in any order: + | Field | Type | Null | Default | Comment | + | "geo" | "geography" | "YES" | EMPTY | EMPTY | + When executing query: + """ + DESC TAG only_point; + """ + Then the result should be, in any order: + | Field | Type | Null | Default | Comment | + | "geo" | "geography(point)" | "YES" | EMPTY | EMPTY | + When executing query: + """ + DESC TAG only_linestring; + """ + Then the result should be, in any order: + | Field | Type | Null | Default | Comment | + | "geo" | "geography(linestring)" | "YES" | EMPTY | EMPTY | + When executing query: + """ + DESC TAG only_polygon; + """ + Then the result should be, in any order: + | Field | Type | Null | Default | Comment | + | "geo" | "geography(polygon)" | "YES" | EMPTY | EMPTY | + When executing query: + """ + DESC EDGE any_shape_edge; + """ + Then the result should be, in any order: + | Field | Type | Null | Default | Comment | + | "geo" | "geography" | "YES" | EMPTY | EMPTY | + # Show create geo schema + When executing query: + """ + SHOW CREATE TAG only_point; + """ + Then the result should be, in any order: + | Tag | Create Tag | + | "only_point" | 'CREATE TAG `only_point` (\n `geo` geography(point) NULL\n) ttl_duration = 0, ttl_col = ""' | + + Scenario: test geo CURD + # Any geo shape(point/linestring/polygon) is allowed to insert to the column geography + When try to execute query: + """ + INSERT VERTEX any_shape(geo) VALUES "101":(ST_GeogFromText("POINT(3 8)")); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX any_shape(geo) VALUES "102":(ST_GeogFromText("LINESTRING(3 8, 4.7 73.23)")); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX any_shape(geo) VALUES "103":(ST_GeogFromText("POLYGON((0 1, 1 2, 2 3, 0 1))")); + """ + Then the execution should be successful + # Only point is allowed to insert to the column geograph(point) + When executing query: + """ + INSERT VERTEX only_point(geo) VALUES "201":(ST_GeogFromText("POINT(3 8)")); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX only_point(geo) VALUES "202":(ST_GeogFromText("LINESTRING(3 8, 4.7 73.23)")); + """ + Then a ExecutionError should be raised at runtime: Storage Error: The data type does not meet the requirements. Use the correct type of data. + When executing query: + """ + INSERT VERTEX only_point(geo) VALUES "203":(ST_GeogFromText("POLYGON((0 1, 1 2, 2 3, 0 1))")); + """ + Then a ExecutionError should be raised at runtime: Storage Error: The data type does not meet the requirements. Use the correct type of data. + # Only linestring is allowed to insert to the column geograph(linestring) + When executing query: + """ + INSERT VERTEX only_linestring(geo) VALUES "301":(ST_GeogFromText("POINT(3 8)")); + """ + Then a ExecutionError should be raised at runtime: Storage Error: The data type does not meet the requirements. Use the correct type of data. + When executing query: + """ + INSERT VERTEX only_linestring(geo) VALUES "302":(ST_GeogFromText("LINESTRING(3 8, 4.7 73.23)")); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX only_linestring(geo) VALUES "303":(ST_GeogFromText("POLYGON((0 1, 1 2, 2 3, 0 1))")); + """ + Then a ExecutionError should be raised at runtime: Storage Error: The data type does not meet the requirements. Use the correct type of data. + # Only polygon is allowed to insert to the column geograph(polygon) + When executing query: + """ + INSERT VERTEX only_polygon(geo) VALUES "401":(ST_GeogFromText("POINT(3 8)")); + """ + Then a ExecutionError should be raised at runtime: Storage Error: The data type does not meet the requirements. Use the correct type of data. + When executing query: + """ + INSERT VERTEX only_polygon(geo) VALUES "402":(ST_GeogFromText("LINESTRING(3 8, 4.7 73.23)")); + """ + Then a ExecutionError should be raised at runtime: Storage Error: The data type does not meet the requirements. Use the correct type of data. + When executing query: + """ + INSERT VERTEX only_polygon(geo) VALUES "403":(ST_GeogFromText("POLYGON((0 1, 1 2, 2 3, 0 1))")); + """ + Then the execution should be successful + When executing query: + """ + INSERT EDGE any_shape_edge(geo) VALUES "201"->"302":(ST_GeogFromText("POLYGON((0 1, 1 2, 2 3, 0 1))")); + """ + Then the execution should be successful + # Fetch the geo column + When executing query: + """ + FETCH PROP ON any_shape "101","102","103" YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "101" | "POINT(3 8)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + FETCH PROP ON only_point "201","202","203" YIELD ST_ASText(only_point.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_point.geo) | + | "201" | "POINT(3 8)" | + When executing query: + """ + FETCH PROP ON only_linestring "301","302","303" YIELD ST_ASText(only_linestring.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_linestring.geo) | + | "302" | "LINESTRING(3 8,4.7 73.23)" | + When executing query: + """ + FETCH PROP ON only_polygon "401","402","403" YIELD ST_ASText(only_polygon.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_polygon.geo) | + | "403" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + FETCH PROP ON any_shape_edge "201"->"302" YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | any_shape_edge._src | any_shape_edge._dst | any_shape_edge._rank | ST_ASText(any_shape_edge.geo) | + | "201" | "302" | 0 | "POLYGON((0 1,1 2,2 3,0 1))" | + # Create index on geo column + When executing query: + """ + CREATE TAG INDEX any_shape_geo_index ON any_shape(geo); + """ + Then the execution should be successful + When executing query: + """ + CREATE TAG INDEX only_point_geo_index ON only_point(geo); + """ + Then the execution should be successful + When executing query: + """ + CREATE TAG INDEX only_linestring_geo_index ON only_linestring(geo); + """ + Then the execution should be successful + When executing query: + """ + CREATE TAG INDEX only_polygon_geo_index ON only_polygon(geo); + """ + Then the execution should be successful + When executing query: + """ + CREATE EDGE INDEX any_shape_edge_geo_index ON any_shape_edge(geo); + """ + Then the execution should be successful + # Rebuild the geo index + When submit a job: + """ + REBUILD TAG INDEX any_shape_geo_index; + """ + Then wait the job to finish + When submit a job: + """ + REBUILD TAG INDEX only_point_geo_index; + """ + Then wait the job to finish + When submit a job: + """ + REBUILD TAG INDEX only_linestring_geo_index; + """ + Then wait the job to finish + When submit a job: + """ + REBUILD TAG INDEX only_polygon_geo_index; + """ + Then wait the job to finish + When submit a job: + """ + REBUILD EDGE INDEX any_shape_edge_geo_index; + """ + Then wait the job to finish + # Lookup on geo index + When profiling query: + """ + LOOKUP ON any_shape YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 2 | Project | 3 | | + | 3 | TagIndexFullScan | 0 | | + | 0 | Start | | | + When executing query: + """ + LOOKUP ON only_point YIELD ST_ASText(only_point.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_point.geo) | + | "201" | "POINT(3 8)" | + When executing query: + """ + LOOKUP ON only_linestring YIELD ST_ASText(only_linestring.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_linestring.geo) | + | "302" | "LINESTRING(3 8,4.7 73.23)" | + When executing query: + """ + LOOKUP ON only_polygon YIELD ST_ASText(only_polygon.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_polygon.geo) | + | "403" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + LOOKUP ON any_shape_edge YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | SrcVID | DstVID | Ranking | ST_ASText(any_shape_edge.geo) | + | "201" | "302" | 0 | "POLYGON((0 1,1 2,2 3,0 1))" | + # Match with geo index + When executing query: + """ + MATCH (v:any_shape) RETURN ST_ASText(v.geo); + """ + Then the result should be, in any order: + | ST_ASText(v.geo) | + | "POINT(3 8)" | + | "LINESTRING(3 8,4.7 73.23)" | + | "POLYGON((0 1,1 2,2 3,0 1))" | + # Insert geo data with index + When try to execute query: + """ + INSERT VERTEX any_shape(geo) VALUES "108":(ST_GeogFromText("POINT(72.3 84.6)")); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX only_point(geo) VALUES "208":(ST_GeogFromText("POINT(0.01 0.01)")); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX only_linestring(geo) VALUES "308":(ST_GeogFromText("LINESTRING(9 9, 8 8, 7 7, 9 9)")); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX only_polygon(geo) VALUES "408":(ST_GeogFromText("POLYGON((0 1, 1 2, 2 3, 0 1))")); + """ + Then the execution should be successful + When executing query: + """ + INSERT EDGE any_shape_edge(geo) VALUES "108"->"408":(ST_GeogFromText("POLYGON((-20 -20, -20 20, 20 20, 20 -20, -20 -20), (1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))")); + """ + Then the execution should be successful + # Lookup on geo index agagin + When executing query: + """ + LOOKUP ON any_shape YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON only_point YIELD ST_ASText(only_point.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_point.geo) | + | "201" | "POINT(3 8)" | + | "208" | "POINT(0.01 0.01)" | + When executing query: + """ + LOOKUP ON only_linestring YIELD ST_ASText(only_linestring.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_linestring.geo) | + | "302" | "LINESTRING(3 8,4.7 73.23)" | + | "308" | "LINESTRING(9 9,8 8,7 7,9 9)" | + When executing query: + """ + LOOKUP ON only_polygon YIELD ST_ASText(only_polygon.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(only_polygon.geo) | + | "403" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "408" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + LOOKUP ON any_shape_edge YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | SrcVID | DstVID | Ranking | ST_ASText(any_shape_edge.geo) | + | "108" | "408" | 0 | "POLYGON((-20 -20,-20 20,20 20,20 -20,-20 -20),(1 1,2 2,0 2,1 1))" | + | "201" | "302" | 0 | "POLYGON((0 1,1 2,2 3,0 1))" | + # Lookup and Yield geo functions + When executing query: + """ + LOOKUP ON any_shape YIELD S2_CellIdFromPoint(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | S2_CellIdFromPoint(any_shape.geo) | + | "101" | 1166542697063163289 | + | "102" | BAD_DATA | + | "103" | BAD_DATA | + | "108" | 4987215245349669805 | + When executing query: + """ + LOOKUP ON any_shape YIELD S2_CoveringCellIds(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | S2_CoveringCellIds(any_shape.geo) | + | "101" | [1166542697063163289] | + | "102" | [1167558203395801088, 1279022294173220864, 1315051091192184832, 1351079888211148800, 5039527983027585024, 5062045981164437504, 5174635971848699904, 5183643171103440896] | + | "103" | [1152391494368201343, 1153466862374223872, 1153554823304445952, 1153836298281156608, 1153959443583467520, 1154240918560178176, 1160503736791990272, 1160591697722212352] | + | "108" | [4987215245349669805] | + # Lookup with geo predicates which could be index accelerated + # ST_Intersects + When profiling query: + """ + LOOKUP ON any_shape WHERE ST_Intersects(any_shape.geo, ST_GeogFromText('POINT(3 8)')) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + And the execution plan should be: + | id | name | dependencies | operator info | + | 3 | Project | 4 | | + | 4 | IndexScan | 0 | | + | 0 | Start | | | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Intersects(any_shape.geo, ST_GeogFromText('POINT(0 1)')) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Intersects(any_shape.geo, ST_GeogFromText('POINT(4.7 73.23)')) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Intersects(any_shape.geo, ST_Point(72.3, 84.6)) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Intersects(ST_Point(72.3, 84.6), any_shape.geo) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Intersects(any_shape.geo, any_shape.geo) YIELD ST_ASText(any_shape.geo); + """ + Then a SemanticError should be raised at runtime: Expression ST_Intersects(any_shape.geo,any_shape.geo) not supported yet + # Match with geo predicate + When executing query: + """ + MATCH (v:any_shape) WHERE ST_Intersects(v.geo, ST_GeogFromText('POINT(3 8)')) RETURN ST_ASText(v.geo); + """ + Then the result should be, in any order: + | ST_ASText(v.geo) | + | "POINT(3 8)" | + | "LINESTRING(3 8,4.7 73.23)" | + # ST_Distance + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Distance(any_shape.geo, ST_Point(3, 8)) < 1.0 YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Distance(any_shape.geo, ST_Point(3, 8)) <= 1.0 YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Distance(any_shape.geo, ST_Point(3, 8)) <= 8909524.383934561 YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Distance(any_shape.geo, ST_Point(3, 8)) < 8909524.383934561 YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Distance(any_shape.geo, ST_Point(3, 8)) < 8909524.383934563 YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE 8909524.383934560 > ST_Distance(any_shape.geo, ST_Point(3, 8)) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + LOOKUP ON any_shape WHERE 8909524.3839345630 >= ST_Distance(any_shape.geo, ST_Point(3, 8)) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Distance(any_shape.geo, ST_Point(3, 8)) > 1.0 YIELD ST_ASText(any_shape.geo); + """ + Then a SemanticError should be raised at runtime: Expression (ST_Distance(any_shape.geo,ST_Point(3,8))>1) not supported yet + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Distance(any_shape.geo, ST_Point(3, 8)) != 1.0 YIELD ST_ASText(any_shape.geo); + """ + Then a SemanticError should be raised at runtime: Expression (ST_Distance(any_shape.geo,ST_Point(3,8))!=1) not supported yet + # ST_DWithin + When executing query: + """ + LOOKUP ON any_shape WHERE ST_DWithin(any_shape.geo, ST_Point(3, 8), 8909524.383934561) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_DWithin(any_shape.geo, ST_Point(3, 8), 100.0) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + # ST_Covers + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Covers(any_shape.geo, ST_Point(3, 8)) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Covers(any_shape.geo, ST_Point(3, 8)) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "POINT(3 8)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Covers(ST_GeogFromText('POLYGON((-0.7 3.8,3.6 3.2,1.8 -0.8,-3.4 2.4,-0.7 3.8))'), any_shape.geo) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_CoveredBy(any_shape.geo, ST_GeogFromText('POLYGON((-0.7 3.8,3.6 3.2,1.8 -0.8,-3.4 2.4,-0.7 3.8))')) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + # Update vertex with index + When executing query: + """ + UPDATE VERTEX ON any_shape "101" SET any_shape.geo = ST_GeogFromText('LINESTRING(3 8, 6 16)'); + """ + Then the execution should be successful + When executing query: + """ + FETCH PROP ON any_shape "101" YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "LINESTRING(3 8,6 16)" | + When executing query: + """ + LOOKUP ON any_shape YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "LINESTRING(3 8,6 16)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_DWithin(any_shape.geo, ST_Point(3, 8), 100.0) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "101" | "LINESTRING(3 8,6 16)" | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + # Update edge with index + When executing query: + """ + UPDATE EDGE ON any_shape_edge "201"->"302" SET any_shape_edge.geo = ST_GeogFromText('POINT(-1 -1)'); + """ + Then the execution should be successful + When executing query: + """ + FETCH PROP ON any_shape_edge "201"->"302" YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | any_shape_edge._src | any_shape_edge._dst | any_shape_edge._rank | ST_ASText(any_shape_edge.geo) | + | "201" | "302" | 0 | "POINT(-1 -1)" | + When executing query: + """ + LOOKUP ON any_shape_edge YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | SrcVID | DstVID | Ranking | ST_ASText(any_shape_edge.geo) | + | "108" | "408" | 0 | "POLYGON((-20 -20,-20 20,20 20,20 -20,-20 -20),(1 1,2 2,0 2,1 1))" | + | "201" | "302" | 0 | "POINT(-1 -1)" | + When executing query: + """ + LOOKUP ON any_shape_edge WHERE ST_Intersects(any_shape_edge.geo, ST_Point(-1, -1)) YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | SrcVID | DstVID | Ranking | ST_ASText(any_shape_edge.geo) | + | "108" | "408" | 0 | "POLYGON((-20 -20,-20 20,20 20,20 -20,-20 -20),(1 1,2 2,0 2,1 1))" | + | "201" | "302" | 0 | "POINT(-1 -1)" | + # Delete vertex with index + When executing query: + """ + DELETE VERTEX "101"; + """ + Then the execution should be successful + When executing query: + """ + FETCH PROP ON any_shape "101" YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + When executing query: + """ + LOOKUP ON any_shape YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + | "103" | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "POINT(72.3 84.6)" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Covers(any_shape.geo, ST_Point(3, 8)) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + | "102" | "LINESTRING(3 8,4.7 73.23)" | + # Delete edge with index + When executing query: + """ + DELETE EDGE any_shape_edge "201"->"302"; + """ + Then the execution should be successful + When executing query: + """ + FETCH PROP ON any_shape_edge "201"->"302" YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | any_shape_edge._src | any_shape_edge._dst | any_shape_edge._rank | ST_ASText(any_shape_edge.geo) | + When executing query: + """ + LOOKUP ON any_shape_edge YIELD ST_ASText(any_shape_edge.geo); + """ + Then the result should be, in any order: + | SrcVID | DstVID | Ranking | ST_ASText(any_shape_edge.geo) | + | "108" | "408" | 0 | "POLYGON((-20 -20,-20 20,20 20,20 -20,-20 -20),(1 1,2 2,0 2,1 1))" | + When executing query: + """ + LOOKUP ON any_shape WHERE ST_Intersects(ST_Point(-1, -1), any_shape.geo) YIELD ST_ASText(any_shape.geo); + """ + Then the result should be, in any order: + | VertexID | ST_ASText(any_shape.geo) | + # Drop tag index + When executing query: + """ + DROP TAG INDEX any_shape_geo_index; + """ + Then the execution should be successful + And wait 3 seconds + When executing query: + """ + LOOKUP ON any_shape; + """ + Then a ExecutionError should be raised at runtime: There is no index to use at runtime + # Drop edge index + When executing query: + """ + DROP EDGE INDEX any_shape_edge_geo_index; + """ + Then the execution should be successful + And wait 3 seconds + When executing query: + """ + LOOKUP ON any_shape_edge; + """ + Then a ExecutionError should be raised at runtime: There is no index to use at runtime + # Drop tag + When executing query: + """ + DROP TAG any_shape; + """ + Then the execution should be successful + # Drop edge + When executing query: + """ + DROP EDGE any_shape_edge; + """ + Then the execution should be successful diff --git a/tests/tck/features/lookup/EdgeIndexFullScan.feature b/tests/tck/features/lookup/EdgeIndexFullScan.feature index d07348979c3..d5aae560e51 100644 --- a/tests/tck/features/lookup/EdgeIndexFullScan.feature +++ b/tests/tck/features/lookup/EdgeIndexFullScan.feature @@ -26,11 +26,11 @@ Feature: Lookup edge index full scan And having executed: """ INSERT EDGE - edge_1(col1_str, col2_int) + edge_1(col1_str, col2_int) VALUES - '101'->'102':('Red1', 11), - '102'->'103':('Yellow', 22), - '103'->'101':('Blue', 33); + '101'->'102':('Red1', 11), + '102'->'103':('Yellow', 22), + '103'->'101':('Blue', 33); """ Scenario: Edge with relational RegExp filter @@ -147,10 +147,11 @@ Feature: Lookup edge index full scan | SrcVID | DstVID | Ranking | edge_1.col2_int | | "101" | "102" | 0 | 11 | And the execution plan should be: - | id | name | dependencies | operator info | - | 3 | Project | 4 | | - | 4 | EdgeIndexPrefixScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 3 | Project | 2 | | + | 2 | Filter | 4 | | + | 4 | EdgeIndexFullScan | 0 | | + | 0 | Start | | | Scenario: Edge with complex relational IN filter # (a IN b) AND (c IN d)