From 8aeedd99bc69dec16354845577c7b3fb08102fa8 Mon Sep 17 00:00:00 2001 From: jievince <38901892+jievince@users.noreply.github.com> Date: Thu, 30 Sep 2021 00:00:11 +0800 Subject: [PATCH] Geo spatial: 3. geography data and index --- src/clients/meta/MetaClient.cpp | 10 +- src/codec/RowReaderV2.cpp | 17 +- src/codec/RowWriterV2.cpp | 18 +- src/codec/RowWriterV2.h | 3 + src/codec/test/ResultSchemaProvider.cpp | 8 +- src/codec/test/ResultSchemaProvider.h | 5 +- src/codec/test/SchemaWriter.cpp | 6 +- src/codec/test/SchemaWriter.h | 3 +- src/common/datatypes/Geography.cpp | 32 +- src/common/datatypes/Geography.h | 4 + src/common/geo/CMakeLists.txt | 5 + src/common/geo/GeoIndex.cpp | 172 ++++++++++ src/common/geo/GeoIndex.h | 100 ++++++ src/common/meta/NebulaSchemaProvider.cpp | 6 +- src/common/meta/NebulaSchemaProvider.h | 12 +- src/common/meta/SchemaProviderIf.h | 4 + src/common/utils/IndexKeyUtils.h | 18 +- src/common/utils/test/CMakeLists.txt | 3 + src/daemons/CMakeLists.txt | 1 + src/graph/util/SchemaUtil.cpp | 10 +- src/graph/validator/MaintainValidator.cpp | 2 + src/kvstore/test/CMakeLists.txt | 1 + src/meta/CMakeLists.txt | 1 + src/parser/test/ParserTest.cpp | 21 ++ src/storage/index/LookupBaseProcessor-inl.h | 6 +- src/storage/test/CMakeLists.txt | 1 + src/tools/db-dump/CMakeLists.txt | 1 + src/tools/db-upgrade/CMakeLists.txt | 1 + src/tools/meta-dump/CMakeLists.txt | 1 + src/tools/simple-kv-verify/CMakeLists.txt | 1 + src/tools/storage-perf/CMakeLists.txt | 1 + tests/tck/features/geo/GeoBase.feature | 337 ++++++++++++++++++++ tests/tck/features/geo/GeoFunction.feature | 0 tests/tck/features/geo/GeoIndex.feature | 0 34 files changed, 785 insertions(+), 26 deletions(-) create mode 100644 src/common/geo/GeoIndex.cpp create mode 100644 src/common/geo/GeoIndex.h create mode 100644 tests/tck/features/geo/GeoBase.feature create mode 100644 tests/tck/features/geo/GeoFunction.feature create mode 100644 tests/tck/features/geo/GeoIndex.feature diff --git a/src/clients/meta/MetaClient.cpp b/src/clients/meta/MetaClient.cpp index a3e46fc9c0d..bfcb53c5f27 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..1c326cab2ef 100644 --- a/src/codec/RowReaderV2.cpp +++ b/src/codec/RowReaderV2.cpp @@ -176,8 +176,21 @@ 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 Geography(); + } + 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 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..67d7cb430d5 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() {} @@ -91,6 +96,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: diff --git a/src/common/datatypes/Geography.h b/src/common/datatypes/Geography.h index 5f2ecd31790..54cf9c7deb6 100644 --- a/src/common/datatypes/Geography.h +++ b/src/common/datatypes/Geography.h @@ -135,6 +135,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 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/GeoIndex.cpp b/src/common/geo/GeoIndex.cpp new file mode 100644 index 00000000000..83a7c886779 --- /dev/null +++ b/src/common/geo/GeoIndex.cpp @@ -0,0 +1,172 @@ +/* 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; + // set_column_name should be called later + if (isRangeScan) { + hint.set_scan_type(nebula::storage::cpp2::ScanType::RANGE); + hint.set_begin_value( + IndexKeyUtils::encodeUint64(rangeMin)); // Encode uint64_t as string ahead of time + 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 cells = coveringCells(g); + std::vector cellIds; + cellIds.reserve(cells.size()); + for (auto& cell : cells) { + cellIds.push_back(cell.id()); + } + return cellIds; +} + +std::vector GeoIndex::dWithin(const Geography& g, double distance) const noexcept { + auto r = g.asS2(); + if (!r) { + return {}; + } + + S1Angle radius = S2Earth::ToAngle(util::units::Meters(distance)); + switch (g.shape()) { + case GeoShape::POINT: { + const S2Point& gPoint = static_cast(r.get())->point(); + S2Cap gCap(gPoint, radius); + auto cells = coveringCells(gCap); + return scanRange(cells); + } + case GeoShape::LINESTRING: { + S2Polyline* gLine = static_cast(r.get()); + MutableS2ShapeIndex index; + index.Add(std::make_unique(gLine)); + S2ShapeIndexBufferedRegion gBuffer(&index, radius); + auto cells = coveringCells(gBuffer); + return scanRange(cells); + } + case GeoShape::POLYGON: { + S2Polygon* gPolygon = static_cast(r.get()); + S2ShapeIndexBufferedRegion gBuffer(&gPolygon->index(), radius); + auto cells = coveringCells(gBuffer); + return scanRange(cells); + } + default: + LOG(FATAL) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return {}; + } +} + +std::vector GeoIndex::coveringCells(const Geography& g) const noexcept { + auto r = g.asS2(); + // TODO(jie): Better to ensure the Geography value is valid to build s2 when constructing. + if (!r) { + return {}; + } + + // Currently region coverer options doesn't work for point. Point always use level 30. + if (g.shape() == GeoShape::POINT) { + const S2Point& gPoint = static_cast(r.get())->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::coveringCells(const S2Region& r) const noexcept { + S2RegionCoverer rc(rcParams_.s2RegionCovererOpts()); + std::vector covering; + rc.GetCovering(r, &covering); + 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 sort it? + // May need to call S2RegionCoverer::CanonicalizeCovering(&ancestors)? + return ancestors; +} + +std::vector GeoIndex::scanRange(const std::vector& cells) const noexcept { + 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()); + } + } + + if (!pointsOnly_) { + auto ancestors = ancestorCells(cells); + for (const S2CellId& cellId : ancestors) { + scanRanges.emplace_back(cellId.id()); + } + } + + return scanRanges; +} + +} // 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..21edf475ee7 --- /dev/null +++ b/src/common/geo/GeoIndex.h @@ -0,0 +1,100 @@ +/* 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 { + int minCellLevel_ = 4; + int maxCellLevel_ = 23; // 30? + 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 min) : rangeMin(min), isRangeScan(false) {} + + nebula::storage::cpp2::IndexColumnHint toIndexColumnHint(); +}; + +class GeoIndex { + public: + explicit GeoIndex(const RegionCoverParams& params, bool pointsOnly = false) + : rcParams_(params), pointsOnly_(pointsOnly) {} + + // build index + std::vector indexCells(const Geography& g) const noexcept; + + // query index + std::vector covers(const Geography& g) const noexcept; + + std::vector coveredBy(const Geography& g) const noexcept; + + std::vector intersects(const Geography& g) const noexcept; + + std::vector dWithin(const Geography& g, double distance) const noexcept; + + private: + std::vector coveringCells(const Geography& g) const noexcept; + std::vector coveringCells(const S2Region& r) const noexcept; + + std::vector ancestorCells(const std::vector& cells) const noexcept; + + std::vector scanRange(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/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.h b/src/common/utils/IndexKeyUtils.h index 23d699abce8..aaf2fe1cfd1 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,8 +139,7 @@ class IndexKeyUtils final { return encodeDateTime(v.getDateTime()); } case Value::Type::GEOGRAPHY: { - // TODO(jie) - return ""; + return encodeGeography(v.getGeography()); } default: LOG(ERROR) << "Unsupported default value type"; @@ -299,6 +299,20 @@ class IndexKeyUtils final { return buf; } + static std::string encodeGeography(const nebula::Geography& gg) { + // get params from index meta + geo::RegionCoverParams + rc; // TODO(jie): Get index params to build 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[0]; // 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)); 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..05aef32acd0 100644 --- a/src/daemons/CMakeLists.txt +++ b/src/daemons/CMakeLists.txt @@ -45,6 +45,7 @@ set(storage_meta_deps $ $ $ + $ ) nebula_add_executable( 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/MaintainValidator.cpp b/src/graph/validator/MaintainValidator.cpp index ca04be664ee..345b31d1039 100644 --- a/src/graph/validator/MaintainValidator.cpp +++ b/src/graph/validator/MaintainValidator.cpp @@ -37,6 +37,8 @@ Status SchemaValidator::validateColumns(const std::vector 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/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/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/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..4018359e8b0 --- /dev/null +++ b/tests/tck/features/geo/GeoBase.feature @@ -0,0 +1,337 @@ +# 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: desc and show create 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: insert geo data and query them + # 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 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))" | + 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))" | + # 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) | + | "201" | "302" | 0 | "POLYGON((0 1,1 2,2 3,0 1))" | + | "108" | "408" | 0 | "POLYGON((-20 -20,-20 20,20 20,20 -20,-20 -20),(1 1,2 2,0 2,1 1))" | diff --git a/tests/tck/features/geo/GeoFunction.feature b/tests/tck/features/geo/GeoFunction.feature new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/tck/features/geo/GeoIndex.feature b/tests/tck/features/geo/GeoIndex.feature new file mode 100644 index 00000000000..e69de29bb2d