Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable MULTIUPDATE and LOOKUP | UPDATE #5953

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

JackChuengQAQ
Copy link

What type of PR is this?

  • bug
  • feature
  • enhancement

What problem(s) does this PR solve?

Issue(s) number:

#5934

Description:

  • Added MULTIUPDATE syntax for batch processing.
  • Introduced LOOKUP | UPDATE syntax for seamless query and update operations.

批量更新语句 (MULTIUPDATE)

在原生 Nebula 中,UPDATE 语句的操作限制为一次仅更新一个节点或一条边。为实现 LOOKUP | UPDATE 语句的功能,首先必须扩展 UPDATE 的能力,以支持批量更新操作。因此,新定义了 MULTIUPDATE 语句,以支持批量更新操作。这一新增功能允许同时更新多个节点或边,并为复杂查询和更新需求提供了基本支持。

批量节点更新

MULTIUPDATE VERTEX 语句可以更新点,相较于原生 UPDATE 语句,可以一次更新一个点或者多个点。

  • 语法

批量节点更新的语法如下:

MULTIUPDATE VERTEX ON <tag_name> <vid> [ , <vid> ... ]
SET <update_prop>
[WHEN <condition>]
[YIELD <output>]
  • 示例
// 查看年龄为 26 的球员
[nba]> lookup on player where player.age==26 yield id(vertex), player.age
+-------------------+------------+
| id(VERTEX)        | player.age |
+-------------------+------------+
| "Carmelo Anthony" | 26         |
| "Cory Joseph"     | 26         |
+-------------------+------------+

// 同时将两位球员的年龄更新 age=age-1
[nba]> multiupdate vertex on player "Carmelo Anthony", "Cory Joseph" \
       SET age=age-1 yield name, age
+-------------------+-----+
| name              | age |
+-------------------+-----+
| "Carmelo Anthony" | 25  |
| "Cory Joseph"     | 25  |
+-------------------+-----+

批量边更新

MULTIUPDATE EDGE 语句可以更新边,相较于原生 UPDATE 语句,可以一次更新一条边或者多个边。

  • 语法

批量边更新的语法如下

MULTIUPDATE EDGE ON <edge_type> 
<src_vid> -> <dst_vid>[@<rank>] [, <src_vid> -> <dst_vid>[@<rank>] ...]
SET <update_prop>
[WHEN <condition>]
[YIELD <output>]
  • 示例
// 查看 likeness 超过 100 的边
[nba]> lookup on like where like.likeness > 100 yield edge as e
+---------------------------------------------------------------+
| e                                                             |
+---------------------------------------------------------------+
| [:like "Tim Duncan"->"Manu Ginobili" @0 {likeness: 104}]      |
| [:like "Shaquille O'Neal"->"JaVale McGee" @0 {likeness: 101}] |
+---------------------------------------------------------------+

// 同时将两条边的 likeness 设置为 100
[nba]> multiupdate edge on like \
       "Tim Duncan" -> "Manu Ginobili", "Shaquille O'Neal"->"JaVale McGee" \  
       SET likeness=100 YIELD likeness
+----------+
| likeness |
+----------+
| 100      |
| 100      |
+----------+

查询 & 更新语句 (LOOKUP | UPDATE)

查询 & 更新点

为了更新满足特定条件的部分数据的需求,设计了两种语句:一种是使用管道符,连接 LOOKUP 和 UPDATE 语句;另一种则是直接使用 LOOKUP | UPDATE 语句,一次性实现查询 & 更新的需求。

使用管道符

MULTIUPDATE VERTEX 语句可以结合管道符使用,将 LOOKUP 语句的查询结果传递给 MULTIUPDATE VERTEX 语句,实现查询 & 更新的同时进行。

  • 示例
[nba]> lookup on player where player.age==25 yield id(vertex), player.age
+-------------------+------------+
| id(VERTEX)        | player.age |
+-------------------+------------+
| "Carmelo Anthony" | 25         |
| "Cory Joseph"     | 25         |
+-------------------+------------+

// 通过管道符对 tag 属性进行更新,令 age=26
[nba]> lookup on player where player.age==25 yield id(vertex) as id \
       | multiupdate vertex on player $-.id set age=26 yield age
+-----+
| age |
+-----+
| 26  |
| 26  |
+-----+

使用 LOOKUP | UPDATE 语句

为了使得用户能够更加直接地使用 “查询 + 更新”的功能,避免对于某些语句的重复输入。设计了 LOOKUP | UPDATE 语句,能够直接对 WHERE 子句的条件进行查询,并对查询得到的节点进行更新。

  • 语法
LOOKUP | UPDATE VERTEX ON <tag_name> 
[WHERE <expression> [AND <expression> ...]]
SET <update_prop>
[WHEN <condition>]
[YIELD <output>]

该语法相当于首先执行 LOOKUP 语句,然后执行 MULTIUPDATE 语句,并通过管道符进行连接。即等价于下面的查询语句。

LOOKUP ON <tag_name> 
[WHERE <expression> [AND <expression> ...]]
YIELD id(vertex) AS id
| MULTIUPDATE VERTEX ON $-.id
SET <update_prop>
[WHEN <condition>]
[YIELD <output>]
  • 示例
[nba]> lookup on player where player.age==26 yield id(vertex), player.age
+-------------------+------------+
| id(VERTEX)        | player.age |
+-------------------+------------+
| "Carmelo Anthony" | 26         |
| "Cory Joseph"     | 26         |
+-------------------+------------+

[nba]> lookup | update vertex on player where player.age==26 \
       set age=age+1 yield age
+-----+
| age |
+-----+
| 27  |
| 27  |
+-----+

通过对比可以看出,采用 LOOKUP | UPDATE 的语句相比使用管道符的方式更加简洁明了,便于用户操作,进而优化了用户的操作体验。

查询 & 更新边

类似地,对于更新边,提供了管道符的方式以及 LOOKUP | UPDATE 的语句 的两种方式。

使用管道符

MULTIUPDATE VERTEX 语句可以结合管道符使用,将 LOOKUP 语句的查询结果传递给 MULTIUPDATE VERTEX 语句,实现查询 & 更新的同时进行。

  • 示例
// 查看 likeness=99 的边
[nba]> lookup on like where like.likeness == 99 \
       yield src(edge), dst(edge), like.likeness
+--------------+-----------------+---------------+
| src(EDGE)    | dst(EDGE)       | like.likeness |
+--------------+-----------------+---------------+
| "Tim Duncan" | "Manu Ginobili" | 99            |
| "Tim Duncan" | "Tony Parker"   | 99            |
+--------------+-----------------+---------------+

// 将 likeness=99 的边的 likness 更新 50
[nba]> lookup on like where like.likeness == 99 \
       yield src(edge) as src, dst(edge) as dst \
       | multiupdate edge on like $-.src -> $-.dst \
         SET likeness=50 yield likeness
+----------+
| likeness |
+----------+
| 50       |
| 50       |
+----------+

使用 LOOKUP | UPDATE 语句

为了使得用户能够更加直接地使用 “查询 + 更新”的功能,避免对于某些语句的重复输入。LOOKUP | UPDATE 语句同样适用于边更新,能够直接对 WHERE 子句的条件进行查询,并对查询得到的节点进行更新。

  • 语法
LOOKUP | UPDATE EDGE ON <edgetype> 
[WHERE <expression> [AND <expression> ...]]
SET <update_prop>
[WHEN <condition>]
[YIELD <output>]

该语法相当于首先执行 LOOKUP 语句,然后执行 MULTIUPDATE 语句,并通过管道符进行连接。即等价于下面的,使用管道符连接的查询语句。

LOOKUP ON <edgetype> 
[WHERE <expression> [AND <expression> ...]]
YIELD src(edge) AS src, dst(edge) AS dst, rank(edge) AS rank,
| MULTIUPDATE EDGE ON $-.src -> $-.dst @ $-.rank
SET <update_prop>
[WHEN <condition>]
[YIELD <output>]
  • 示例
[nba]> lookup on like where like.likeness == 50 \
       yield src(edge), dst(edge), like.likeness
+-------------------+-----------------+---------------+
| src(EDGE)         | dst(EDGE)       | like.likeness |
+-------------------+-----------------+---------------+
| "Tim Duncan"      | "Manu Ginobili" | 50            |
| "Tim Duncan"      | "Tony Parker"   | 50            |
| "Marco Belinelli" | "Tony Parker"   | 50            |
+-------------------+-----------------+---------------+

[nba]> lookup | update edge on like where like.likeness==50 \
       set likeness=likeness-1 yield likeness
+----------+
| likeness |
+----------+
| 49       |
| 49       |
| 49       |
+----------+

通过对比可以看出,相比使用管道符, 采用 LOOKUP | UPDATE 的语句更加简洁明了,尤其在更新边的场景下,需要从 LOOKUP 语句传递给 UPDATE 的字段名有 src, dst 和 rank。LOOKUP | UPDATE 的语句不需要用户输入这些字段名,使得用户的操作相对便捷。

How do you solve it?

技术实现主要分为以下三个部分:

  • UpdateMulti- 算子实现
  • UpdateRef- 算子实现
  • LOOKUP | UPDATE 的语句改写。

对于新增算子,根据 Nebula 数据库的架构,Parser->Validator->Planner->Executor,新增算子至少需要在四个模块中增加对应的代码。

UpdateMulti- 算子实现

UpdateMulti- 算子包括 UpdateMultiVertex 算子和 UpdateMultiEdge 算子,分别对应批量节点更新和批量边更新。通过增加 UpdateMulti- 算子,实现了节点属性和边属性的批量更新。

Parser

在定义了新关键字 KW_MULTIUPDATE ("multiupdate") 后,update_multi_vertex_sentence语句和 update_multi_edge_sentence语句定义如下。

  • update_multi_vertex_sentence语句
update_multi_vertex_sentence 
    : KW_MULTIUPDATE KW_VERTEX KW_ON name_label vid_list 
      KW_SET update_list when_clause yield_clause {
        auto sentence = new UpdateMultiVertexSentence($5, $4, $7, $8, $9);
        $$ = sentence;
    }
    ;
  • update_multi_edge_sentence语句
update_multi_edge_sentence
    : KW_MULTIUPDATE KW_EDGE KW_ON name_label edge_keys 
      KW_SET update_list when_clause yield_clause {
        auto sentence = new UpdateMultiEdgeSentence($5, $4, $7, $8, $9);
        $$ = sentence; 
    }
    ;

相比于原生 UPDATE 语句,MULTIUPDATE VERTEX 语句将vid替换为 vid_lists; MULTIUPDATE EDGE 语句将 vid R_ARROW vidvid R_ARROW vid AT rank 等多种表示一条边的方式,替换为可以包含多个边的edge_keys

在 Mutate Sentence 中,新增 UpdateMultiVertexSentence 类和 UpdateMultiEdgeSentence 类,对相应的输入信息进行存储,使用 Kind::kMultiVertex 和 Kind::kMultiEdge 进行标识,并在下一阶段进行使用。

Validator

Validator 类主要由三个函数组成。首先,构造函数接受得到的 Sentence,对 Validator 类进行初始化;其次,调用 Validator 类的 ValidateImpl函数,对语句内容进行校验;最后,调用 toPlan函数,构造查询节点和对应的查询计划。对应地,在 MutateValidator 中新增了 UpdateMultiVertexValidator 类和 UpdateMultiEdgeValidator 类。

相比于原生的 Update 语句,UpdateMulti-算子对应的 Validator 需要对节点(边)列表中的每一个节点(边)进行校验。

  • UpdateMultiVertexValidator::validateImpl() 节点校验部分

移植原生的校验流程,对 vidList 中的每一个 vid 进行校验。

for (auto &vid : sentence->getVertices()->vidList()) {
    auto idRet = SchemaUtil::toVertexID(vid, vidType_);
    if (!idRet.ok()) {
        LOG(ERROR) << idRet.status();
        return std::move(idRet).status();
    }
    vertices_.emplace_back(std::move(idRet).value());
}
  • UpdateMultiEdgeValidator::validateImpl() 边校验部分

在原生的 UpdateEdgeValidator中,srcid,dstid,rank 作为直接的成员变量。由于 UpdateEdgeValidator 需要一次性处理多条边,使用 EdgeKeys() 对边信息进行管理。为了提高代码的可读性,新建了 EdgeId 结构体,用于存储每条边的srcid,dstid,rank信息。

for (auto &edgekey : sentence->getEdgeKeys()->keys()) {
    auto srcIdRet = SchemaUtil::toVertexID(edgekey->srcid(), vidType_);
    if (!srcIdRet.ok()) {
        // ERROR
    }
    auto dstIdRet = SchemaUtil::toVertexID(edgekey->dstid(), vidType_);
    if (!dstIdRet.ok()) {
        // ERROR
    }
    auto rank = edgekey->rank();
    EdgeId edgeId = EdgeId(std::move(srcIdRet).value(), std::move(dstIdRet).value(), rank);

    edgeIds_.emplace_back(std::move(edgeId));
}

对于 toPlan函数,则是构造算子对应的计划节点。

值得注意的是,在图数据库 Nebula 中,一条边会以入边和出边的形式分别存储在两个端点中,即正向边src->dst 和反向边 dst->src。在更新边时,需要构建两个 UpdateEdge 计划节点,进行两次更新。UpdateMultiEdgeValidator::toPlan()采取了类似的方式。 其中,反向边的 edgeType 是 -edgeType_。函数最后的布尔值参数表示更新的是正向边 (false) 还是反向边 (true)。

auto *outNode = UpdateMultiEdge::make(qctx_,
                                  nullptr,
                                  spaceId_,
                                  name_,
                                  edgeIds_,
                                  edgeType_,
                                  insertable_,
                                  updatedProps_,
                                  {},
                                  condition_,
                                  {},
                                  false);
auto *inNode = UpdateMultiEdge::make(qctx_,
                                  outNode,
                                  spaceId_,
                                  std::move(name_),
                                  std::move(edgeIds_),
                                  -edgeType_,
                                  insertable_,
                                  std::move(updatedProps_),
                                  std::move(returnProps_),
                                  std::move(condition_),
                                  std::move(yieldColNames_),
                                  true);
root_ = inNode;
tail_ = outNode;

Planner

在 Planner 的 Mutate 计划节点中,新增了 UpdateMultiVertex 类 和 UpdateMultiEdge 类。UpdateMultiVertex 类与原生的 UpdateVertex 类相似,将 vid 替换为了 vid_list。UpdateMultiEdge 类采用相似的方法,在 UpdateEdge 类中,由于每次只更新一条边,可以在构建计划节点时直接交换 src 和 dst 节点的位置实现对反向边的更新。在 UpdateMultiEdge 类中,新增了布尔值随机变量 isRerverse_, 以表示更新的是否为反向边。

Executor

在 Executor 中,Executor 类接收计划节点,并向 storage 层发送 Request 执行,在执行完成后,接收 Responce 并对结果进行汇总。对应地,在 UpdateExecutor 中新增了 UpdateMultiVertexExecutor 类和 UpdateMultiEdgeExecutor 类。

相比于原生的 UpdateVertexExecutor 和 UpdateEdgeExecutor,UpdateMultiVertexExecutor 类和 UpdateMultiEdgeExecutor 类在执行过程中一次异步地向存储层发送多个请求,并对结果进行统一地处理。以 UpdateMultiVertexExecutor 类的 execute函数为例。

  • UpdateMultiVertexExecutor::execute() 请求发送和回复处理部分

观察代码,首先,对应 vid_list 中的每一个节点,构建并向 storage 层发送 updateVertex 请求 Request;其次,使用folly::collectAll对每个请求得到的 Responce 进行汇总 (包括错误码检查和结果处理);最后,如果存在 yield 子句 (即value.props_ref().has_value()为真),还需要将结果汇总到数据集中进行返回。

// update request for each vertex
for (auto &vId : VIds) {
    futures.emplace_back(
        qctx()
        ->getStorageClient()
        ->updateVertex(param,
        vId,
        umvNode->getTagId(),
        umvNode->getUpdatedProps(),
        umvNode->getInsertable(),
        umvNode->getReturnProps(),
        umvNode->getCondition())
        .via(runner()));
}

// collect all responses
return folly::collectAll(futures)
    .via(runner())
    .ensure([updateMultiVertTime]() {
        VLOG(1) << "updateMultiVertTime: " << updateMultiVertTime.elapsedInUSec() << "us";
    })
    .thenValue([this](
    std::vector<folly::Try<StatusOr<storage::cpp2::UpdateResponse>>> results) {
        memory::MemoryCheckGuard guard;
        SCOPED_TIMER(&execTime_);
        DataSet finalResult;
        bool propsExist = false;
        for (auto& result : results) {
            // EXCEPTION CHECK
            // ...
            auto value = std::move(result.value()).value();

            if (value.props_ref().has_value()) {
                propsExist = true;
                auto status = handleMultiResult(finalResult, std::move(*value.props_ref()));
                if (!status.ok()) {
                    return status;
                }
            }
        }

        // print the final result
        if (!propsExist) {
            return Status::OK();
        } else {
            return finish(ResultBuilder()
                .value(std::move(finalResult))
                .iter(Iterator::Kind::kDefault)
                .build());
        }
    });

为了在 Executor 中能对多个 Responce 进行处理和汇总,在 Update 基类中增加了成员函数 handleMultiResult能够将多个 Responce 汇总到一个数据集中。在新增的四个算子中,该成员函数均被共享和使用。

  • handleMultiResult函数实现
Status UpdateBaseExecutor::handleMultiResult(DataSet &result, DataSet &&data) {
    // Exception Check
    // ...
    result.colNames = yieldNames_;
    // result.colNames = std::move(yieldNames_);
    for (auto &row : data.rows) {
      std::vector<Value> columns;
      for (auto i = 1u; i < row.values.size(); i++) {
        columns.emplace_back(std::move(row.values[i]));
      }
      result.rows.emplace_back(std::move(columns));
    }
    return Status::OK();
}

UpdateRef- 算子实现

UpdateRef- 算子包括 UpdateRefVertex 算子和 UpdateRefEdge 算子,分别对应节点更新和边更新。UpdateRef- 算子允许 Update 算子使用引用属性,接受其他语句的结果并进行处理,是 lookup | update 功能实现的基础。通过增加 RefMulti- 算子,允许 UPDATE 语句引用属性,实现 lookup | update 功能。

Parser

仍然使用 MULTIUPDATE 关键字,将引用属性作为需要更新的对象。update_ref_vertex_sentence语句和 update_ref_edge_sentence语句定义如下。

  • update_ref_vertex_sentence语句

在构建 UpdateRefVertexSentence,可以提前对引用属性进行校验。

update_ref_vertex_sentence
    : KW_MULTIUPDATE KW_VERTEX KW_ON name_label vid_ref_expression KW_SET update_list when_clause yield_clause {
        if(graph::ExpressionUtils::findAny($5,{Expression::Kind::kVar})) {
            throw nebula::GraphParser::syntax_error(@5, "Parameter is not supported in update clause");
        }
        auto sentence = new UpdateRefVertexSentence($5, $4, $7, $8, $9);
        $$ = sentence;
    }
    ;
  • update_ref_edge_sentence语句
update_ref_edge_sentence
    : KW_MULTIUPDATE KW_EDGE KW_ON name_label edge_key_ref 
      KW_SET update_list when_clause yield_clause {
        auto sentence = new UpdateRefEdgeSentence($5, $4, $7, $8, $9);
        $$ = sentence; 
    }
    ;

相比于原生 UPDATE 语句,支持 UpdateRef- 算子的语句将vid替换为引用属性 vid_ref; MULTIUPDATE EDGE 语句表示一条边的方式,替换为可以包含$-.src, $-.dst, $-.rankedge_key_ref

在 Mutate Sentence 中,对应地新增 UpdateRefVertexSentence 类和 UpdateRefVertexSentence 类,对相应的输入信息进行存储,使用 Kind::kRefVertex 和 Kind::kRefEdge 进行标识,并在下一阶段进行使用。

Validator

对应地,在 MutateValidator 中新增了 UpdateRefVertexValidator 类和 UpdateRefEdgeValidator 类。

相比于原生的 Update 语句,UpdateRef-算子对应的 Validator 需要对引用属性进行校验。以 UpdateRefVertexValidator 类中的校验部分为例。

  • UpdateRefVertexValidator::validateImpl() 引用属性校验部分

首先从 sentence 中提取 vidRef_,然后调用 deduceExprType 对引用属性进行解析,最后进行类型校验。

vidRef_ = sentence->getVertices()->ref();
auto type = deduceExprType(vidRef_);
NG_RETURN_IF_ERROR(type);
if (type.value() != vidType_) {
    std::stringstream ss;
    ss << "The vid `" << vidRef_->toString() << "' should be type of `" << vidType_
       << "', but was`" << type.value() << "'";
    return Status::SemanticError(ss.str());
  }

类似地,在 UpdateRefEdgeValidator 类中,需要对 $-.src, $-.dst, $-.rank分别进行引用属性的校验。

在调用 toPlan函数时,使用一个 dedup计划节点对接收到的引用属性内容进行去重,再将去重后的结果传递给 UpdateRefVertexUpdateRefEdge 计划节点。

  • UpdateRefVertexValidator::toPlan() 计划构建部分
auto *dedupVid = Dedup::make(qctx_, nullptr);
dedupVid->setInputVar(vidVar);
auto urvNode = UpdateRefVertex::make(qctx_,
                                 dedupVid,
                                 spaceId_,
                                 std::move(name_),
                                 vidRef_,
                                 tagId_,
                                 insertable_,
                                 std::move(updatedProps_),
                                 std::move(returnProps_),
                                 std::move(condition_),
                                 std::move(yieldColNames_));
root_ = urvNode;
tail_ = dedupVid;
  • UpdateRefEdgeValidator::toPlan() 计划构建部分
auto *ureNode = UpdateRefEdge::make(qctx_,
                                    dedup,
                                    spaceId_,
                                    std::move(name_),
                                    edgeKeyRefs_.front(),
                                    edgeType_,
                                    insertable_,
                                    updatedProps_,
                                    returnProps_,
                                    condition_,
                                    yieldColNames_);
  root_ = ureNode;
  tail_ = dedup;
  return Status::OK();

Planer

在 Planner 的 Mutate 计划节点中,新增了 UpdateRefVertex 类 和 UpdateRefEdge 类。UpdateRef- 计划节点的实现难点主要在于,原生 Nebula 中的 Update 基类的基类是 SingleDependencyNode,本身不具备接收输入信息的接口。因此,为了使得新增 UpdateRef- 算子能够正确地接收引用属性的输入,新建了 UpdateRef 基类,继承 SingleInputNode 类型,然后在 UpdateRef 基类的基础上,对 UpdateRefVertex 类 和 UpdateRefEdge 类进行了实现。

  • UpdateRef基类的定义
class UpdateRef : public SingleInputNode {
public:
    // ...
protected:
  friend ObjectPool;
  UpdateRef(QueryContext* qctx,
         Kind kind,
         PlanNode* input,
         GraphSpaceID spaceId,
         std::string name,
         bool insertable,
         std::vector<storage::cpp2::UpdatedProp> updatedProps,
         std::vector<std::string> returnProps,
         std::string condition,
         std::vector<std::string> yieldNames)
      : SingleInputNode(qctx, kind, input),
        spaceId_(spaceId),
        schemaName_(std::move(name)),
        insertable_(insertable),
        updatedProps_(std::move(updatedProps)),
        returnProps_(std::move(returnProps)),
        condition_(std::move(condition)),
        yieldNames_(std::move(yieldNames)) {}
protected:
    // ...

Executor

对应地,在 UpdateExecutor 中新增了 UpdateRefVertexExecutor 类和 UpdateRefEdgeExecutor 类。与 UpdateMultiVertexExecutor 类似,在执行过程中一次异步地向存储层发送多个请求,并对结果进行统一地处理。UpdateRef- 算子在构建请求之前,需要根据引用属性,提取出接收的结果。以 UpdateRefVertexExecutor 类为例。

  • UpdateRefVertexExecutor::execute() 属性提取部分。

首先,根据 vidRef,从计划节点的祖先节点中获取 inputVar;再使用 getResult()函数根据inputVar获取inputResult;最后遍历iter,将需要更新的节点 id 置入 vertices 中。

    if (vidRef != nullptr) {
      auto inputVar = urvNode->inputVar();
      if (inputVar.empty()) {
        DCHECK(urvNode->dep() != nullptr);
        auto* gn = static_cast<const SingleInputNode*>(urvNode->dep())->dep();
        DCHECK(gn != nullptr);
        inputVar = static_cast<const SingleInputNode*>(gn)->inputVar();
      }
      DCHECK(!inputVar.empty());
      auto& inputResult = ectx_->getResult(inputVar);
      auto iter = inputResult.iter();
      vertices.reserve(iter->size());
      QueryExpressionContext ctx(ectx_);
      for (; iter->valid(); iter->next()) {
        auto val = Expression::eval(vidRef, ctx(iter.get()));
        if (val.isNull() || val.empty()) {
          continue;
        }
        if (!SchemaUtil::isValidVid(val, *spaceInfo.spaceDesc.vid_type_ref())) {
            // ...
        }
        vertices.emplace_back(std::move(val));
      }
    }

提取出需要更新的节点(边)后,即可以使用类似于 UpdateMulti- 算子的方式,对这些节点(边)进行批量更新。

由于 UpdateRefEdgeExecutor 类将正向边和反向边的处理设计在一个计划节点中,因此,UpdateRefEdgeExecutor 需要同时构造正向和反向边,即:

    storage::cpp2::EdgeKey edgeKey;
    edgeKey.src_ref() = srcId;
    edgeKey.dst_ref() = dstId;
    edgeKey.ranking_ref() = rank.getInt();
    edgeKey.edge_type_ref() = edgeType;

    storage::cpp2::EdgeKey reverse_edgeKey;
    reverse_edgeKey.src_ref() = std::move(dstId);
    reverse_edgeKey.dst_ref() = std::move(srcId);
    reverse_edgeKey.ranking_ref() = rank.getInt();
    reverse_edgeKey.edge_type_ref() = -edgeType;

###LOOKUP | UPDATE 的语句改写

  • 使用管道符的语句

在使用管道符的语句中,通过管道符 | 对 LOOKUP 语句和 UPDATE 语句进行连接,构建 PipedSentence,在 LOOKUP 语句执行完毕后,将结果输送给 UPDATE 语句进行执行。

lookup_pipe_update_sentence
    : lookup_sentence PIPE update_ref_vertex_sentence {
        $$ = new PipedSentence($1, $3);
    } 
    | lookup_sentence PIPE update_ref_edge_sentence {
        $$ = new PipedSentence($1, $3);
    }
  • LOOKUP | UPDATE VERTEX 语句

LOOKUP | UPDATE VERTEX 语句本质上等价于 通过管道符 | 对 LOOKUP 语句和 UPDATE 语句进行连接。但希望通过语法解析器,避免用户在使用过程中对于节点信息和引用属性的繁琐输入。对于两个语句,tag name 应当是相同的。对于第一个执行的 LOOKUP 语句,其 YIELD 子句应当返回id(vertex) as id;对于第二个执行的 UPDATE 语句,其接受的引用属性应当为 $-.id。因此,在语法解析器中,需要手动添加 idExpr, "id" 作为 LOOKUP 语句的 YieldColumn;同时,$-.id 作为 UPDATE 语句的引用属性。值得注意的是,在语法解析器中,需要对字符串 tag_name 进行深拷贝复制。 最后使用构造的 lookup_sentence 和 update_ref_vertex_sentence 构建 lookup_pipe_update_sentence。

lookup_pipe_update_sentence
    : KW_LOOKUP PIPE KW_UPDATE KW_VERTEX KW_ON name_label lookup_where_clause KW_SET update_list when_clause yield_clause {
        // yield_clause for lookup_sentence is only `id(vertex) as id`
        ArgumentList* optArgList = ArgumentList::make(qctx->objPool());
        Expression* arg = VertexExpression::make(qctx->objPool());
        optArgList->addArgument(arg);
        Expression* idExpr = FunctionCallExpression::make(qctx->objPool(), "id", optArgList);
        YieldColumn* idYieldColumn = new YieldColumn(idExpr, "id");
        
        auto fields = new YieldColumns();
        fields->addColumn(idYieldColumn);
        auto vid_yield_clause = new YieldClause(fields, true);
        
        std::string* name_label_copy = new std::string(*$6);

        // look up
        auto lookup_sentence = new LookupSentence($6, $7, vid_yield_clause);

        // input_ref `$-.id`
        auto vid_input_ref = InputPropertyExpression::make(qctx->objPool(), "id");

        // multiupdate
        auto ref_update_sentence = new UpdateRefVertexSentence(vid_input_ref, name_label_copy, $9, $10, $11);

        $$ = new PipedSentence(lookup_sentence, ref_update_sentence);
    }
  • LOOKUP | UPDATE EDGE 语句

类似的,对于LOOKUP | UPDATE EDGE 语句,进行类似的改写。对于两个语句,edge type 应当是相同的。对于第一个执行的 LOOKUP 语句,其 YIELD 子句应当返回src(edge) as src, dst(edge) as dst, rank(edge) as rank;对于第二个执行的 UPDATE 语句,其接受的引用属性应当为 $-.src -> $-.dst @ $-.rank。因此,在语法解析器中,需要手动添加 "src", "dst", "rank" 作为 LOOKUP 语句的 YieldColumn;同时,利用$-.src, $-.dst, $-.rank构建 EdgeKeyRef 作为 UPDATE EDGE 语句的引用属性。最后使用构造的 lookup_sentence 和 update_ref_edge_sentence 构建 lookup_pipe_update_sentence。

KW_LOOKUP PIPE KW_UPDATE KW_EDGE KW_ON name_label lookup_where_clause KW_SET update_list when_clause yield_clause {
        // yield_clause for lookup_sentence is `src(edge) as src, dst(edge) as dst, rank(edge) as rank`
        ArgumentList* arg_list_src = ArgumentList::make(qctx->objPool());
        Expression *arg_src = EdgeExpression::make(qctx->objPool());
        arg_list_src->addArgument(arg_src);
        Expression* srcExpr = FunctionCallExpression::make(qctx->objPool(), "src", arg_list_src);
        YieldColumn* srcYieldColumn = new YieldColumn(srcExpr, "src");

        ArgumentList* arg_list_dst = ArgumentList::make(qctx->objPool());
        Expression *arg_dst = EdgeExpression::make(qctx->objPool());
        arg_list_dst->addArgument(arg_dst);
        Expression* dstExpr = FunctionCallExpression::make(qctx->objPool(), "dst", arg_list_dst);
        YieldColumn* dstYieldColumn = new YieldColumn(dstExpr, "dst");

        ArgumentList* arg_list_rank = ArgumentList::make(qctx->objPool());
        Expression *arg_rank = EdgeExpression::make(qctx->objPool());
        arg_list_rank->addArgument(arg_rank);
        Expression* rankExpr = FunctionCallExpression::make(qctx->objPool(), "rank", arg_list_rank);
        YieldColumn* rankYieldColumn = new YieldColumn(rankExpr, "rank");

        auto fields = new YieldColumns();
        fields->addColumn(srcYieldColumn);
        fields->addColumn(dstYieldColumn);
        fields->addColumn(rankYieldColumn);
        auto edge_yield_clause = new YieldClause(fields, true);

        std::string* name_label_copy = new std::string(*$6);
        auto lookup_sentence = new LookupSentence($6, $7, edge_yield_clause);

        // input_ref `$-.src -> $-.dst @ $-.rank`
        auto src_input_ref = InputPropertyExpression::make(qctx->objPool(), "src");
        auto dst_input_ref = InputPropertyExpression::make(qctx->objPool(), "dst");
        auto rank_input_ref = InputPropertyExpression::make(qctx->objPool(), "rank");
        
        auto edge_key_ref = new EdgeKeyRef(src_input_ref, dst_input_ref, rank_input_ref);

        auto update_ref_edge_sentence = new UpdateRefEdgeSentence(edge_key_ref, name_label_copy, $9, $10, $11);

        $$ = new PipedSentence(lookup_sentence, update_ref_edge_sentence);
    }

Special notes for your reviewer, ex. impact of this fix, design document, etc:

Checklist:

Tests:

  • Unit test(positive and negative cases)
  • Function test
  • Performance test
  • N/A

Affects:

  • Documentation affected (Please add the label if documentation needs to be modified.)
  • Incompatibility (If it breaks the compatibility, please describe it and add the label.)
  • If it's needed to cherry-pick (If cherry-pick to some branches is required, please label the destination version(s).)
  • Performance impacted: Consumes more CPU/Memory

Release notes:

Please confirm whether to be reflected in release notes and how to describe:

  • Added MULTIUPDATE syntax for batch processing.
  • Introduced LOOKUP | UPDATE syntax for seamless query and update operations.

@CLAassistant
Copy link

CLAassistant commented Sep 25, 2024

CLA assistant check
All committers have signed the CLA.

@Salieri-004 Salieri-004 added the ready-for-testing PR: ready for the CI test label Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-for-testing PR: ready for the CI test
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants