From 54c04ed7c632e552399b4f6df03820f45a47791c Mon Sep 17 00:00:00 2001 From: "yihe.zxl" Date: Mon, 13 Jul 2026 15:36:41 +0800 Subject: [PATCH 1/4] feat: add namespace-aware schema views --- include/neug/compiler/catalog/catalog.h | 8 +- include/neug/compiler/gopt/g_catalog.h | 1 + include/neug/compiler/main/metadata_manager.h | 4 + .../neug/storages/graph/operation_params.h | 16 +- include/neug/storages/graph/schema.h | 11 +- include/neug/storages/graph/schema_view.h | 73 +++++ specs/schema_view.md | 229 ++++++++++++++ src/compiler/catalog/catalog.cpp | 108 +++---- src/compiler/gopt/g_catalog.cpp | 5 + src/compiler/main/metadata_manager.cpp | 12 +- src/compiler/planner/gopt_planner.cc | 4 +- src/storages/graph/operation_params.cc | 8 + src/storages/graph/property_graph.cc | 13 +- src/storages/graph/schema.cc | 42 ++- src/storages/graph/schema_view.cc | 202 ++++++++++++ tests/storage/CMakeLists.txt | 2 + tests/storage/test_schema_view.cc | 297 ++++++++++++++++++ 17 files changed, 952 insertions(+), 83 deletions(-) create mode 100644 include/neug/storages/graph/schema_view.h create mode 100644 specs/schema_view.md create mode 100644 src/storages/graph/schema_view.cc create mode 100644 tests/storage/test_schema_view.cc diff --git a/include/neug/compiler/catalog/catalog.h b/include/neug/compiler/catalog/catalog.h index 33f8613fd..5e1a92a7f 100644 --- a/include/neug/compiler/catalog/catalog.h +++ b/include/neug/compiler/catalog/catalog.h @@ -23,12 +23,13 @@ #pragma once #include +#include #include "neug/compiler/catalog/catalog_entry/function_catalog_entry.h" #include "neug/compiler/catalog/catalog_set.h" #include "neug/compiler/common/cast.h" #include "neug/compiler/function/function.h" -#include "neug/storages/graph/schema.h" +#include "neug/storages/graph/schema_view.h" namespace neug::main { struct DBConfig; @@ -167,14 +168,15 @@ class NEUG_API Catalog { } virtual std::unique_ptr clone(const Schema* schema) const; + virtual std::unique_ptr clone(const SchemaView* schema) const; private: void initCatalogSets(); protected: - void setSchema(const Schema* schema); + void setSchema(const SchemaView* schema); - const Schema* schema; + std::optional schema; private: std::shared_ptr sequences; diff --git a/include/neug/compiler/gopt/g_catalog.h b/include/neug/compiler/gopt/g_catalog.h index ed08f44c3..809340be6 100644 --- a/include/neug/compiler/gopt/g_catalog.h +++ b/include/neug/compiler/gopt/g_catalog.h @@ -36,6 +36,7 @@ class GCatalog : public Catalog { const std::string& signatureName); std::unique_ptr clone(const Schema* schema) const override; + std::unique_ptr clone(const SchemaView* schema) const override; private: void registerBuiltInFunctions(); diff --git a/include/neug/compiler/main/metadata_manager.h b/include/neug/compiler/main/metadata_manager.h index 5ab742b12..ae0054339 100644 --- a/include/neug/compiler/main/metadata_manager.h +++ b/include/neug/compiler/main/metadata_manager.h @@ -45,6 +45,7 @@ class CatalogEntry; class GraphStats; class Schema; +class SchemaView; namespace function { struct Function; @@ -92,6 +93,9 @@ class MetadataManager { std::unique_ptr clone(const Schema* schema, const GraphStats& stats) const; + std::unique_ptr clone(const SchemaView* schema, + const GraphStats& stats) const; + std::shared_ptr getGraphStats() const; graph::GraphEntrySet& getGraphEntrySetUnsafe(); diff --git a/include/neug/storages/graph/operation_params.h b/include/neug/storages/graph/operation_params.h index 44ffebd2f..60e433c96 100644 --- a/include/neug/storages/graph/operation_params.h +++ b/include/neug/storages/graph/operation_params.h @@ -31,6 +31,7 @@ class CreateVertexTypeParam { std::vector> properties; std::vector primary_key_names; bool temporary = false; + std::string namespace_name = "default"; CreateVertexTypeParam() = default; friend class CreateVertexTypeParamBuilder; @@ -43,6 +44,7 @@ class CreateVertexTypeParam { return primary_key_names; } bool IsTemporary() const { return temporary; } + const std::string& GetNamespace() const { return namespace_name; } void Serialize(InArchive& arc) const; static CreateVertexTypeParam Deserialize(OutArchive& arc); @@ -87,6 +89,11 @@ class CreateVertexTypeParamBuilder { return *this; } + CreateVertexTypeParamBuilder& Namespace(const std::string& namespace_name) { + config.namespace_name = namespace_name; + return *this; + } + CreateVertexTypeParam Build() { if (config.vertex_label_name.empty()) { LOG(ERROR) << "Vertex label cannot be empty."; @@ -110,6 +117,7 @@ class CreateEdgeTypeParam { EdgeStrategy ie_edge_strategy; std::optional sort_key_for_nbr; bool temporary = false; + std::string namespace_name = "default"; CreateEdgeTypeParam() = default; friend class CreateEdgeTypeParamBuilder; @@ -126,6 +134,7 @@ class CreateEdgeTypeParam { return sort_key_for_nbr; } bool IsTemporary() const { return temporary; } + const std::string& GetNamespace() const { return namespace_name; } void Serialize(InArchive& arc) const; static CreateEdgeTypeParam Deserialize(OutArchive& arc); @@ -187,6 +196,11 @@ class CreateEdgeTypeParamBuilder { return *this; } + CreateEdgeTypeParamBuilder& Namespace(const std::string& namespace_name) { + config.namespace_name = namespace_name; + return *this; + } + CreateEdgeTypeParam Build() { if (config.src_label_name.empty()) { LOG(ERROR) << "Source label must be specified."; @@ -547,4 +561,4 @@ class DeleteEdgePropertiesParamBuilder { } }; -} // namespace neug \ No newline at end of file +} // namespace neug diff --git a/include/neug/storages/graph/schema.h b/include/neug/storages/graph/schema.h index d5e1cf092..9fc021c87 100644 --- a/include/neug/storages/graph/schema.h +++ b/include/neug/storages/graph/schema.h @@ -247,6 +247,9 @@ struct VertexSchema : public SchemaEntry { std::string description; size_t max_num; + // Logical namespace this vertex type belongs to. + std::string namespace_name = "default"; + // Mark whether the vertex property is soft deleted std::vector vprop_soft_deleted; @@ -409,6 +412,9 @@ struct EdgeSchema : public SchemaEntry { std::vector property_names; std::vector default_property_values; + // Logical namespace this edge type belongs to. + std::string namespace_name = "default"; + // Mark whether the edge property is soft deleted std::vector eprop_soft_deleted; @@ -556,7 +562,7 @@ class Schema { size_t max_vnum = static_cast(1) << 32, const std::string& description = "", const std::vector& default_property_values = {}, - bool temporary = false); + bool temporary = false, const std::string& namespace_name = "default"); void AddEdgeLabel(const std::string& src_label, const std::string& dst_label, const std::string& edge_label, @@ -568,7 +574,8 @@ class Schema { std::optional sort_key_for_nbr = std::nullopt, const std::string& description = "", const std::vector& default_property_values = {}, - bool temporary = false); + bool temporary = false, + const std::string& namespace_name = "default"); bool is_vertex_label_temporary(label_t label) const; bool is_edge_label_temporary(uint32_t edge_triplet_key) const; diff --git a/include/neug/storages/graph/schema_view.h b/include/neug/storages/graph/schema_view.h new file mode 100644 index 000000000..efc45d0af --- /dev/null +++ b/include/neug/storages/graph/schema_view.h @@ -0,0 +1,73 @@ +/** Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include "neug/storages/graph/schema.h" + +namespace neug { + +class SchemaView { + public: + SchemaView(const Schema* schema, std::string namespace_name); + SchemaView(const Schema& schema, std::string namespace_name); + + const Schema& GetSchema() const; + const std::string& GetNamespace() const { return namespace_; } + + std::vector> GetVertexSchemas() const; + std::vector> GetEdgeSchemas() const; + + result> GetVertexSchema( + label_t label) const; + result> GetVertexSchema( + const std::string& label) const; + + result> GetEdgeSchema( + label_t src_label, label_t dst_label, label_t edge_label) const; + result> GetEdgeSchema( + const std::string& src_label, const std::string& dst_label, + const std::string& edge_label) const; + + bool ContainsVertexLabel(label_t label) const; + bool ContainsVertexLabel(const std::string& label) const; + + bool ContainsEdgeLabel(label_t label) const; + bool ContainsEdgeLabel(const std::string& label) const; + + bool ContainsEdgeTriplet(label_t src_label, label_t dst_label, + label_t edge_label) const; + bool ContainsEdgeTriplet(const std::string& src_label, + const std::string& dst_label, + const std::string& edge_label) const; + + std::vector GetVertexLabelIds() const; + std::vector GetEdgeLabelIds() const; + + private: + void EnsureSchema() const; + + bool IsVertexInNamespace(label_t label) const; + bool IsVertexInNamespace(const VertexSchema& schema) const; + bool IsEdgeInNamespace(const EdgeSchema& schema) const; + + const Schema* schema_; + std::string namespace_; +}; + +} // namespace neug diff --git a/specs/schema_view.md b/specs/schema_view.md new file mode 100644 index 000000000..91313da98 --- /dev/null +++ b/specs/schema_view.md @@ -0,0 +1,229 @@ +# SchemaView 设计 + +## 什么是 SchemaView? + +`SchemaView` 提供了一层 Schema 逻辑视图,用于在同一个底层 Schema 存储中隔离不同逻辑 Schema 下的点边类型。 + +底层 Schema 负责统一管理所有点边类型,而 `SchemaView` 基于 namespace 对底层 Schema 进行过滤和访问控制,使不同逻辑 Schema 之间的点边类型相互隔离。 + +简单来说: + +- **Schema:** 管理所有实际存在的点边类型; +- **SchemaView:** 提供某个逻辑 Schema namespace 下的访问视图,只暴露属于该 namespace 的点边类型。 + +--- + +## 为什么需要 SchemaView? + +引入 `SchemaView` 的主要目的是在不改变现有 Schema 管理模型的情况下,逐步支持 NeuG 中不同场景下的 Schema 隔离需求。 + +目前 SchemaView 主要服务于以下两个场景: + +### 1. 支持内部功能创建隐藏点边类型 + +一些 NeuG 内部功能需要在数据库中创建辅助点边表,但这些点边类型不应该暴露给普通用户。 + +例如全文索引: + +- 全文索引可能需要在内部创建额外的点表、边表,用于存储索引相关的数据; +- 这些内部表需要复用 NeuG 的 Schema 和存储能力; +- 但用户不应该在正常图查询中看到或访问这些内部点边类型。 + +通过为内部功能分配独立的 namespace,并通过对应的 `SchemaView` 访问,可以实现: + +- 内部功能可以正常创建和访问自己的点边类型; +- 用户侧 SchemaView 不包含这些内部点边类型; +- 不同功能模块之间的 Schema 互相隔离。 + +--- + +### 2. 支持用户侧多图(Multi-Graph)功能 + +在内部功能场景验证 SchemaView 后,可以进一步开放给用户,用于支持多图能力。 + +用户可以显式创建多个 Schema,并在指定 Schema 下创建点边类型。不同 Schema 之间的点边类型相互隔离,用户可以基于指定 Schema 执行图查询。 + +例如: + +```cypher +CREATE SCHEMA s1; +CREATE SCHEMA s2; + +CREATE NODE TABLE s1.Person ( + id INT64, + name STRING, + PRIMARY KEY(id) +); + +CREATE NODE TABLE s2.Book ( + id INT64, + title STRING, + PRIMARY KEY(id) +); + +MATCH (n: s1.Person) +WHERE n.name = 'XX' +RETURN count(n); + +MATCH(n: s2.Book) +WHERE n.title <> 'XX' +RETURN count(n); +``` + +## SchemaView 设计 + +当前方案倾向于在 Schema 层之上提供一层 `SchemaView` 抽象,通过 namespace 对 Schema 进行逻辑隔离。`SchemaView` 表示某个特定 namespace 下的 Schema 视图,所有 Schema 查询操作均通过该 View 完成。通过在 View 层过滤非当前 namespace 的点边类型,实现不同 namespace 之间的 Schema 隔离。 + + +`SchemaView` 内部维护: + +- 底层 `Schema` 对象的只读引用; +- 当前 View 所属的 namespace。 + +底层 `Schema` 仍然统一存储所有 namespace 下的点边类型,并按照 label 提供统一的 Schema 管理能力。namespace 隔离逻辑由 `SchemaView` 负责,而不是由底层 `Schema` 实现。 + + +当前设计**暂不支持不同 namespace 使用相同的 label name**: + +- 主要原因是底层 Schema 当前仍然基于 label name 作为点边类型的唯一标识进行存储和管理。因此,不同 namespace 下的 label name 需要由用户保证全局唯一。 + +- 如果未来需要支持 namespace 内 label 重名,需要调整底层 Schema 存储结构,将 `(namespace, label)` 作为点边类型的唯一标识。 + +## 实现 + +### 接口设计 + +`SchemaView` 对外提供与 `Schema` 类似的查询接口,但所有查询结果均限定在当前 namespace 范围内,例如: + +- `GetVertexSchemas` / `GetEdgeSchemas`: 返回当前 namespace 下的点边 Schema; +- `GetVertexSchema` / `GetEdgeSchema`: 仅返回属于当前 namespace 的指定 label Schema; +- `ContainsXXX`: 判断指定 label 是否属于当前 namespace。 + +所有 Get 类接口内部通过 `EnsureSchema` 或 namespace 校验逻辑,保证不会返回其他 namespace 下的 Schema。 + +```c++ + class SchemaView { + public: + SchemaView(const Schema* schema, std::string namespace_name); + SchemaView(const Schema& schema, std::string namespace_name); + + const Schema& GetSchema() const; + const std::string& GetNamespace() const; + + // Get 接口内部调用 EnsureSchema 保证只返回当前 namespace 内的点边 + std::vector> GetVertexSchemas() const; + std::vector> GetEdgeSchemas() const; + + result> GetVertexSchema(label_t label) const; + result> GetVertexSchema( + const std::string& label) const; + + result> GetEdgeSchema( + label_t src_label, label_t dst_label, label_t edge_label) const; + result> GetEdgeSchema( + const std::string& src_label, + const std::string& dst_label, + const std::string& edge_label) const; + + // 内部调用 IsVertexInNamespace,确保当前 label 在 namespace 内 + bool ContainsVertexLabel(label_t label) const; + bool ContainsVertexLabel(const std::string& label) const; + + bool ContainsEdgeLabel(label_t label) const; + bool ContainsEdgeLabel(const std::string& label) const; + + bool ContainsEdgeTriplet( + label_t src_label, label_t dst_label, label_t edge_label) const; + bool ContainsEdgeTriplet( + const std::string& src_label, + const std::string& dst_label, + const std::string& edge_label) const; + + std::vector GetVertexLabelIds() const; + std::vector GetEdgeLabelIds() const; + + private: + void EnsureSchema() const; + + bool IsVertexInNamespace(label_t label) const; + bool IsVertexInNamespace(const VertexSchema& schema) const; + bool IsEdgeInNamespace(const EdgeSchema& schema) const; + + const Schema* schema_; + std::string namespace_; + }; +``` + +### 其他接口改动 + +我们进一步修改 Schema `AddVertexLabel/AddEdgeLabel` 接口: +- 用于在创建点边类型时指定所在 namespace; +- 不显示指定 namespace 默认为 `"default"`,代表默认 namespace 类型; + +```c++ +class Schema { +public: + void AddVertexLabel( + const std::string& label, const std::vector& property_types, + const std::vector& property_names, + const std::vector>& primary_key, + size_t max_vnum = static_cast(1) << 32, + const std::string& description = "", + const std::vector& default_property_values = {}, + bool temporary = false, + string namespace = "default"); + + void AddEdgeLabel( + const std::string& src_label, const std::string& dst_label, + const std::string& edge_label, + const std::vector& properties, + const std::vector& prop_names, + EdgeStrategy oe = EdgeStrategy::kMultiple, + EdgeStrategy ie = EdgeStrategy::kMultiple, + bool oe_mutable = true, bool ie_mutable = true, + std::optional sort_key_for_nbr = std::nullopt, + const std::string& description = "", + const std::vector& default_property_values = {}, + bool temporary = false, + string namespace = "default"); +}; +``` + + +`GraphInterface` 也需要透传 namespace 参数: +- 我们直接在 `CreateVertexTypeParam/CreateEdgeTypeParam` 增加 namespace 参数; +- 在全文索引调用 GraphInterface 创建点边类型时,需要显示设置 `namespace = fts`; + +```c++ +class StorageUpdateInterface{ +public: + virtual Status CreateVertexType(const CreateVertexTypeParam& config) = 0; + virtual Status CreateEdgeType(const CreateEdgeTypeParam& config) = 0; +} +``` + +```c++ +class CreateVertexTypeParam { + private: + std::string vertex_label_name; + std::vector> properties; + std::vector primary_key_names; + bool temporary = false; + std::string namespace = "default"; +}; +``` + +```c++ +class CreateEdgeTypeParam { + private: + std::string src_label_name; + std::string dst_label_name; + std::string edge_label_name; + std::vector> properties; + EdgeStrategy oe_edge_strategy; + EdgeStrategy ie_edge_strategy; + std::optional sort_key_for_nbr; + bool temporary = false; + std::string namespace = "default"; +}; +``` diff --git a/src/compiler/catalog/catalog.cpp b/src/compiler/catalog/catalog.cpp index 0ddf0618a..38514565e 100644 --- a/src/compiler/catalog/catalog.cpp +++ b/src/compiler/catalog/catalog.cpp @@ -58,19 +58,28 @@ bool nameEquals(std::string_view lhs, std::string_view rhs) { } } // namespace -Catalog::Catalog() : schema{nullptr}, version{0} { initCatalogSets(); } +Catalog::Catalog() : schema{std::nullopt}, version{0} { initCatalogSets(); } Catalog::Catalog(const std::string& directory, VirtualFileSystem* vfs) - : schema{nullptr}, version{0} {} + : schema{std::nullopt}, version{0} {} std::unique_ptr Catalog::clone(const Schema* schema) const { + SchemaView schema_view(schema, "default"); + return clone(&schema_view); +} + +std::unique_ptr Catalog::clone(const SchemaView* schema) const { auto cloned = std::make_unique(*this); cloned->setSchema(schema); return cloned; } -void Catalog::setSchema(const Schema* schema) { - this->schema = schema; +void Catalog::setSchema(const SchemaView* schema) { + if (schema == nullptr) { + this->schema.reset(); + } else { + this->schema = *schema; + } incrementVersion(); } @@ -87,21 +96,15 @@ void Catalog::initCatalogSets() { bool Catalog::containsTable(const Transaction* transaction, const std::string& tableName, bool useInternal) const { - if (schema == nullptr) { + if (!schema) { return false; } - for (auto& entry : schema->get_all_vertex_schemas()) { - if (entry != nullptr && - schema->is_vertex_label_valid(entry->get_entry_id()) && - nameEquals(entry->label_name, tableName)) { + for (const auto& entry : schema->GetVertexSchemas()) { + if (nameEquals(entry->label_name, tableName)) { return true; } } - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (nameEquals(edgeSchema->edge_label_name, tableName) || nameEquals(getChildRelTableName(*edgeSchema), tableName)) { return true; @@ -112,14 +115,14 @@ bool Catalog::containsTable(const Transaction* transaction, bool Catalog::containsTable(const Transaction* transaction, table_id_t tableID, bool useInternal) const { - if (schema == nullptr) { + if (!schema) { return false; } if (tableID <= std::numeric_limits::max() && - schema->is_vertex_label_valid(static_cast(tableID))) { + schema->ContainsVertexLabel(static_cast(tableID))) { return true; } - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (edgeSchema->get_entry_id() == tableID || edgeSchema->getLabelId() == tableID) { return true; @@ -130,13 +133,13 @@ bool Catalog::containsTable(const Transaction* transaction, table_id_t tableID, const SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, table_id_t tableID) const { - if (schema != nullptr && tableID <= std::numeric_limits::max() && - schema->is_vertex_label_valid(static_cast(tableID))) { - return schema->get_vertex_schema(static_cast(tableID)).get(); + if (schema && tableID <= std::numeric_limits::max() && + schema->ContainsVertexLabel(static_cast(tableID))) { + return schema->GetVertexSchema(static_cast(tableID)).value().get(); } - if (schema != nullptr) { + if (schema) { const EdgeSchema* labelMatch = nullptr; - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (edgeSchema->get_entry_id() == tableID) { return edgeSchema.get(); } @@ -160,12 +163,10 @@ const SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, const std::string& tableName, bool useInternal) const { - if (schema != nullptr) { - VertexSchema* vertexResult = nullptr; - for (auto& entry : schema->get_all_vertex_schemas()) { - if (entry == nullptr || - !schema->is_vertex_label_valid(entry->get_entry_id()) || - !nameEquals(entry->label_name, tableName)) { + if (schema) { + const VertexSchema* vertexResult = nullptr; + for (const auto& entry : schema->GetVertexSchemas()) { + if (!nameEquals(entry->label_name, tableName)) { continue; } if (vertexResult != nullptr) { @@ -175,16 +176,12 @@ SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, vertexResult = entry.get(); } if (vertexResult != nullptr) { - return vertexResult; + return const_cast(vertexResult); } } - EdgeSchema* result = nullptr; - if (schema != nullptr) { - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + const EdgeSchema* result = nullptr; + if (schema) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (!nameEquals(edgeSchema->edge_label_name, tableName) && !nameEquals(getChildRelTableName(*edgeSchema), tableName)) { continue; @@ -200,20 +197,17 @@ SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, THROW_SCHEMA_MISMATCH( stringFormat("{} does not exist in catalog.", tableName)); } - return result; + return const_cast(result); } std::vector Catalog::getNodeTableEntries( const Transaction* transaction, bool useInternal) const { std::vector result; - if (schema == nullptr) { + if (!schema) { return result; } - for (auto& entry : schema->get_all_vertex_schemas()) { - if (entry != nullptr && - schema->is_vertex_label_valid(entry->get_entry_id())) { - result.push_back(entry.get()); - } + for (const auto& entry : schema->GetVertexSchemas()) { + result.push_back(const_cast(entry.get())); } return result; } @@ -221,15 +215,11 @@ std::vector Catalog::getNodeTableEntries( std::vector Catalog::getRelTableEntries( const Transaction* transaction, bool useInternal) const { std::vector result; - if (schema == nullptr) { + if (!schema) { return result; } - for (auto& [_, entry] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(entry->getSrcTableID()) || - !schema->is_vertex_label_valid(entry->getDstTableID())) { - continue; - } - result.push_back(entry.get()); + for (const auto& entry : schema->GetEdgeSchemas()) { + result.push_back(const_cast(entry.get())); } std::sort(result.begin(), result.end(), [](const auto* lhs, const auto* rhs) { return std::tie(lhs->edge_label_id, lhs->src_label_id, lhs->dst_label_id, @@ -254,15 +244,11 @@ std::vector Catalog::getTableEntries( bool Catalog::containsRelGroup(const Transaction* transaction, const std::string& name) const { - if (schema == nullptr) { + if (!schema) { return false; } common::idx_t count = 0; - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (nameEquals(edgeSchema->edge_label_name, name)) { ++count; } @@ -273,14 +259,10 @@ bool Catalog::containsRelGroup(const Transaction* transaction, std::vector Catalog::getRelGroupEntry( const Transaction* transaction, const std::string& name) const { std::vector result; - if (schema != nullptr) { - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + if (schema) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (nameEquals(edgeSchema->edge_label_name, name)) { - result.push_back(edgeSchema.get()); + result.push_back(const_cast(edgeSchema.get())); } } } diff --git a/src/compiler/gopt/g_catalog.cpp b/src/compiler/gopt/g_catalog.cpp index 9fff8961d..bfda59fc1 100644 --- a/src/compiler/gopt/g_catalog.cpp +++ b/src/compiler/gopt/g_catalog.cpp @@ -29,6 +29,11 @@ namespace catalog { GCatalog::GCatalog() : Catalog() { registerBuiltInFunctions(); } std::unique_ptr GCatalog::clone(const Schema* schema) const { + SchemaView schema_view(schema, "default"); + return clone(&schema_view); +} + +std::unique_ptr GCatalog::clone(const SchemaView* schema) const { auto cloned = std::make_unique(*this); cloned->setSchema(schema); return cloned; diff --git a/src/compiler/main/metadata_manager.cpp b/src/compiler/main/metadata_manager.cpp index facc0c3f3..9ff0651d8 100644 --- a/src/compiler/main/metadata_manager.cpp +++ b/src/compiler/main/metadata_manager.cpp @@ -68,7 +68,7 @@ MetadataManager::MetadataManager( graphEntrySet{std::move(graphEntrySet)} {} std::unique_ptr MetadataManager::clone( - const Schema* schema, const GraphStats& stats) const { + const SchemaView* schema, const GraphStats& stats) const { if (!catalog) { THROW_CATALOG_EXCEPTION("Catalog is not set"); } @@ -85,6 +85,16 @@ const graph::GraphEntrySet& MetadataManager::getGraphEntrySet() const { return *graphEntrySet; } +std::unique_ptr MetadataManager::clone( + const Schema* schema, const GraphStats& stats) const { + if (!catalog) { + THROW_CATALOG_EXCEPTION("Catalog is not set"); + } + return std::unique_ptr( + new MetadataManager(catalog->clone(schema), stats, memoryManager, vfs, + extensionManager, graphEntrySet)); +} + std::shared_ptr MetadataManager::getGraphStats() const { return std::make_shared(statsManager); } diff --git a/src/compiler/planner/gopt_planner.cc b/src/compiler/planner/gopt_planner.cc index 968ad976e..5b9dd40b3 100644 --- a/src/compiler/planner/gopt_planner.cc +++ b/src/compiler/planner/gopt_planner.cc @@ -20,6 +20,7 @@ limitations under the License. #include "neug/compiler/gopt/g_catalog.h" #include "neug/compiler/gopt/g_physical_convertor.h" #include "neug/compiler/gopt/g_result_schema.h" +#include "neug/storages/graph/schema_view.h" #include "neug/utils/exception/exception.h" namespace neug { @@ -33,7 +34,8 @@ result> GOptPlanner::compilePlan( RETURN_ERROR(Status(StatusCode::ERR_INVALID_SCHEMA, "Schema is null")); } - auto queryDatabase = database->clone(schema, stats); + SchemaView schema_view(schema, "default"); + auto queryDatabase = database->clone(&schema_view, stats); main::ClientContext queryContext(queryDatabase.get()); if (queryDatabase->getCatalog() == nullptr) { diff --git a/src/storages/graph/operation_params.cc b/src/storages/graph/operation_params.cc index 13d4e32bd..7a065f043 100644 --- a/src/storages/graph/operation_params.cc +++ b/src/storages/graph/operation_params.cc @@ -32,6 +32,7 @@ void CreateVertexTypeParam::Serialize(InArchive& arc) const { for (const auto& key : primary_key_names) { arc << key; } + arc << namespace_name; } CreateVertexTypeParam CreateVertexTypeParam::Deserialize(OutArchive& arc) { @@ -55,6 +56,9 @@ CreateVertexTypeParam CreateVertexTypeParam::Deserialize(OutArchive& arc) { arc >> key; builder.AddPrimaryKeyName(key); } + std::string namespace_name; + arc >> namespace_name; + builder.Namespace(namespace_name); return builder.Build(); } @@ -70,6 +74,7 @@ void CreateEdgeTypeParam::Serialize(InArchive& arc) const { } else { arc << static_cast(0); } + arc << namespace_name; } CreateEdgeTypeParam CreateEdgeTypeParam::Deserialize(OutArchive& arc) { @@ -98,6 +103,9 @@ CreateEdgeTypeParam CreateEdgeTypeParam::Deserialize(OutArchive& arc) { arc >> sort_key; builder.SortKeyForNbr(sort_key); } + std::string namespace_name; + arc >> namespace_name; + builder.Namespace(namespace_name); return builder.Build(); } diff --git a/src/storages/graph/property_graph.cc b/src/storages/graph/property_graph.cc index 090bf2a5e..3f9c693b1 100644 --- a/src/storages/graph/property_graph.cc +++ b/src/storages/graph/property_graph.cc @@ -218,7 +218,8 @@ Status PropertyGraph::CreateVertexType(const CreateVertexTypeParam& config) { const auto& vertex_type_name = config.GetVertexLabel(); schema_.AddVertexLabel(vertex_type_name, property_types, property_names, primary_keys, Schema::MAX_VNUM, description, - default_property_values, config.IsTemporary()); + default_property_values, config.IsTemporary(), + config.GetNamespace()); label_t vertex_label_id = schema_.get_vertex_label_id(vertex_type_name); VertexTable fresh_vt(schema_.get_vertex_schema(vertex_label_id)); fresh_vt.Init(ckp_, memory_level_); @@ -284,6 +285,13 @@ Status PropertyGraph::CreateEdgeType(const CreateEdgeTypeParam& config) { "Persistent edge cannot reference temporary vertex. Edge [" + edge_type_name + "] must be temporary."); } + const auto& namespace_name = config.GetNamespace(); + if (schema_.get_vertex_schema(src_lid)->namespace_name != namespace_name || + schema_.get_vertex_schema(dst_lid)->namespace_name != namespace_name) { + return Status( + StatusCode::ERR_INVALID_ARGUMENT, + "Edge namespace must match its source and destination vertices."); + } std::vector property_names; std::vector property_types; std::vector default_property_values; @@ -302,7 +310,8 @@ Status PropertyGraph::CreateEdgeType(const CreateEdgeTypeParam& config) { schema_.AddEdgeLabel(src_vertex_type, dst_vertex_type, edge_type_name, property_types, property_names, oe_strategy, ie_strategy, oe_mutable, ie_mutable, sort_key_for_nbr, description, - default_property_values, config.IsTemporary()); + default_property_values, config.IsTemporary(), + config.GetNamespace()); edge_label_total_count_ = schema_.edge_label_frontier(); label_t src_label_i = schema_.get_vertex_label_id(src_vertex_type); diff --git a/src/storages/graph/schema.cc b/src/storages/graph/schema.cc index a709dacce..f2e519972 100644 --- a/src/storages/graph/schema.cc +++ b/src/storages/graph/schema.cc @@ -651,7 +651,8 @@ void Schema::AddVertexLabel( const std::vector& property_names, const std::vector>& primary_key, size_t max_vnum, const std::string& description, - const std::vector& default_property_values, bool temporary) { + const std::vector& default_property_values, bool temporary, + const std::string& namespace_name) { label_t v_label_id = vertex_label_to_index(label); if (vlabel_tomb_.get(v_label_id)) { // Add back a deleted label vlabel_tomb_.reset(v_label_id); @@ -667,9 +668,10 @@ void Schema::AddVertexLabel( default_property_values, description, max_vnum); v_schemas_[v_label_id]->label_id = v_label_id; v_schemas_[v_label_id]->temporary = temporary; + v_schemas_[v_label_id]->namespace_name = namespace_name; VLOG(10) << "Add vertex label: " << label << ", id: " << (int) v_label_id << ", prop size: " << v_schemas_[v_label_id]->property_names.size() - << ", temporary: " << temporary; + << ", temporary: " << temporary << ", namespace: " << namespace_name; } void Schema::AddEdgeLabel( @@ -678,7 +680,8 @@ void Schema::AddEdgeLabel( const std::vector& prop_names, EdgeStrategy oe, EdgeStrategy ie, bool oe_mutable, bool ie_mutable, std::optional sort_key_for_nbr, const std::string& description, - const std::vector& default_property_values, bool temporary) { + const std::vector& default_property_values, bool temporary, + const std::string& namespace_name) { label_t src_label_id = vertex_label_to_index(src_label); label_t dst_label_id = vertex_label_to_index(dst_label); label_t edge_label_id = edge_label_to_index(edge_label); @@ -705,9 +708,10 @@ void Schema::AddEdgeLabel( elabel_triplet_tomb_.reset(label_id); } e_schemas_[label_id]->temporary = temporary; + e_schemas_[label_id]->namespace_name = namespace_name; VLOG(10) << "Add edge label: " << edge_label << ", id: " << (int) label_id << ", prop size: " << e_schemas_[label_id]->property_names.size() - << ", temporary: " << temporary; + << ", temporary: " << temporary << ", namespace: " << namespace_name; } bool Schema::is_vertex_label_temporary(label_t label) const { @@ -1454,10 +1458,14 @@ static Status parse_vertex_schema(YAML::Node node, Schema& schema) { std::vector property_types; std::vector property_names; std::string description; // default is empty string + std::string namespace_name = "default"; if (node["description"]) { description = node["description"].as(); } + if (node["namespace"]) { + namespace_name = node["namespace"].as(); + } if (node["nullable"]) { LOG(ERROR) << "nullable is not supported yet"; @@ -1526,7 +1534,8 @@ static Status parse_vertex_schema(YAML::Node node, Schema& schema) { } schema.AddVertexLabel(label_name, property_types, property_names, - primary_keys, max_num, description); + primary_keys, max_num, description, {}, false, + namespace_name); // check the type_id equals to storage's label_id int32_t type_id; if (!get_scalar(node, "type_id", type_id)) { @@ -1567,12 +1576,16 @@ static Status parse_edge_schema(YAML::Node node, Schema& schema) { std::vector property_types; std::vector prop_names; std::string description; // default is empty string + std::string namespace_name = "default"; RETURN_IF_NOT_OK(parse_edge_properties(node["properties"], edge_label_name, property_types, prop_names)); if (node["description"]) { description = node["description"].as(); } + if (node["namespace"]) { + namespace_name = node["namespace"].as(); + } if (node["nullable"]) { LOG(ERROR) << "nullable is not supported yet"; return Status(StatusCode::ERR_NOT_IMPLEMENTED, @@ -1754,7 +1767,8 @@ static Status parse_edge_schema(YAML::Node node, Schema& schema) { << " properties"; schema.AddEdgeLabel(src_label_name, dst_label_name, edge_label_name, property_types, prop_names, cur_oe, cur_ie, oe_mutable, - ie_mutable, sort_key_for_nbr, description); + ie_mutable, sort_key_for_nbr, description, {}, false, + namespace_name); } // check the type_id equals to storage's label_id @@ -1850,6 +1864,7 @@ bool dump_vertices_schema(const Schema& schema, YAML::Node& node) { YAML::Node cur_node(YAML::NodeType::Map); cur_node["type_name"] = schema.get_vertex_label_name(v_label); cur_node["description"] = schema.get_vertex_description(v_label); + cur_node["namespace"] = schema.get_vertex_schema(v_label)->namespace_name; cur_node["type_id"] = std::to_string(v_label); cur_node["properties"] = YAML::Node(YAML::NodeType::Sequence); auto properties = schema.get_vertex_properties(v_label); @@ -1897,6 +1912,10 @@ bool dump_edges_schema(const Schema& schema, YAML::Node& node) { for (auto src_v : v_labels) { for (auto dst_v : v_labels) { if (schema.is_edge_triplet_valid(src_v, dst_v, e_label)) { + if (!cur_node["namespace"]) { + cur_node["namespace"] = + schema.get_edge_schema(src_v, dst_v, e_label)->namespace_name; + } if (!properties_set) { auto properties = schema.get_edge_properties(src_v, dst_v, e_label); auto property_names = @@ -2597,7 +2616,8 @@ InArchive& operator<<(InArchive& archive, const VertexSchema& v_schema) { archive << v_schema.label_name << v_schema.property_types << v_schema.property_names << v_schema.primary_keys << v_schema.default_property_values << v_schema.description - << v_schema.max_num << v_schema.vprop_soft_deleted; + << v_schema.max_num << v_schema.vprop_soft_deleted + << v_schema.namespace_name; return archive; } @@ -2605,7 +2625,8 @@ OutArchive& operator>>(OutArchive& archive, VertexSchema& v_schema) { archive >> v_schema.label_name >> v_schema.property_types >> v_schema.property_names >> v_schema.primary_keys >> v_schema.default_property_values >> v_schema.description >> - v_schema.max_num >> v_schema.vprop_soft_deleted; + v_schema.max_num >> v_schema.vprop_soft_deleted >> + v_schema.namespace_name; return archive; } @@ -2615,7 +2636,7 @@ InArchive& operator<<(InArchive& archive, const EdgeSchema& e_schema) { << e_schema.ie_mutable << e_schema.oe_mutable << e_schema.ie_strategy << e_schema.oe_strategy << e_schema.properties << e_schema.property_names << e_schema.default_property_values - << e_schema.eprop_soft_deleted; + << e_schema.eprop_soft_deleted << e_schema.namespace_name; if (e_schema.sort_key_for_nbr.has_value()) { archive << static_cast(1) << e_schema.sort_key_for_nbr.value(); } else { @@ -2629,7 +2650,8 @@ OutArchive& operator>>(OutArchive& archive, EdgeSchema& e_schema) { e_schema.edge_label_name >> e_schema.description >> e_schema.ie_mutable >> e_schema.oe_mutable >> e_schema.ie_strategy >> e_schema.oe_strategy >> e_schema.properties >> e_schema.property_names >> - e_schema.default_property_values >> e_schema.eprop_soft_deleted; + e_schema.default_property_values >> e_schema.eprop_soft_deleted >> + e_schema.namespace_name; uint8_t has_sort_key_for_nbr; archive >> has_sort_key_for_nbr; if (has_sort_key_for_nbr) { diff --git a/src/storages/graph/schema_view.cc b/src/storages/graph/schema_view.cc new file mode 100644 index 000000000..37a907871 --- /dev/null +++ b/src/storages/graph/schema_view.cc @@ -0,0 +1,202 @@ +/** Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "neug/storages/graph/schema_view.h" + +#include + +#include "neug/utils/exception/exception.h" + +namespace neug { + +SchemaView::SchemaView(const Schema* schema, std::string namespace_name) + : schema_(schema), namespace_(std::move(namespace_name)) { + EnsureSchema(); +} + +SchemaView::SchemaView(const Schema& schema, std::string namespace_name) + : SchemaView(&schema, std::move(namespace_name)) {} + +const Schema& SchemaView::GetSchema() const { + EnsureSchema(); + return *schema_; +} + +std::vector> SchemaView::GetVertexSchemas() + const { + EnsureSchema(); + std::vector> result; + for (const auto& vertex_schema : schema_->get_all_vertex_schemas()) { + if (vertex_schema != nullptr && + schema_->is_vertex_label_valid(vertex_schema->label_id) && + IsVertexInNamespace(*vertex_schema)) { + result.emplace_back(vertex_schema); + } + } + return result; +} + +std::vector> SchemaView::GetEdgeSchemas() + const { + EnsureSchema(); + std::vector> result; + for (const auto& [_, edge_schema] : schema_->get_all_edge_schemas()) { + if (edge_schema != nullptr && + schema_->is_edge_triplet_valid(edge_schema->src_label_id, + edge_schema->dst_label_id, + edge_schema->edge_label_id) && + IsEdgeInNamespace(*edge_schema)) { + result.emplace_back(edge_schema); + } + } + return result; +} + +result> SchemaView::GetVertexSchema( + label_t label) const { + if (!ContainsVertexLabel(label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Vertex label " + std::to_string(label) + + " not found in namespace " + namespace_ + "."); + } + return schema_->get_vertex_schema(label); +} + +result> SchemaView::GetVertexSchema( + const std::string& label) const { + if (!ContainsVertexLabel(label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Vertex label " + label + " not found in namespace " + + namespace_ + "."); + } + return schema_->get_vertex_schema(schema_->get_vertex_label_id(label)); +} + +result> SchemaView::GetEdgeSchema( + label_t src_label, label_t dst_label, label_t edge_label) const { + if (!ContainsEdgeTriplet(src_label, dst_label, edge_label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Edge triplet (" + std::to_string(src_label) + ", " + + std::to_string(dst_label) + ", " + + std::to_string(edge_label) + + ") not found in namespace " + namespace_ + "."); + } + return schema_->get_edge_schema(src_label, dst_label, edge_label); +} + +result> SchemaView::GetEdgeSchema( + const std::string& src_label, const std::string& dst_label, + const std::string& edge_label) const { + if (!ContainsEdgeTriplet(src_label, dst_label, edge_label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Edge triplet (" + src_label + ", " + dst_label + ", " + + edge_label + ") not found in namespace " + + namespace_ + "."); + } + return schema_->get_edge_schema(schema_->get_vertex_label_id(src_label), + schema_->get_vertex_label_id(dst_label), + schema_->get_edge_label_id(edge_label)); +} + +bool SchemaView::ContainsVertexLabel(label_t label) const { + EnsureSchema(); + return schema_->is_vertex_label_valid(label) && IsVertexInNamespace(label); +} + +bool SchemaView::ContainsVertexLabel(const std::string& label) const { + EnsureSchema(); + return schema_->is_vertex_label_valid(label) && + IsVertexInNamespace(schema_->get_vertex_label_id(label)); +} + +bool SchemaView::ContainsEdgeLabel(label_t label) const { + EnsureSchema(); + if (!schema_->is_edge_label_valid(label)) { + return false; + } + for (const auto& edge_schema : GetEdgeSchemas()) { + if (edge_schema->edge_label_id == label) { + return true; + } + } + return false; +} + +bool SchemaView::ContainsEdgeLabel(const std::string& label) const { + EnsureSchema(); + return schema_->is_edge_label_valid(label) && + ContainsEdgeLabel(schema_->get_edge_label_id(label)); +} + +bool SchemaView::ContainsEdgeTriplet(label_t src_label, label_t dst_label, + label_t edge_label) const { + EnsureSchema(); + if (!schema_->is_edge_triplet_valid(src_label, dst_label, edge_label)) { + return false; + } + return IsEdgeInNamespace( + *schema_->get_edge_schema(src_label, dst_label, edge_label)); +} + +bool SchemaView::ContainsEdgeTriplet(const std::string& src_label, + const std::string& dst_label, + const std::string& edge_label) const { + EnsureSchema(); + if (!schema_->is_edge_triplet_valid(src_label, dst_label, edge_label)) { + return false; + } + return ContainsEdgeTriplet(schema_->get_vertex_label_id(src_label), + schema_->get_vertex_label_id(dst_label), + schema_->get_edge_label_id(edge_label)); +} + +std::vector SchemaView::GetVertexLabelIds() const { + std::vector result; + for (const auto& vertex_schema : GetVertexSchemas()) { + result.emplace_back(vertex_schema->label_id); + } + return result; +} + +std::vector SchemaView::GetEdgeLabelIds() const { + std::vector result; + std::unordered_set seen; + for (const auto& edge_schema : GetEdgeSchemas()) { + if (seen.emplace(edge_schema->edge_label_id).second) { + result.emplace_back(edge_schema->edge_label_id); + } + } + return result; +} + +void SchemaView::EnsureSchema() const { + if (schema_ == nullptr) { + THROW_INVALID_ARGUMENT_EXCEPTION("Schema is null"); + } +} + +bool SchemaView::IsVertexInNamespace(label_t label) const { + return IsVertexInNamespace(*schema_->get_vertex_schema(label)); +} + +bool SchemaView::IsVertexInNamespace(const VertexSchema& schema) const { + return schema.namespace_name == namespace_; +} + +bool SchemaView::IsEdgeInNamespace(const EdgeSchema& schema) const { + return schema.namespace_name == namespace_; +} + +} // namespace neug diff --git a/tests/storage/CMakeLists.txt b/tests/storage/CMakeLists.txt index 7f3758525..6f9a92a10 100644 --- a/tests/storage/CMakeLists.txt +++ b/tests/storage/CMakeLists.txt @@ -21,6 +21,8 @@ add_neug_test(edge_table_test test_edge_table.cc) add_neug_test(graph_view_test test_graph_view.cc) +add_neug_test(schema_view_test test_schema_view.cc) + add_neug_test(graph_snapshot_store_test test_graph_snapshot_store_concurrency.cc) add_neug_test(temporary_graph_test test_temporary_graph.cc) diff --git a/tests/storage/test_schema_view.cc b/tests/storage/test_schema_view.cc new file mode 100644 index 000000000..4981e2359 --- /dev/null +++ b/tests/storage/test_schema_view.cc @@ -0,0 +1,297 @@ +/** Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "neug/compiler/planner/gopt_planner.h" +#include "neug/storages/allocators.h" +#include "neug/storages/checkpoint_manager.h" +#include "neug/storages/graph/graph_interface.h" +#include "neug/storages/graph/graph_view.h" +#include "neug/storages/graph/operation_params.h" +#include "neug/storages/graph/property_graph.h" +#include "neug/storages/graph/schema_view.h" +#include "unittest/utils.h" + +#ifdef BUILD_HTTP_SERVER +#include "neug/neug.h" +#include "neug/server/neug_db_service.h" +#include "neug/transaction/update_transaction.h" +#endif + +namespace neug { + +class SchemaViewTest : public ::testing::Test { + protected: + void SetUp() override { + work_dir_ = std::string("/tmp/test_schema_view_") + + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::filesystem::remove_all(work_dir_); + std::filesystem::create_directories(work_dir_); + + checkpoint_manager_.Open(work_dir_); + graph_ = std::make_unique(); + graph_->Open(make_checkpoint(checkpoint_manager_), MemoryLevel::kInMemory); + graph_view_ = std::make_unique(*graph_); + allocator_ = std::make_unique(MemoryLevel::kInMemory, work_dir_); + graph_interface_ = std::make_unique( + *graph_, *graph_view_, 0, *allocator_); + } + + void TearDown() override { + graph_interface_.reset(); + graph_view_.reset(); + graph_.reset(); + allocator_.reset(); + std::filesystem::remove_all(work_dir_); + } + + void CreateVertex(const std::string& label, + const std::string& namespace_name) { + CreateVertexTypeParamBuilder builder; + ASSERT_TRUE(graph_interface_ + ->CreateVertexType(builder.VertexLabel(label) + .AddProperty("id", Value::INT64(0)) + .AddPrimaryKeyName("id") + .Namespace(namespace_name) + .Build()) + .ok()); + } + + void CreateSelfEdge(const std::string& vertex_label, + const std::string& edge_label, + const std::string& namespace_name) { + CreateEdgeTypeParamBuilder builder; + ASSERT_TRUE(graph_interface_ + ->CreateEdgeType(builder.SrcLabel(vertex_label) + .DstLabel(vertex_label) + .EdgeLabel(edge_label) + .Namespace(namespace_name) + .Build()) + .ok()); + } + + std::string work_dir_; + CheckpointManager checkpoint_manager_; + std::unique_ptr graph_; + std::unique_ptr graph_view_; + std::unique_ptr allocator_; + std::unique_ptr graph_interface_; +}; + +TEST_F(SchemaViewTest, IsolatesVertexAndEdgeTypesByNamespace) { + CreateVertex("DefaultPerson", "default"); + CreateSelfEdge("DefaultPerson", "DefaultKnows", "default"); + CreateVertex("FtsDocument", "fts"); + CreateSelfEdge("FtsDocument", "FtsReferences", "fts"); + + const auto& schema = graph_->schema(); + SchemaView default_view(schema, "default"); + SchemaView fts_view(schema, "fts"); + + const auto default_vertex_id = schema.get_vertex_label_id("DefaultPerson"); + const auto fts_vertex_id = schema.get_vertex_label_id("FtsDocument"); + const auto default_edge_id = schema.get_edge_label_id("DefaultKnows"); + const auto fts_edge_id = schema.get_edge_label_id("FtsReferences"); + + EXPECT_TRUE(default_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_TRUE(default_view.ContainsEdgeLabel("DefaultKnows")); + EXPECT_TRUE(default_view.ContainsEdgeTriplet("DefaultPerson", "DefaultPerson", + "DefaultKnows")); + EXPECT_FALSE(default_view.ContainsVertexLabel("FtsDocument")); + EXPECT_FALSE(default_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_FALSE(default_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + + auto default_vertex_by_id = default_view.GetVertexSchema(default_vertex_id); + ASSERT_TRUE(default_vertex_by_id); + EXPECT_EQ(default_vertex_by_id.value()->label_name, "DefaultPerson"); + auto default_vertex_by_name = default_view.GetVertexSchema("DefaultPerson"); + ASSERT_TRUE(default_vertex_by_name); + EXPECT_EQ(default_vertex_by_name.value()->label_id, default_vertex_id); + auto default_edge_by_id = default_view.GetEdgeSchema( + default_vertex_id, default_vertex_id, default_edge_id); + ASSERT_TRUE(default_edge_by_id); + EXPECT_EQ(default_edge_by_id.value()->edge_label_name, "DefaultKnows"); + auto default_edge_by_name = default_view.GetEdgeSchema( + "DefaultPerson", "DefaultPerson", "DefaultKnows"); + ASSERT_TRUE(default_edge_by_name); + EXPECT_EQ(default_edge_by_name.value()->edge_label_id, default_edge_id); + + auto hidden_vertex_by_id = default_view.GetVertexSchema(fts_vertex_id); + ASSERT_FALSE(hidden_vertex_by_id); + EXPECT_EQ(hidden_vertex_by_id.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_vertex_by_name = default_view.GetVertexSchema("FtsDocument"); + ASSERT_FALSE(hidden_vertex_by_name); + EXPECT_EQ(hidden_vertex_by_name.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_edge_by_id = + default_view.GetEdgeSchema(fts_vertex_id, fts_vertex_id, fts_edge_id); + ASSERT_FALSE(hidden_edge_by_id); + EXPECT_EQ(hidden_edge_by_id.error().error_code(), StatusCode::ERR_NOT_FOUND); + auto hidden_edge_by_name = + default_view.GetEdgeSchema("FtsDocument", "FtsDocument", "FtsReferences"); + ASSERT_FALSE(hidden_edge_by_name); + EXPECT_EQ(hidden_edge_by_name.error().error_code(), + StatusCode::ERR_NOT_FOUND); + + EXPECT_TRUE(fts_view.ContainsVertexLabel("FtsDocument")); + EXPECT_TRUE(fts_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_TRUE(fts_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + EXPECT_FALSE(fts_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_FALSE(fts_view.ContainsEdgeLabel("DefaultKnows")); + EXPECT_FALSE(fts_view.ContainsEdgeTriplet("DefaultPerson", "DefaultPerson", + "DefaultKnows")); + EXPECT_FALSE(fts_view.GetVertexSchema(default_vertex_id)); + EXPECT_FALSE(fts_view.GetEdgeSchema(default_vertex_id, default_vertex_id, + default_edge_id)); + + ASSERT_EQ(default_view.GetVertexSchemas().size(), 1u); + EXPECT_EQ(default_view.GetVertexSchemas()[0]->label_name, "DefaultPerson"); + ASSERT_EQ(default_view.GetEdgeSchemas().size(), 1u); + EXPECT_EQ(default_view.GetEdgeSchemas()[0]->edge_label_name, "DefaultKnows"); + ASSERT_EQ(fts_view.GetVertexSchemas().size(), 1u); + EXPECT_EQ(fts_view.GetVertexSchemas()[0]->label_name, "FtsDocument"); + ASSERT_EQ(fts_view.GetEdgeSchemas().size(), 1u); + EXPECT_EQ(fts_view.GetEdgeSchemas()[0]->edge_label_name, "FtsReferences"); +} + +TEST_F(SchemaViewTest, GoptPlannerUsesDefaultNamespace) { + CreateVertex("DefaultPerson", "default"); + CreateVertex("FtsDocument", "fts"); + + GOptPlanner planner; + GraphStats stats; + auto default_result = planner.compilePlan("MATCH (n:DefaultPerson) RETURN n", + &graph_->schema(), stats); + EXPECT_TRUE(default_result.has_value()); + + auto fts_result = planner.compilePlan("MATCH (n:FtsDocument) RETURN n", + &graph_->schema(), stats); + EXPECT_FALSE(fts_result.has_value()); +} + +#ifdef BUILD_HTTP_SERVER +TEST(StorageTPSchemaViewTest, + StorageTPUpdateInterfaceIsolatesTypesByNamespaceAfterCommit) { + const auto work_dir = + std::string("/tmp/test_schema_view_") + + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::filesystem::remove_all(work_dir); + + NeugDB db; + NeugDBConfig config(work_dir); + config.memory_level = MemoryLevel::kInMemory; + ASSERT_TRUE(db.Open(config)); + auto service = std::make_shared(db); + + { + auto session = service->AcquireSession(); + auto txn = session->GetUpdateTransaction(); + StorageTPUpdateInterface graph_interface(txn); + + auto create_vertex = [&](const std::string& label, + const std::string& namespace_name) { + CreateVertexTypeParamBuilder builder; + return graph_interface.CreateVertexType( + builder.VertexLabel(label) + .AddProperty("id", Value::INT64(0)) + .AddPrimaryKeyName("id") + .Namespace(namespace_name) + .Build()); + }; + auto create_self_edge = [&](const std::string& vertex_label, + const std::string& edge_label, + const std::string& namespace_name) { + CreateEdgeTypeParamBuilder builder; + return graph_interface.CreateEdgeType(builder.SrcLabel(vertex_label) + .DstLabel(vertex_label) + .EdgeLabel(edge_label) + .Namespace(namespace_name) + .Build()); + }; + + ASSERT_TRUE(create_vertex("DefaultPerson", "default").ok()); + ASSERT_TRUE( + create_self_edge("DefaultPerson", "DefaultKnows", "default").ok()); + ASSERT_TRUE(create_vertex("FtsDocument", "fts").ok()); + ASSERT_TRUE(create_self_edge("FtsDocument", "FtsReferences", "fts").ok()); + + SchemaView default_view(txn.schema(), "default"); + SchemaView fts_view(txn.schema(), "fts"); + EXPECT_TRUE(default_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_TRUE(default_view.ContainsEdgeLabel("DefaultKnows")); + EXPECT_FALSE(default_view.ContainsVertexLabel("FtsDocument")); + EXPECT_FALSE(default_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_TRUE(fts_view.ContainsVertexLabel("FtsDocument")); + EXPECT_TRUE(fts_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_FALSE(fts_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_FALSE(fts_view.ContainsEdgeLabel("DefaultKnows")); + + ASSERT_TRUE(txn.Commit()); + } + + { + auto session = service->AcquireSession(); + auto txn = session->GetReadTransaction(); + const auto& schema = txn.schema(); + SchemaView default_view(schema, "default"); + SchemaView fts_view(schema, "fts"); + + const auto default_vertex_id = schema.get_vertex_label_id("DefaultPerson"); + const auto fts_vertex_id = schema.get_vertex_label_id("FtsDocument"); + const auto default_edge_id = schema.get_edge_label_id("DefaultKnows"); + const auto fts_edge_id = schema.get_edge_label_id("FtsReferences"); + + EXPECT_TRUE(default_view.ContainsEdgeTriplet( + "DefaultPerson", "DefaultPerson", "DefaultKnows")); + EXPECT_FALSE(default_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + auto hidden_fts_vertex = default_view.GetVertexSchema(fts_vertex_id); + ASSERT_FALSE(hidden_fts_vertex); + EXPECT_EQ(hidden_fts_vertex.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_fts_edge = + default_view.GetEdgeSchema(fts_vertex_id, fts_vertex_id, fts_edge_id); + ASSERT_FALSE(hidden_fts_edge); + EXPECT_EQ(hidden_fts_edge.error().error_code(), StatusCode::ERR_NOT_FOUND); + + EXPECT_TRUE(fts_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + EXPECT_FALSE(fts_view.ContainsEdgeTriplet("DefaultPerson", "DefaultPerson", + "DefaultKnows")); + auto hidden_default_vertex = fts_view.GetVertexSchema(default_vertex_id); + ASSERT_FALSE(hidden_default_vertex); + EXPECT_EQ(hidden_default_vertex.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_default_edge = fts_view.GetEdgeSchema( + default_vertex_id, default_vertex_id, default_edge_id); + ASSERT_FALSE(hidden_default_edge); + EXPECT_EQ(hidden_default_edge.error().error_code(), + StatusCode::ERR_NOT_FOUND); + } + + db.Close(); + std::filesystem::remove_all(work_dir); +} +#endif + +} // namespace neug From ec7dc7bd0bc48cb062ee89238064c0aec7705944 Mon Sep 17 00:00:00 2001 From: "yihe.zxl" Date: Mon, 13 Jul 2026 15:36:41 +0800 Subject: [PATCH 2/4] feat: add namespace-aware schema views --- include/neug/compiler/catalog/catalog.h | 8 +- include/neug/compiler/gopt/g_catalog.h | 1 + include/neug/compiler/main/metadata_manager.h | 4 + .../neug/storages/graph/operation_params.h | 16 +- include/neug/storages/graph/schema.h | 11 +- include/neug/storages/graph/schema_view.h | 73 +++++ src/compiler/catalog/catalog.cpp | 108 +++---- src/compiler/gopt/g_catalog.cpp | 5 + src/compiler/main/metadata_manager.cpp | 12 +- src/compiler/planner/gopt_planner.cc | 4 +- src/storages/graph/operation_params.cc | 8 + src/storages/graph/property_graph.cc | 13 +- src/storages/graph/schema.cc | 42 ++- src/storages/graph/schema_view.cc | 202 ++++++++++++ tests/storage/CMakeLists.txt | 2 + tests/storage/test_schema_view.cc | 297 ++++++++++++++++++ 16 files changed, 723 insertions(+), 83 deletions(-) create mode 100644 include/neug/storages/graph/schema_view.h create mode 100644 src/storages/graph/schema_view.cc create mode 100644 tests/storage/test_schema_view.cc diff --git a/include/neug/compiler/catalog/catalog.h b/include/neug/compiler/catalog/catalog.h index 33f8613fd..5e1a92a7f 100644 --- a/include/neug/compiler/catalog/catalog.h +++ b/include/neug/compiler/catalog/catalog.h @@ -23,12 +23,13 @@ #pragma once #include +#include #include "neug/compiler/catalog/catalog_entry/function_catalog_entry.h" #include "neug/compiler/catalog/catalog_set.h" #include "neug/compiler/common/cast.h" #include "neug/compiler/function/function.h" -#include "neug/storages/graph/schema.h" +#include "neug/storages/graph/schema_view.h" namespace neug::main { struct DBConfig; @@ -167,14 +168,15 @@ class NEUG_API Catalog { } virtual std::unique_ptr clone(const Schema* schema) const; + virtual std::unique_ptr clone(const SchemaView* schema) const; private: void initCatalogSets(); protected: - void setSchema(const Schema* schema); + void setSchema(const SchemaView* schema); - const Schema* schema; + std::optional schema; private: std::shared_ptr sequences; diff --git a/include/neug/compiler/gopt/g_catalog.h b/include/neug/compiler/gopt/g_catalog.h index ed08f44c3..809340be6 100644 --- a/include/neug/compiler/gopt/g_catalog.h +++ b/include/neug/compiler/gopt/g_catalog.h @@ -36,6 +36,7 @@ class GCatalog : public Catalog { const std::string& signatureName); std::unique_ptr clone(const Schema* schema) const override; + std::unique_ptr clone(const SchemaView* schema) const override; private: void registerBuiltInFunctions(); diff --git a/include/neug/compiler/main/metadata_manager.h b/include/neug/compiler/main/metadata_manager.h index 5ab742b12..ae0054339 100644 --- a/include/neug/compiler/main/metadata_manager.h +++ b/include/neug/compiler/main/metadata_manager.h @@ -45,6 +45,7 @@ class CatalogEntry; class GraphStats; class Schema; +class SchemaView; namespace function { struct Function; @@ -92,6 +93,9 @@ class MetadataManager { std::unique_ptr clone(const Schema* schema, const GraphStats& stats) const; + std::unique_ptr clone(const SchemaView* schema, + const GraphStats& stats) const; + std::shared_ptr getGraphStats() const; graph::GraphEntrySet& getGraphEntrySetUnsafe(); diff --git a/include/neug/storages/graph/operation_params.h b/include/neug/storages/graph/operation_params.h index 44ffebd2f..60e433c96 100644 --- a/include/neug/storages/graph/operation_params.h +++ b/include/neug/storages/graph/operation_params.h @@ -31,6 +31,7 @@ class CreateVertexTypeParam { std::vector> properties; std::vector primary_key_names; bool temporary = false; + std::string namespace_name = "default"; CreateVertexTypeParam() = default; friend class CreateVertexTypeParamBuilder; @@ -43,6 +44,7 @@ class CreateVertexTypeParam { return primary_key_names; } bool IsTemporary() const { return temporary; } + const std::string& GetNamespace() const { return namespace_name; } void Serialize(InArchive& arc) const; static CreateVertexTypeParam Deserialize(OutArchive& arc); @@ -87,6 +89,11 @@ class CreateVertexTypeParamBuilder { return *this; } + CreateVertexTypeParamBuilder& Namespace(const std::string& namespace_name) { + config.namespace_name = namespace_name; + return *this; + } + CreateVertexTypeParam Build() { if (config.vertex_label_name.empty()) { LOG(ERROR) << "Vertex label cannot be empty."; @@ -110,6 +117,7 @@ class CreateEdgeTypeParam { EdgeStrategy ie_edge_strategy; std::optional sort_key_for_nbr; bool temporary = false; + std::string namespace_name = "default"; CreateEdgeTypeParam() = default; friend class CreateEdgeTypeParamBuilder; @@ -126,6 +134,7 @@ class CreateEdgeTypeParam { return sort_key_for_nbr; } bool IsTemporary() const { return temporary; } + const std::string& GetNamespace() const { return namespace_name; } void Serialize(InArchive& arc) const; static CreateEdgeTypeParam Deserialize(OutArchive& arc); @@ -187,6 +196,11 @@ class CreateEdgeTypeParamBuilder { return *this; } + CreateEdgeTypeParamBuilder& Namespace(const std::string& namespace_name) { + config.namespace_name = namespace_name; + return *this; + } + CreateEdgeTypeParam Build() { if (config.src_label_name.empty()) { LOG(ERROR) << "Source label must be specified."; @@ -547,4 +561,4 @@ class DeleteEdgePropertiesParamBuilder { } }; -} // namespace neug \ No newline at end of file +} // namespace neug diff --git a/include/neug/storages/graph/schema.h b/include/neug/storages/graph/schema.h index d5e1cf092..9fc021c87 100644 --- a/include/neug/storages/graph/schema.h +++ b/include/neug/storages/graph/schema.h @@ -247,6 +247,9 @@ struct VertexSchema : public SchemaEntry { std::string description; size_t max_num; + // Logical namespace this vertex type belongs to. + std::string namespace_name = "default"; + // Mark whether the vertex property is soft deleted std::vector vprop_soft_deleted; @@ -409,6 +412,9 @@ struct EdgeSchema : public SchemaEntry { std::vector property_names; std::vector default_property_values; + // Logical namespace this edge type belongs to. + std::string namespace_name = "default"; + // Mark whether the edge property is soft deleted std::vector eprop_soft_deleted; @@ -556,7 +562,7 @@ class Schema { size_t max_vnum = static_cast(1) << 32, const std::string& description = "", const std::vector& default_property_values = {}, - bool temporary = false); + bool temporary = false, const std::string& namespace_name = "default"); void AddEdgeLabel(const std::string& src_label, const std::string& dst_label, const std::string& edge_label, @@ -568,7 +574,8 @@ class Schema { std::optional sort_key_for_nbr = std::nullopt, const std::string& description = "", const std::vector& default_property_values = {}, - bool temporary = false); + bool temporary = false, + const std::string& namespace_name = "default"); bool is_vertex_label_temporary(label_t label) const; bool is_edge_label_temporary(uint32_t edge_triplet_key) const; diff --git a/include/neug/storages/graph/schema_view.h b/include/neug/storages/graph/schema_view.h new file mode 100644 index 000000000..efc45d0af --- /dev/null +++ b/include/neug/storages/graph/schema_view.h @@ -0,0 +1,73 @@ +/** Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include "neug/storages/graph/schema.h" + +namespace neug { + +class SchemaView { + public: + SchemaView(const Schema* schema, std::string namespace_name); + SchemaView(const Schema& schema, std::string namespace_name); + + const Schema& GetSchema() const; + const std::string& GetNamespace() const { return namespace_; } + + std::vector> GetVertexSchemas() const; + std::vector> GetEdgeSchemas() const; + + result> GetVertexSchema( + label_t label) const; + result> GetVertexSchema( + const std::string& label) const; + + result> GetEdgeSchema( + label_t src_label, label_t dst_label, label_t edge_label) const; + result> GetEdgeSchema( + const std::string& src_label, const std::string& dst_label, + const std::string& edge_label) const; + + bool ContainsVertexLabel(label_t label) const; + bool ContainsVertexLabel(const std::string& label) const; + + bool ContainsEdgeLabel(label_t label) const; + bool ContainsEdgeLabel(const std::string& label) const; + + bool ContainsEdgeTriplet(label_t src_label, label_t dst_label, + label_t edge_label) const; + bool ContainsEdgeTriplet(const std::string& src_label, + const std::string& dst_label, + const std::string& edge_label) const; + + std::vector GetVertexLabelIds() const; + std::vector GetEdgeLabelIds() const; + + private: + void EnsureSchema() const; + + bool IsVertexInNamespace(label_t label) const; + bool IsVertexInNamespace(const VertexSchema& schema) const; + bool IsEdgeInNamespace(const EdgeSchema& schema) const; + + const Schema* schema_; + std::string namespace_; +}; + +} // namespace neug diff --git a/src/compiler/catalog/catalog.cpp b/src/compiler/catalog/catalog.cpp index 0ddf0618a..38514565e 100644 --- a/src/compiler/catalog/catalog.cpp +++ b/src/compiler/catalog/catalog.cpp @@ -58,19 +58,28 @@ bool nameEquals(std::string_view lhs, std::string_view rhs) { } } // namespace -Catalog::Catalog() : schema{nullptr}, version{0} { initCatalogSets(); } +Catalog::Catalog() : schema{std::nullopt}, version{0} { initCatalogSets(); } Catalog::Catalog(const std::string& directory, VirtualFileSystem* vfs) - : schema{nullptr}, version{0} {} + : schema{std::nullopt}, version{0} {} std::unique_ptr Catalog::clone(const Schema* schema) const { + SchemaView schema_view(schema, "default"); + return clone(&schema_view); +} + +std::unique_ptr Catalog::clone(const SchemaView* schema) const { auto cloned = std::make_unique(*this); cloned->setSchema(schema); return cloned; } -void Catalog::setSchema(const Schema* schema) { - this->schema = schema; +void Catalog::setSchema(const SchemaView* schema) { + if (schema == nullptr) { + this->schema.reset(); + } else { + this->schema = *schema; + } incrementVersion(); } @@ -87,21 +96,15 @@ void Catalog::initCatalogSets() { bool Catalog::containsTable(const Transaction* transaction, const std::string& tableName, bool useInternal) const { - if (schema == nullptr) { + if (!schema) { return false; } - for (auto& entry : schema->get_all_vertex_schemas()) { - if (entry != nullptr && - schema->is_vertex_label_valid(entry->get_entry_id()) && - nameEquals(entry->label_name, tableName)) { + for (const auto& entry : schema->GetVertexSchemas()) { + if (nameEquals(entry->label_name, tableName)) { return true; } } - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (nameEquals(edgeSchema->edge_label_name, tableName) || nameEquals(getChildRelTableName(*edgeSchema), tableName)) { return true; @@ -112,14 +115,14 @@ bool Catalog::containsTable(const Transaction* transaction, bool Catalog::containsTable(const Transaction* transaction, table_id_t tableID, bool useInternal) const { - if (schema == nullptr) { + if (!schema) { return false; } if (tableID <= std::numeric_limits::max() && - schema->is_vertex_label_valid(static_cast(tableID))) { + schema->ContainsVertexLabel(static_cast(tableID))) { return true; } - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (edgeSchema->get_entry_id() == tableID || edgeSchema->getLabelId() == tableID) { return true; @@ -130,13 +133,13 @@ bool Catalog::containsTable(const Transaction* transaction, table_id_t tableID, const SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, table_id_t tableID) const { - if (schema != nullptr && tableID <= std::numeric_limits::max() && - schema->is_vertex_label_valid(static_cast(tableID))) { - return schema->get_vertex_schema(static_cast(tableID)).get(); + if (schema && tableID <= std::numeric_limits::max() && + schema->ContainsVertexLabel(static_cast(tableID))) { + return schema->GetVertexSchema(static_cast(tableID)).value().get(); } - if (schema != nullptr) { + if (schema) { const EdgeSchema* labelMatch = nullptr; - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (edgeSchema->get_entry_id() == tableID) { return edgeSchema.get(); } @@ -160,12 +163,10 @@ const SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, const std::string& tableName, bool useInternal) const { - if (schema != nullptr) { - VertexSchema* vertexResult = nullptr; - for (auto& entry : schema->get_all_vertex_schemas()) { - if (entry == nullptr || - !schema->is_vertex_label_valid(entry->get_entry_id()) || - !nameEquals(entry->label_name, tableName)) { + if (schema) { + const VertexSchema* vertexResult = nullptr; + for (const auto& entry : schema->GetVertexSchemas()) { + if (!nameEquals(entry->label_name, tableName)) { continue; } if (vertexResult != nullptr) { @@ -175,16 +176,12 @@ SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, vertexResult = entry.get(); } if (vertexResult != nullptr) { - return vertexResult; + return const_cast(vertexResult); } } - EdgeSchema* result = nullptr; - if (schema != nullptr) { - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + const EdgeSchema* result = nullptr; + if (schema) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (!nameEquals(edgeSchema->edge_label_name, tableName) && !nameEquals(getChildRelTableName(*edgeSchema), tableName)) { continue; @@ -200,20 +197,17 @@ SchemaEntry* Catalog::getTableCatalogEntry(const Transaction* transaction, THROW_SCHEMA_MISMATCH( stringFormat("{} does not exist in catalog.", tableName)); } - return result; + return const_cast(result); } std::vector Catalog::getNodeTableEntries( const Transaction* transaction, bool useInternal) const { std::vector result; - if (schema == nullptr) { + if (!schema) { return result; } - for (auto& entry : schema->get_all_vertex_schemas()) { - if (entry != nullptr && - schema->is_vertex_label_valid(entry->get_entry_id())) { - result.push_back(entry.get()); - } + for (const auto& entry : schema->GetVertexSchemas()) { + result.push_back(const_cast(entry.get())); } return result; } @@ -221,15 +215,11 @@ std::vector Catalog::getNodeTableEntries( std::vector Catalog::getRelTableEntries( const Transaction* transaction, bool useInternal) const { std::vector result; - if (schema == nullptr) { + if (!schema) { return result; } - for (auto& [_, entry] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(entry->getSrcTableID()) || - !schema->is_vertex_label_valid(entry->getDstTableID())) { - continue; - } - result.push_back(entry.get()); + for (const auto& entry : schema->GetEdgeSchemas()) { + result.push_back(const_cast(entry.get())); } std::sort(result.begin(), result.end(), [](const auto* lhs, const auto* rhs) { return std::tie(lhs->edge_label_id, lhs->src_label_id, lhs->dst_label_id, @@ -254,15 +244,11 @@ std::vector Catalog::getTableEntries( bool Catalog::containsRelGroup(const Transaction* transaction, const std::string& name) const { - if (schema == nullptr) { + if (!schema) { return false; } common::idx_t count = 0; - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (nameEquals(edgeSchema->edge_label_name, name)) { ++count; } @@ -273,14 +259,10 @@ bool Catalog::containsRelGroup(const Transaction* transaction, std::vector Catalog::getRelGroupEntry( const Transaction* transaction, const std::string& name) const { std::vector result; - if (schema != nullptr) { - for (auto& [_, edgeSchema] : schema->get_all_edge_schemas()) { - if (!schema->is_vertex_label_valid(edgeSchema->getSrcTableID()) || - !schema->is_vertex_label_valid(edgeSchema->getDstTableID())) { - continue; - } + if (schema) { + for (const auto& edgeSchema : schema->GetEdgeSchemas()) { if (nameEquals(edgeSchema->edge_label_name, name)) { - result.push_back(edgeSchema.get()); + result.push_back(const_cast(edgeSchema.get())); } } } diff --git a/src/compiler/gopt/g_catalog.cpp b/src/compiler/gopt/g_catalog.cpp index 9fff8961d..bfda59fc1 100644 --- a/src/compiler/gopt/g_catalog.cpp +++ b/src/compiler/gopt/g_catalog.cpp @@ -29,6 +29,11 @@ namespace catalog { GCatalog::GCatalog() : Catalog() { registerBuiltInFunctions(); } std::unique_ptr GCatalog::clone(const Schema* schema) const { + SchemaView schema_view(schema, "default"); + return clone(&schema_view); +} + +std::unique_ptr GCatalog::clone(const SchemaView* schema) const { auto cloned = std::make_unique(*this); cloned->setSchema(schema); return cloned; diff --git a/src/compiler/main/metadata_manager.cpp b/src/compiler/main/metadata_manager.cpp index facc0c3f3..9ff0651d8 100644 --- a/src/compiler/main/metadata_manager.cpp +++ b/src/compiler/main/metadata_manager.cpp @@ -68,7 +68,7 @@ MetadataManager::MetadataManager( graphEntrySet{std::move(graphEntrySet)} {} std::unique_ptr MetadataManager::clone( - const Schema* schema, const GraphStats& stats) const { + const SchemaView* schema, const GraphStats& stats) const { if (!catalog) { THROW_CATALOG_EXCEPTION("Catalog is not set"); } @@ -85,6 +85,16 @@ const graph::GraphEntrySet& MetadataManager::getGraphEntrySet() const { return *graphEntrySet; } +std::unique_ptr MetadataManager::clone( + const Schema* schema, const GraphStats& stats) const { + if (!catalog) { + THROW_CATALOG_EXCEPTION("Catalog is not set"); + } + return std::unique_ptr( + new MetadataManager(catalog->clone(schema), stats, memoryManager, vfs, + extensionManager, graphEntrySet)); +} + std::shared_ptr MetadataManager::getGraphStats() const { return std::make_shared(statsManager); } diff --git a/src/compiler/planner/gopt_planner.cc b/src/compiler/planner/gopt_planner.cc index 968ad976e..5b9dd40b3 100644 --- a/src/compiler/planner/gopt_planner.cc +++ b/src/compiler/planner/gopt_planner.cc @@ -20,6 +20,7 @@ limitations under the License. #include "neug/compiler/gopt/g_catalog.h" #include "neug/compiler/gopt/g_physical_convertor.h" #include "neug/compiler/gopt/g_result_schema.h" +#include "neug/storages/graph/schema_view.h" #include "neug/utils/exception/exception.h" namespace neug { @@ -33,7 +34,8 @@ result> GOptPlanner::compilePlan( RETURN_ERROR(Status(StatusCode::ERR_INVALID_SCHEMA, "Schema is null")); } - auto queryDatabase = database->clone(schema, stats); + SchemaView schema_view(schema, "default"); + auto queryDatabase = database->clone(&schema_view, stats); main::ClientContext queryContext(queryDatabase.get()); if (queryDatabase->getCatalog() == nullptr) { diff --git a/src/storages/graph/operation_params.cc b/src/storages/graph/operation_params.cc index 13d4e32bd..7a065f043 100644 --- a/src/storages/graph/operation_params.cc +++ b/src/storages/graph/operation_params.cc @@ -32,6 +32,7 @@ void CreateVertexTypeParam::Serialize(InArchive& arc) const { for (const auto& key : primary_key_names) { arc << key; } + arc << namespace_name; } CreateVertexTypeParam CreateVertexTypeParam::Deserialize(OutArchive& arc) { @@ -55,6 +56,9 @@ CreateVertexTypeParam CreateVertexTypeParam::Deserialize(OutArchive& arc) { arc >> key; builder.AddPrimaryKeyName(key); } + std::string namespace_name; + arc >> namespace_name; + builder.Namespace(namespace_name); return builder.Build(); } @@ -70,6 +74,7 @@ void CreateEdgeTypeParam::Serialize(InArchive& arc) const { } else { arc << static_cast(0); } + arc << namespace_name; } CreateEdgeTypeParam CreateEdgeTypeParam::Deserialize(OutArchive& arc) { @@ -98,6 +103,9 @@ CreateEdgeTypeParam CreateEdgeTypeParam::Deserialize(OutArchive& arc) { arc >> sort_key; builder.SortKeyForNbr(sort_key); } + std::string namespace_name; + arc >> namespace_name; + builder.Namespace(namespace_name); return builder.Build(); } diff --git a/src/storages/graph/property_graph.cc b/src/storages/graph/property_graph.cc index 090bf2a5e..3f9c693b1 100644 --- a/src/storages/graph/property_graph.cc +++ b/src/storages/graph/property_graph.cc @@ -218,7 +218,8 @@ Status PropertyGraph::CreateVertexType(const CreateVertexTypeParam& config) { const auto& vertex_type_name = config.GetVertexLabel(); schema_.AddVertexLabel(vertex_type_name, property_types, property_names, primary_keys, Schema::MAX_VNUM, description, - default_property_values, config.IsTemporary()); + default_property_values, config.IsTemporary(), + config.GetNamespace()); label_t vertex_label_id = schema_.get_vertex_label_id(vertex_type_name); VertexTable fresh_vt(schema_.get_vertex_schema(vertex_label_id)); fresh_vt.Init(ckp_, memory_level_); @@ -284,6 +285,13 @@ Status PropertyGraph::CreateEdgeType(const CreateEdgeTypeParam& config) { "Persistent edge cannot reference temporary vertex. Edge [" + edge_type_name + "] must be temporary."); } + const auto& namespace_name = config.GetNamespace(); + if (schema_.get_vertex_schema(src_lid)->namespace_name != namespace_name || + schema_.get_vertex_schema(dst_lid)->namespace_name != namespace_name) { + return Status( + StatusCode::ERR_INVALID_ARGUMENT, + "Edge namespace must match its source and destination vertices."); + } std::vector property_names; std::vector property_types; std::vector default_property_values; @@ -302,7 +310,8 @@ Status PropertyGraph::CreateEdgeType(const CreateEdgeTypeParam& config) { schema_.AddEdgeLabel(src_vertex_type, dst_vertex_type, edge_type_name, property_types, property_names, oe_strategy, ie_strategy, oe_mutable, ie_mutable, sort_key_for_nbr, description, - default_property_values, config.IsTemporary()); + default_property_values, config.IsTemporary(), + config.GetNamespace()); edge_label_total_count_ = schema_.edge_label_frontier(); label_t src_label_i = schema_.get_vertex_label_id(src_vertex_type); diff --git a/src/storages/graph/schema.cc b/src/storages/graph/schema.cc index a709dacce..f2e519972 100644 --- a/src/storages/graph/schema.cc +++ b/src/storages/graph/schema.cc @@ -651,7 +651,8 @@ void Schema::AddVertexLabel( const std::vector& property_names, const std::vector>& primary_key, size_t max_vnum, const std::string& description, - const std::vector& default_property_values, bool temporary) { + const std::vector& default_property_values, bool temporary, + const std::string& namespace_name) { label_t v_label_id = vertex_label_to_index(label); if (vlabel_tomb_.get(v_label_id)) { // Add back a deleted label vlabel_tomb_.reset(v_label_id); @@ -667,9 +668,10 @@ void Schema::AddVertexLabel( default_property_values, description, max_vnum); v_schemas_[v_label_id]->label_id = v_label_id; v_schemas_[v_label_id]->temporary = temporary; + v_schemas_[v_label_id]->namespace_name = namespace_name; VLOG(10) << "Add vertex label: " << label << ", id: " << (int) v_label_id << ", prop size: " << v_schemas_[v_label_id]->property_names.size() - << ", temporary: " << temporary; + << ", temporary: " << temporary << ", namespace: " << namespace_name; } void Schema::AddEdgeLabel( @@ -678,7 +680,8 @@ void Schema::AddEdgeLabel( const std::vector& prop_names, EdgeStrategy oe, EdgeStrategy ie, bool oe_mutable, bool ie_mutable, std::optional sort_key_for_nbr, const std::string& description, - const std::vector& default_property_values, bool temporary) { + const std::vector& default_property_values, bool temporary, + const std::string& namespace_name) { label_t src_label_id = vertex_label_to_index(src_label); label_t dst_label_id = vertex_label_to_index(dst_label); label_t edge_label_id = edge_label_to_index(edge_label); @@ -705,9 +708,10 @@ void Schema::AddEdgeLabel( elabel_triplet_tomb_.reset(label_id); } e_schemas_[label_id]->temporary = temporary; + e_schemas_[label_id]->namespace_name = namespace_name; VLOG(10) << "Add edge label: " << edge_label << ", id: " << (int) label_id << ", prop size: " << e_schemas_[label_id]->property_names.size() - << ", temporary: " << temporary; + << ", temporary: " << temporary << ", namespace: " << namespace_name; } bool Schema::is_vertex_label_temporary(label_t label) const { @@ -1454,10 +1458,14 @@ static Status parse_vertex_schema(YAML::Node node, Schema& schema) { std::vector property_types; std::vector property_names; std::string description; // default is empty string + std::string namespace_name = "default"; if (node["description"]) { description = node["description"].as(); } + if (node["namespace"]) { + namespace_name = node["namespace"].as(); + } if (node["nullable"]) { LOG(ERROR) << "nullable is not supported yet"; @@ -1526,7 +1534,8 @@ static Status parse_vertex_schema(YAML::Node node, Schema& schema) { } schema.AddVertexLabel(label_name, property_types, property_names, - primary_keys, max_num, description); + primary_keys, max_num, description, {}, false, + namespace_name); // check the type_id equals to storage's label_id int32_t type_id; if (!get_scalar(node, "type_id", type_id)) { @@ -1567,12 +1576,16 @@ static Status parse_edge_schema(YAML::Node node, Schema& schema) { std::vector property_types; std::vector prop_names; std::string description; // default is empty string + std::string namespace_name = "default"; RETURN_IF_NOT_OK(parse_edge_properties(node["properties"], edge_label_name, property_types, prop_names)); if (node["description"]) { description = node["description"].as(); } + if (node["namespace"]) { + namespace_name = node["namespace"].as(); + } if (node["nullable"]) { LOG(ERROR) << "nullable is not supported yet"; return Status(StatusCode::ERR_NOT_IMPLEMENTED, @@ -1754,7 +1767,8 @@ static Status parse_edge_schema(YAML::Node node, Schema& schema) { << " properties"; schema.AddEdgeLabel(src_label_name, dst_label_name, edge_label_name, property_types, prop_names, cur_oe, cur_ie, oe_mutable, - ie_mutable, sort_key_for_nbr, description); + ie_mutable, sort_key_for_nbr, description, {}, false, + namespace_name); } // check the type_id equals to storage's label_id @@ -1850,6 +1864,7 @@ bool dump_vertices_schema(const Schema& schema, YAML::Node& node) { YAML::Node cur_node(YAML::NodeType::Map); cur_node["type_name"] = schema.get_vertex_label_name(v_label); cur_node["description"] = schema.get_vertex_description(v_label); + cur_node["namespace"] = schema.get_vertex_schema(v_label)->namespace_name; cur_node["type_id"] = std::to_string(v_label); cur_node["properties"] = YAML::Node(YAML::NodeType::Sequence); auto properties = schema.get_vertex_properties(v_label); @@ -1897,6 +1912,10 @@ bool dump_edges_schema(const Schema& schema, YAML::Node& node) { for (auto src_v : v_labels) { for (auto dst_v : v_labels) { if (schema.is_edge_triplet_valid(src_v, dst_v, e_label)) { + if (!cur_node["namespace"]) { + cur_node["namespace"] = + schema.get_edge_schema(src_v, dst_v, e_label)->namespace_name; + } if (!properties_set) { auto properties = schema.get_edge_properties(src_v, dst_v, e_label); auto property_names = @@ -2597,7 +2616,8 @@ InArchive& operator<<(InArchive& archive, const VertexSchema& v_schema) { archive << v_schema.label_name << v_schema.property_types << v_schema.property_names << v_schema.primary_keys << v_schema.default_property_values << v_schema.description - << v_schema.max_num << v_schema.vprop_soft_deleted; + << v_schema.max_num << v_schema.vprop_soft_deleted + << v_schema.namespace_name; return archive; } @@ -2605,7 +2625,8 @@ OutArchive& operator>>(OutArchive& archive, VertexSchema& v_schema) { archive >> v_schema.label_name >> v_schema.property_types >> v_schema.property_names >> v_schema.primary_keys >> v_schema.default_property_values >> v_schema.description >> - v_schema.max_num >> v_schema.vprop_soft_deleted; + v_schema.max_num >> v_schema.vprop_soft_deleted >> + v_schema.namespace_name; return archive; } @@ -2615,7 +2636,7 @@ InArchive& operator<<(InArchive& archive, const EdgeSchema& e_schema) { << e_schema.ie_mutable << e_schema.oe_mutable << e_schema.ie_strategy << e_schema.oe_strategy << e_schema.properties << e_schema.property_names << e_schema.default_property_values - << e_schema.eprop_soft_deleted; + << e_schema.eprop_soft_deleted << e_schema.namespace_name; if (e_schema.sort_key_for_nbr.has_value()) { archive << static_cast(1) << e_schema.sort_key_for_nbr.value(); } else { @@ -2629,7 +2650,8 @@ OutArchive& operator>>(OutArchive& archive, EdgeSchema& e_schema) { e_schema.edge_label_name >> e_schema.description >> e_schema.ie_mutable >> e_schema.oe_mutable >> e_schema.ie_strategy >> e_schema.oe_strategy >> e_schema.properties >> e_schema.property_names >> - e_schema.default_property_values >> e_schema.eprop_soft_deleted; + e_schema.default_property_values >> e_schema.eprop_soft_deleted >> + e_schema.namespace_name; uint8_t has_sort_key_for_nbr; archive >> has_sort_key_for_nbr; if (has_sort_key_for_nbr) { diff --git a/src/storages/graph/schema_view.cc b/src/storages/graph/schema_view.cc new file mode 100644 index 000000000..37a907871 --- /dev/null +++ b/src/storages/graph/schema_view.cc @@ -0,0 +1,202 @@ +/** Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "neug/storages/graph/schema_view.h" + +#include + +#include "neug/utils/exception/exception.h" + +namespace neug { + +SchemaView::SchemaView(const Schema* schema, std::string namespace_name) + : schema_(schema), namespace_(std::move(namespace_name)) { + EnsureSchema(); +} + +SchemaView::SchemaView(const Schema& schema, std::string namespace_name) + : SchemaView(&schema, std::move(namespace_name)) {} + +const Schema& SchemaView::GetSchema() const { + EnsureSchema(); + return *schema_; +} + +std::vector> SchemaView::GetVertexSchemas() + const { + EnsureSchema(); + std::vector> result; + for (const auto& vertex_schema : schema_->get_all_vertex_schemas()) { + if (vertex_schema != nullptr && + schema_->is_vertex_label_valid(vertex_schema->label_id) && + IsVertexInNamespace(*vertex_schema)) { + result.emplace_back(vertex_schema); + } + } + return result; +} + +std::vector> SchemaView::GetEdgeSchemas() + const { + EnsureSchema(); + std::vector> result; + for (const auto& [_, edge_schema] : schema_->get_all_edge_schemas()) { + if (edge_schema != nullptr && + schema_->is_edge_triplet_valid(edge_schema->src_label_id, + edge_schema->dst_label_id, + edge_schema->edge_label_id) && + IsEdgeInNamespace(*edge_schema)) { + result.emplace_back(edge_schema); + } + } + return result; +} + +result> SchemaView::GetVertexSchema( + label_t label) const { + if (!ContainsVertexLabel(label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Vertex label " + std::to_string(label) + + " not found in namespace " + namespace_ + "."); + } + return schema_->get_vertex_schema(label); +} + +result> SchemaView::GetVertexSchema( + const std::string& label) const { + if (!ContainsVertexLabel(label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Vertex label " + label + " not found in namespace " + + namespace_ + "."); + } + return schema_->get_vertex_schema(schema_->get_vertex_label_id(label)); +} + +result> SchemaView::GetEdgeSchema( + label_t src_label, label_t dst_label, label_t edge_label) const { + if (!ContainsEdgeTriplet(src_label, dst_label, edge_label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Edge triplet (" + std::to_string(src_label) + ", " + + std::to_string(dst_label) + ", " + + std::to_string(edge_label) + + ") not found in namespace " + namespace_ + "."); + } + return schema_->get_edge_schema(src_label, dst_label, edge_label); +} + +result> SchemaView::GetEdgeSchema( + const std::string& src_label, const std::string& dst_label, + const std::string& edge_label) const { + if (!ContainsEdgeTriplet(src_label, dst_label, edge_label)) { + RETURN_STATUS_ERROR(StatusCode::ERR_NOT_FOUND, + "Edge triplet (" + src_label + ", " + dst_label + ", " + + edge_label + ") not found in namespace " + + namespace_ + "."); + } + return schema_->get_edge_schema(schema_->get_vertex_label_id(src_label), + schema_->get_vertex_label_id(dst_label), + schema_->get_edge_label_id(edge_label)); +} + +bool SchemaView::ContainsVertexLabel(label_t label) const { + EnsureSchema(); + return schema_->is_vertex_label_valid(label) && IsVertexInNamespace(label); +} + +bool SchemaView::ContainsVertexLabel(const std::string& label) const { + EnsureSchema(); + return schema_->is_vertex_label_valid(label) && + IsVertexInNamespace(schema_->get_vertex_label_id(label)); +} + +bool SchemaView::ContainsEdgeLabel(label_t label) const { + EnsureSchema(); + if (!schema_->is_edge_label_valid(label)) { + return false; + } + for (const auto& edge_schema : GetEdgeSchemas()) { + if (edge_schema->edge_label_id == label) { + return true; + } + } + return false; +} + +bool SchemaView::ContainsEdgeLabel(const std::string& label) const { + EnsureSchema(); + return schema_->is_edge_label_valid(label) && + ContainsEdgeLabel(schema_->get_edge_label_id(label)); +} + +bool SchemaView::ContainsEdgeTriplet(label_t src_label, label_t dst_label, + label_t edge_label) const { + EnsureSchema(); + if (!schema_->is_edge_triplet_valid(src_label, dst_label, edge_label)) { + return false; + } + return IsEdgeInNamespace( + *schema_->get_edge_schema(src_label, dst_label, edge_label)); +} + +bool SchemaView::ContainsEdgeTriplet(const std::string& src_label, + const std::string& dst_label, + const std::string& edge_label) const { + EnsureSchema(); + if (!schema_->is_edge_triplet_valid(src_label, dst_label, edge_label)) { + return false; + } + return ContainsEdgeTriplet(schema_->get_vertex_label_id(src_label), + schema_->get_vertex_label_id(dst_label), + schema_->get_edge_label_id(edge_label)); +} + +std::vector SchemaView::GetVertexLabelIds() const { + std::vector result; + for (const auto& vertex_schema : GetVertexSchemas()) { + result.emplace_back(vertex_schema->label_id); + } + return result; +} + +std::vector SchemaView::GetEdgeLabelIds() const { + std::vector result; + std::unordered_set seen; + for (const auto& edge_schema : GetEdgeSchemas()) { + if (seen.emplace(edge_schema->edge_label_id).second) { + result.emplace_back(edge_schema->edge_label_id); + } + } + return result; +} + +void SchemaView::EnsureSchema() const { + if (schema_ == nullptr) { + THROW_INVALID_ARGUMENT_EXCEPTION("Schema is null"); + } +} + +bool SchemaView::IsVertexInNamespace(label_t label) const { + return IsVertexInNamespace(*schema_->get_vertex_schema(label)); +} + +bool SchemaView::IsVertexInNamespace(const VertexSchema& schema) const { + return schema.namespace_name == namespace_; +} + +bool SchemaView::IsEdgeInNamespace(const EdgeSchema& schema) const { + return schema.namespace_name == namespace_; +} + +} // namespace neug diff --git a/tests/storage/CMakeLists.txt b/tests/storage/CMakeLists.txt index 7f3758525..6f9a92a10 100644 --- a/tests/storage/CMakeLists.txt +++ b/tests/storage/CMakeLists.txt @@ -21,6 +21,8 @@ add_neug_test(edge_table_test test_edge_table.cc) add_neug_test(graph_view_test test_graph_view.cc) +add_neug_test(schema_view_test test_schema_view.cc) + add_neug_test(graph_snapshot_store_test test_graph_snapshot_store_concurrency.cc) add_neug_test(temporary_graph_test test_temporary_graph.cc) diff --git a/tests/storage/test_schema_view.cc b/tests/storage/test_schema_view.cc new file mode 100644 index 000000000..4981e2359 --- /dev/null +++ b/tests/storage/test_schema_view.cc @@ -0,0 +1,297 @@ +/** Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "neug/compiler/planner/gopt_planner.h" +#include "neug/storages/allocators.h" +#include "neug/storages/checkpoint_manager.h" +#include "neug/storages/graph/graph_interface.h" +#include "neug/storages/graph/graph_view.h" +#include "neug/storages/graph/operation_params.h" +#include "neug/storages/graph/property_graph.h" +#include "neug/storages/graph/schema_view.h" +#include "unittest/utils.h" + +#ifdef BUILD_HTTP_SERVER +#include "neug/neug.h" +#include "neug/server/neug_db_service.h" +#include "neug/transaction/update_transaction.h" +#endif + +namespace neug { + +class SchemaViewTest : public ::testing::Test { + protected: + void SetUp() override { + work_dir_ = std::string("/tmp/test_schema_view_") + + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::filesystem::remove_all(work_dir_); + std::filesystem::create_directories(work_dir_); + + checkpoint_manager_.Open(work_dir_); + graph_ = std::make_unique(); + graph_->Open(make_checkpoint(checkpoint_manager_), MemoryLevel::kInMemory); + graph_view_ = std::make_unique(*graph_); + allocator_ = std::make_unique(MemoryLevel::kInMemory, work_dir_); + graph_interface_ = std::make_unique( + *graph_, *graph_view_, 0, *allocator_); + } + + void TearDown() override { + graph_interface_.reset(); + graph_view_.reset(); + graph_.reset(); + allocator_.reset(); + std::filesystem::remove_all(work_dir_); + } + + void CreateVertex(const std::string& label, + const std::string& namespace_name) { + CreateVertexTypeParamBuilder builder; + ASSERT_TRUE(graph_interface_ + ->CreateVertexType(builder.VertexLabel(label) + .AddProperty("id", Value::INT64(0)) + .AddPrimaryKeyName("id") + .Namespace(namespace_name) + .Build()) + .ok()); + } + + void CreateSelfEdge(const std::string& vertex_label, + const std::string& edge_label, + const std::string& namespace_name) { + CreateEdgeTypeParamBuilder builder; + ASSERT_TRUE(graph_interface_ + ->CreateEdgeType(builder.SrcLabel(vertex_label) + .DstLabel(vertex_label) + .EdgeLabel(edge_label) + .Namespace(namespace_name) + .Build()) + .ok()); + } + + std::string work_dir_; + CheckpointManager checkpoint_manager_; + std::unique_ptr graph_; + std::unique_ptr graph_view_; + std::unique_ptr allocator_; + std::unique_ptr graph_interface_; +}; + +TEST_F(SchemaViewTest, IsolatesVertexAndEdgeTypesByNamespace) { + CreateVertex("DefaultPerson", "default"); + CreateSelfEdge("DefaultPerson", "DefaultKnows", "default"); + CreateVertex("FtsDocument", "fts"); + CreateSelfEdge("FtsDocument", "FtsReferences", "fts"); + + const auto& schema = graph_->schema(); + SchemaView default_view(schema, "default"); + SchemaView fts_view(schema, "fts"); + + const auto default_vertex_id = schema.get_vertex_label_id("DefaultPerson"); + const auto fts_vertex_id = schema.get_vertex_label_id("FtsDocument"); + const auto default_edge_id = schema.get_edge_label_id("DefaultKnows"); + const auto fts_edge_id = schema.get_edge_label_id("FtsReferences"); + + EXPECT_TRUE(default_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_TRUE(default_view.ContainsEdgeLabel("DefaultKnows")); + EXPECT_TRUE(default_view.ContainsEdgeTriplet("DefaultPerson", "DefaultPerson", + "DefaultKnows")); + EXPECT_FALSE(default_view.ContainsVertexLabel("FtsDocument")); + EXPECT_FALSE(default_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_FALSE(default_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + + auto default_vertex_by_id = default_view.GetVertexSchema(default_vertex_id); + ASSERT_TRUE(default_vertex_by_id); + EXPECT_EQ(default_vertex_by_id.value()->label_name, "DefaultPerson"); + auto default_vertex_by_name = default_view.GetVertexSchema("DefaultPerson"); + ASSERT_TRUE(default_vertex_by_name); + EXPECT_EQ(default_vertex_by_name.value()->label_id, default_vertex_id); + auto default_edge_by_id = default_view.GetEdgeSchema( + default_vertex_id, default_vertex_id, default_edge_id); + ASSERT_TRUE(default_edge_by_id); + EXPECT_EQ(default_edge_by_id.value()->edge_label_name, "DefaultKnows"); + auto default_edge_by_name = default_view.GetEdgeSchema( + "DefaultPerson", "DefaultPerson", "DefaultKnows"); + ASSERT_TRUE(default_edge_by_name); + EXPECT_EQ(default_edge_by_name.value()->edge_label_id, default_edge_id); + + auto hidden_vertex_by_id = default_view.GetVertexSchema(fts_vertex_id); + ASSERT_FALSE(hidden_vertex_by_id); + EXPECT_EQ(hidden_vertex_by_id.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_vertex_by_name = default_view.GetVertexSchema("FtsDocument"); + ASSERT_FALSE(hidden_vertex_by_name); + EXPECT_EQ(hidden_vertex_by_name.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_edge_by_id = + default_view.GetEdgeSchema(fts_vertex_id, fts_vertex_id, fts_edge_id); + ASSERT_FALSE(hidden_edge_by_id); + EXPECT_EQ(hidden_edge_by_id.error().error_code(), StatusCode::ERR_NOT_FOUND); + auto hidden_edge_by_name = + default_view.GetEdgeSchema("FtsDocument", "FtsDocument", "FtsReferences"); + ASSERT_FALSE(hidden_edge_by_name); + EXPECT_EQ(hidden_edge_by_name.error().error_code(), + StatusCode::ERR_NOT_FOUND); + + EXPECT_TRUE(fts_view.ContainsVertexLabel("FtsDocument")); + EXPECT_TRUE(fts_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_TRUE(fts_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + EXPECT_FALSE(fts_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_FALSE(fts_view.ContainsEdgeLabel("DefaultKnows")); + EXPECT_FALSE(fts_view.ContainsEdgeTriplet("DefaultPerson", "DefaultPerson", + "DefaultKnows")); + EXPECT_FALSE(fts_view.GetVertexSchema(default_vertex_id)); + EXPECT_FALSE(fts_view.GetEdgeSchema(default_vertex_id, default_vertex_id, + default_edge_id)); + + ASSERT_EQ(default_view.GetVertexSchemas().size(), 1u); + EXPECT_EQ(default_view.GetVertexSchemas()[0]->label_name, "DefaultPerson"); + ASSERT_EQ(default_view.GetEdgeSchemas().size(), 1u); + EXPECT_EQ(default_view.GetEdgeSchemas()[0]->edge_label_name, "DefaultKnows"); + ASSERT_EQ(fts_view.GetVertexSchemas().size(), 1u); + EXPECT_EQ(fts_view.GetVertexSchemas()[0]->label_name, "FtsDocument"); + ASSERT_EQ(fts_view.GetEdgeSchemas().size(), 1u); + EXPECT_EQ(fts_view.GetEdgeSchemas()[0]->edge_label_name, "FtsReferences"); +} + +TEST_F(SchemaViewTest, GoptPlannerUsesDefaultNamespace) { + CreateVertex("DefaultPerson", "default"); + CreateVertex("FtsDocument", "fts"); + + GOptPlanner planner; + GraphStats stats; + auto default_result = planner.compilePlan("MATCH (n:DefaultPerson) RETURN n", + &graph_->schema(), stats); + EXPECT_TRUE(default_result.has_value()); + + auto fts_result = planner.compilePlan("MATCH (n:FtsDocument) RETURN n", + &graph_->schema(), stats); + EXPECT_FALSE(fts_result.has_value()); +} + +#ifdef BUILD_HTTP_SERVER +TEST(StorageTPSchemaViewTest, + StorageTPUpdateInterfaceIsolatesTypesByNamespaceAfterCommit) { + const auto work_dir = + std::string("/tmp/test_schema_view_") + + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::filesystem::remove_all(work_dir); + + NeugDB db; + NeugDBConfig config(work_dir); + config.memory_level = MemoryLevel::kInMemory; + ASSERT_TRUE(db.Open(config)); + auto service = std::make_shared(db); + + { + auto session = service->AcquireSession(); + auto txn = session->GetUpdateTransaction(); + StorageTPUpdateInterface graph_interface(txn); + + auto create_vertex = [&](const std::string& label, + const std::string& namespace_name) { + CreateVertexTypeParamBuilder builder; + return graph_interface.CreateVertexType( + builder.VertexLabel(label) + .AddProperty("id", Value::INT64(0)) + .AddPrimaryKeyName("id") + .Namespace(namespace_name) + .Build()); + }; + auto create_self_edge = [&](const std::string& vertex_label, + const std::string& edge_label, + const std::string& namespace_name) { + CreateEdgeTypeParamBuilder builder; + return graph_interface.CreateEdgeType(builder.SrcLabel(vertex_label) + .DstLabel(vertex_label) + .EdgeLabel(edge_label) + .Namespace(namespace_name) + .Build()); + }; + + ASSERT_TRUE(create_vertex("DefaultPerson", "default").ok()); + ASSERT_TRUE( + create_self_edge("DefaultPerson", "DefaultKnows", "default").ok()); + ASSERT_TRUE(create_vertex("FtsDocument", "fts").ok()); + ASSERT_TRUE(create_self_edge("FtsDocument", "FtsReferences", "fts").ok()); + + SchemaView default_view(txn.schema(), "default"); + SchemaView fts_view(txn.schema(), "fts"); + EXPECT_TRUE(default_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_TRUE(default_view.ContainsEdgeLabel("DefaultKnows")); + EXPECT_FALSE(default_view.ContainsVertexLabel("FtsDocument")); + EXPECT_FALSE(default_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_TRUE(fts_view.ContainsVertexLabel("FtsDocument")); + EXPECT_TRUE(fts_view.ContainsEdgeLabel("FtsReferences")); + EXPECT_FALSE(fts_view.ContainsVertexLabel("DefaultPerson")); + EXPECT_FALSE(fts_view.ContainsEdgeLabel("DefaultKnows")); + + ASSERT_TRUE(txn.Commit()); + } + + { + auto session = service->AcquireSession(); + auto txn = session->GetReadTransaction(); + const auto& schema = txn.schema(); + SchemaView default_view(schema, "default"); + SchemaView fts_view(schema, "fts"); + + const auto default_vertex_id = schema.get_vertex_label_id("DefaultPerson"); + const auto fts_vertex_id = schema.get_vertex_label_id("FtsDocument"); + const auto default_edge_id = schema.get_edge_label_id("DefaultKnows"); + const auto fts_edge_id = schema.get_edge_label_id("FtsReferences"); + + EXPECT_TRUE(default_view.ContainsEdgeTriplet( + "DefaultPerson", "DefaultPerson", "DefaultKnows")); + EXPECT_FALSE(default_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + auto hidden_fts_vertex = default_view.GetVertexSchema(fts_vertex_id); + ASSERT_FALSE(hidden_fts_vertex); + EXPECT_EQ(hidden_fts_vertex.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_fts_edge = + default_view.GetEdgeSchema(fts_vertex_id, fts_vertex_id, fts_edge_id); + ASSERT_FALSE(hidden_fts_edge); + EXPECT_EQ(hidden_fts_edge.error().error_code(), StatusCode::ERR_NOT_FOUND); + + EXPECT_TRUE(fts_view.ContainsEdgeTriplet("FtsDocument", "FtsDocument", + "FtsReferences")); + EXPECT_FALSE(fts_view.ContainsEdgeTriplet("DefaultPerson", "DefaultPerson", + "DefaultKnows")); + auto hidden_default_vertex = fts_view.GetVertexSchema(default_vertex_id); + ASSERT_FALSE(hidden_default_vertex); + EXPECT_EQ(hidden_default_vertex.error().error_code(), + StatusCode::ERR_NOT_FOUND); + auto hidden_default_edge = fts_view.GetEdgeSchema( + default_vertex_id, default_vertex_id, default_edge_id); + ASSERT_FALSE(hidden_default_edge); + EXPECT_EQ(hidden_default_edge.error().error_code(), + StatusCode::ERR_NOT_FOUND); + } + + db.Close(); + std::filesystem::remove_all(work_dir); +} +#endif + +} // namespace neug From 3dfc3d6037d224290b5202ea27203a8f68f55153 Mon Sep 17 00:00:00 2001 From: "yihe.zxl" Date: Mon, 13 Jul 2026 15:48:09 +0800 Subject: [PATCH 3/4] remove unused files --- specs/schema_view.md | 229 ------------------------------------------- 1 file changed, 229 deletions(-) delete mode 100644 specs/schema_view.md diff --git a/specs/schema_view.md b/specs/schema_view.md deleted file mode 100644 index 91313da98..000000000 --- a/specs/schema_view.md +++ /dev/null @@ -1,229 +0,0 @@ -# SchemaView 设计 - -## 什么是 SchemaView? - -`SchemaView` 提供了一层 Schema 逻辑视图,用于在同一个底层 Schema 存储中隔离不同逻辑 Schema 下的点边类型。 - -底层 Schema 负责统一管理所有点边类型,而 `SchemaView` 基于 namespace 对底层 Schema 进行过滤和访问控制,使不同逻辑 Schema 之间的点边类型相互隔离。 - -简单来说: - -- **Schema:** 管理所有实际存在的点边类型; -- **SchemaView:** 提供某个逻辑 Schema namespace 下的访问视图,只暴露属于该 namespace 的点边类型。 - ---- - -## 为什么需要 SchemaView? - -引入 `SchemaView` 的主要目的是在不改变现有 Schema 管理模型的情况下,逐步支持 NeuG 中不同场景下的 Schema 隔离需求。 - -目前 SchemaView 主要服务于以下两个场景: - -### 1. 支持内部功能创建隐藏点边类型 - -一些 NeuG 内部功能需要在数据库中创建辅助点边表,但这些点边类型不应该暴露给普通用户。 - -例如全文索引: - -- 全文索引可能需要在内部创建额外的点表、边表,用于存储索引相关的数据; -- 这些内部表需要复用 NeuG 的 Schema 和存储能力; -- 但用户不应该在正常图查询中看到或访问这些内部点边类型。 - -通过为内部功能分配独立的 namespace,并通过对应的 `SchemaView` 访问,可以实现: - -- 内部功能可以正常创建和访问自己的点边类型; -- 用户侧 SchemaView 不包含这些内部点边类型; -- 不同功能模块之间的 Schema 互相隔离。 - ---- - -### 2. 支持用户侧多图(Multi-Graph)功能 - -在内部功能场景验证 SchemaView 后,可以进一步开放给用户,用于支持多图能力。 - -用户可以显式创建多个 Schema,并在指定 Schema 下创建点边类型。不同 Schema 之间的点边类型相互隔离,用户可以基于指定 Schema 执行图查询。 - -例如: - -```cypher -CREATE SCHEMA s1; -CREATE SCHEMA s2; - -CREATE NODE TABLE s1.Person ( - id INT64, - name STRING, - PRIMARY KEY(id) -); - -CREATE NODE TABLE s2.Book ( - id INT64, - title STRING, - PRIMARY KEY(id) -); - -MATCH (n: s1.Person) -WHERE n.name = 'XX' -RETURN count(n); - -MATCH(n: s2.Book) -WHERE n.title <> 'XX' -RETURN count(n); -``` - -## SchemaView 设计 - -当前方案倾向于在 Schema 层之上提供一层 `SchemaView` 抽象,通过 namespace 对 Schema 进行逻辑隔离。`SchemaView` 表示某个特定 namespace 下的 Schema 视图,所有 Schema 查询操作均通过该 View 完成。通过在 View 层过滤非当前 namespace 的点边类型,实现不同 namespace 之间的 Schema 隔离。 - - -`SchemaView` 内部维护: - -- 底层 `Schema` 对象的只读引用; -- 当前 View 所属的 namespace。 - -底层 `Schema` 仍然统一存储所有 namespace 下的点边类型,并按照 label 提供统一的 Schema 管理能力。namespace 隔离逻辑由 `SchemaView` 负责,而不是由底层 `Schema` 实现。 - - -当前设计**暂不支持不同 namespace 使用相同的 label name**: - -- 主要原因是底层 Schema 当前仍然基于 label name 作为点边类型的唯一标识进行存储和管理。因此,不同 namespace 下的 label name 需要由用户保证全局唯一。 - -- 如果未来需要支持 namespace 内 label 重名,需要调整底层 Schema 存储结构,将 `(namespace, label)` 作为点边类型的唯一标识。 - -## 实现 - -### 接口设计 - -`SchemaView` 对外提供与 `Schema` 类似的查询接口,但所有查询结果均限定在当前 namespace 范围内,例如: - -- `GetVertexSchemas` / `GetEdgeSchemas`: 返回当前 namespace 下的点边 Schema; -- `GetVertexSchema` / `GetEdgeSchema`: 仅返回属于当前 namespace 的指定 label Schema; -- `ContainsXXX`: 判断指定 label 是否属于当前 namespace。 - -所有 Get 类接口内部通过 `EnsureSchema` 或 namespace 校验逻辑,保证不会返回其他 namespace 下的 Schema。 - -```c++ - class SchemaView { - public: - SchemaView(const Schema* schema, std::string namespace_name); - SchemaView(const Schema& schema, std::string namespace_name); - - const Schema& GetSchema() const; - const std::string& GetNamespace() const; - - // Get 接口内部调用 EnsureSchema 保证只返回当前 namespace 内的点边 - std::vector> GetVertexSchemas() const; - std::vector> GetEdgeSchemas() const; - - result> GetVertexSchema(label_t label) const; - result> GetVertexSchema( - const std::string& label) const; - - result> GetEdgeSchema( - label_t src_label, label_t dst_label, label_t edge_label) const; - result> GetEdgeSchema( - const std::string& src_label, - const std::string& dst_label, - const std::string& edge_label) const; - - // 内部调用 IsVertexInNamespace,确保当前 label 在 namespace 内 - bool ContainsVertexLabel(label_t label) const; - bool ContainsVertexLabel(const std::string& label) const; - - bool ContainsEdgeLabel(label_t label) const; - bool ContainsEdgeLabel(const std::string& label) const; - - bool ContainsEdgeTriplet( - label_t src_label, label_t dst_label, label_t edge_label) const; - bool ContainsEdgeTriplet( - const std::string& src_label, - const std::string& dst_label, - const std::string& edge_label) const; - - std::vector GetVertexLabelIds() const; - std::vector GetEdgeLabelIds() const; - - private: - void EnsureSchema() const; - - bool IsVertexInNamespace(label_t label) const; - bool IsVertexInNamespace(const VertexSchema& schema) const; - bool IsEdgeInNamespace(const EdgeSchema& schema) const; - - const Schema* schema_; - std::string namespace_; - }; -``` - -### 其他接口改动 - -我们进一步修改 Schema `AddVertexLabel/AddEdgeLabel` 接口: -- 用于在创建点边类型时指定所在 namespace; -- 不显示指定 namespace 默认为 `"default"`,代表默认 namespace 类型; - -```c++ -class Schema { -public: - void AddVertexLabel( - const std::string& label, const std::vector& property_types, - const std::vector& property_names, - const std::vector>& primary_key, - size_t max_vnum = static_cast(1) << 32, - const std::string& description = "", - const std::vector& default_property_values = {}, - bool temporary = false, - string namespace = "default"); - - void AddEdgeLabel( - const std::string& src_label, const std::string& dst_label, - const std::string& edge_label, - const std::vector& properties, - const std::vector& prop_names, - EdgeStrategy oe = EdgeStrategy::kMultiple, - EdgeStrategy ie = EdgeStrategy::kMultiple, - bool oe_mutable = true, bool ie_mutable = true, - std::optional sort_key_for_nbr = std::nullopt, - const std::string& description = "", - const std::vector& default_property_values = {}, - bool temporary = false, - string namespace = "default"); -}; -``` - - -`GraphInterface` 也需要透传 namespace 参数: -- 我们直接在 `CreateVertexTypeParam/CreateEdgeTypeParam` 增加 namespace 参数; -- 在全文索引调用 GraphInterface 创建点边类型时,需要显示设置 `namespace = fts`; - -```c++ -class StorageUpdateInterface{ -public: - virtual Status CreateVertexType(const CreateVertexTypeParam& config) = 0; - virtual Status CreateEdgeType(const CreateEdgeTypeParam& config) = 0; -} -``` - -```c++ -class CreateVertexTypeParam { - private: - std::string vertex_label_name; - std::vector> properties; - std::vector primary_key_names; - bool temporary = false; - std::string namespace = "default"; -}; -``` - -```c++ -class CreateEdgeTypeParam { - private: - std::string src_label_name; - std::string dst_label_name; - std::string edge_label_name; - std::vector> properties; - EdgeStrategy oe_edge_strategy; - EdgeStrategy ie_edge_strategy; - std::optional sort_key_for_nbr; - bool temporary = false; - std::string namespace = "default"; -}; -``` From 4779614602dffe1c36284eac0f7e8fafdb818a58 Mon Sep 17 00:00:00 2001 From: "yihe.zxl" Date: Mon, 13 Jul 2026 16:37:26 +0800 Subject: [PATCH 4/4] fix: hide edges with deleted endpoint labels --- src/storages/graph/schema_view.cc | 12 +++++++++--- tests/storage/test_schema_view.cc | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/storages/graph/schema_view.cc b/src/storages/graph/schema_view.cc index 37a907871..4af8a00d2 100644 --- a/src/storages/graph/schema_view.cc +++ b/src/storages/graph/schema_view.cc @@ -54,6 +54,8 @@ std::vector> SchemaView::GetEdgeSchemas() std::vector> result; for (const auto& [_, edge_schema] : schema_->get_all_edge_schemas()) { if (edge_schema != nullptr && + schema_->is_vertex_label_valid(edge_schema->src_label_id) && + schema_->is_vertex_label_valid(edge_schema->dst_label_id) && schema_->is_edge_triplet_valid(edge_schema->src_label_id, edge_schema->dst_label_id, edge_schema->edge_label_id) && @@ -143,11 +145,15 @@ bool SchemaView::ContainsEdgeLabel(const std::string& label) const { bool SchemaView::ContainsEdgeTriplet(label_t src_label, label_t dst_label, label_t edge_label) const { EnsureSchema(); - if (!schema_->is_edge_triplet_valid(src_label, dst_label, edge_label)) { + if (!schema_->is_vertex_label_valid(src_label) || + !schema_->is_vertex_label_valid(dst_label) || + !schema_->is_edge_label_valid(edge_label) || + !schema_->is_edge_triplet_valid(src_label, dst_label, edge_label)) { return false; } - return IsEdgeInNamespace( - *schema_->get_edge_schema(src_label, dst_label, edge_label)); + return IsVertexInNamespace(src_label) && IsVertexInNamespace(dst_label) && + IsEdgeInNamespace( + *schema_->get_edge_schema(src_label, dst_label, edge_label)); } bool SchemaView::ContainsEdgeTriplet(const std::string& src_label, diff --git a/tests/storage/test_schema_view.cc b/tests/storage/test_schema_view.cc index 4981e2359..29a0a18eb 100644 --- a/tests/storage/test_schema_view.cc +++ b/tests/storage/test_schema_view.cc @@ -189,6 +189,25 @@ TEST_F(SchemaViewTest, GoptPlannerUsesDefaultNamespace) { EXPECT_FALSE(fts_result.has_value()); } +TEST_F(SchemaViewTest, HidesEdgesWhoseEndpointWasDeleted) { + CreateVertex("DefaultPerson", "default"); + CreateSelfEdge("DefaultPerson", "DefaultKnows", "default"); + + const auto vertex_id = graph_->schema().get_vertex_label_id("DefaultPerson"); + const auto edge_id = graph_->schema().get_edge_label_id("DefaultKnows"); + ASSERT_TRUE(graph_interface_->DeleteVertexType("DefaultPerson").ok()); + + SchemaView view(graph_->schema(), "default"); + EXPECT_TRUE(view.GetVertexSchemas().empty()); + EXPECT_TRUE(view.GetEdgeSchemas().empty()); + EXPECT_FALSE(view.ContainsEdgeLabel(edge_id)); + EXPECT_FALSE(view.ContainsEdgeTriplet(vertex_id, vertex_id, edge_id)); + + auto edge_schema = view.GetEdgeSchema(vertex_id, vertex_id, edge_id); + ASSERT_FALSE(edge_schema); + EXPECT_EQ(edge_schema.error().error_code(), StatusCode::ERR_NOT_FOUND); +} + #ifdef BUILD_HTTP_SERVER TEST(StorageTPSchemaViewTest, StorageTPUpdateInterfaceIsolatesTypesByNamespaceAfterCommit) {