插入块 (Upsert Block)

upsert块允许在单个请求中执行查询和变更。upsert块包含一个查询块和一个或多个变更块。在查询块中定义的变量可以使用uidval函数在变更块中使用。

一般情况下,Upsert Block的结构如下:

upsert {
  query <query block>
  [fragment <fragment block>]
  mutation <mutation block 1>
  [mutation <mutation block 2>]
  ...
}

upsert块的执行还返回在执行变更之前对数据库状态执行的查询的响应。为了获得最新的结果,我们应该提交变更并执行另一个查询。

uid 函数

uid函数允许从查询块中定义的变量中提取uid。根据查询块的执行结果,有两种可能的结果:

  • 如果变量是空的,即没有节点匹配查询,uid函数返回一个新的uid,在set操作的情况下,因此被视为类似于一个空节点。另一方面,对于delete/del操作,它不返回UID,因此该操作成为no-op,并被静默地忽略。空节点在所有变更块中获得相同的UID
  • 如果变量存储一个或多个uid, uid函数将返回存储在变量中的所有uid。在这种情况下,将对返回的所有uid执行操作,一次一个。

val 函数

val函数允许从值变量中提取值。值变量存储uid到对应值的映射。因此,val(v)被存储在N-QuadUID (Subject)映射中的值所替换。如果变量v没有给定UID的值,那么这种变更将被静默地忽略。val函数也可以用于聚合变量的结果,在这种情况下,变更中的所有uid都将用聚合值更新。

uid 函数的使用例子

考虑拥有下面Schema的例子:

curl localhost:8080/alter -X POST -d $'
  name: string @index(term) .
  email: string @index(exact, trigram) @upsert .
  age: int @index(int) .' | jq

现在,假设我们想要创建一个具有emailnameuser。我们还希望确保一个电子邮件在数据库中恰好有一个对应的用户。为了实现这一点,我们首先需要用给定的电子邮件查询数据库中是否存在用户。如果用户存在,则使用其UID更新name信息。如果用户不存在,我们将创建一个新用户并更新电子邮件和名称信息。

我们能够用下面的upsert block块达成上面的需求:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    q(func: eq(email, "user@company1.io")) {
      v as uid
      name
    }
  }

  mutation {
    set {
      uid(v) <name> "first last" .
      uid(v) <email> "user@company1.io" .
    }
  }
}' | jq

返回结果:

{
  "data": {
    "q": [],
    "code": "Success",
    "message": "Done",
    "uids": {
      "uid(v)": "0x1"
    }
  },
  "extensions": {...}
}

upsert块的查询部分将用户的UID和提供的电子邮件存储在变量v中。变更部分从变量v中提取UID,并将名称和电子邮件信息存储在数据库中。如果用户存在,则更新。如果用户不存在,uid(v)将被视为一个空白节点,并创建一个新用户,如上所述。

如果我们再次运行相同的变更,数据将会被覆盖,并且不会创建新的uid。请注意,当再次执行变更时,结果中的uid映射为空,并且数据映射(键q)包含在前一个upsert中创建的uid

{
  "data": {
    "q": [
      {
        "uid": "0x1",
        "name": "first last"
      }
    ],
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '
{
  "query": "{ q(func: eq(email, \"user@company1.io\")) {v as uid, name} }",
  "set": {
    "uid": "uid(v)",
    "name": "first last",
    "email": "user@company1.io"
  }
}' | jq

现在,我们想为拥有相同电子邮件user@company1.io的相同用户添加年龄信息。我们可以使用upsert块做如下相同的事情:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    q(func: eq(email, "user@company1.io")) {
      v as uid
    }
  }

  mutation {
    set {
      uid(v) <age> "28" .
    }
  }
}' | jq

返回结果:

{
  "data": {
    "q": [
      {
        "uid": "0x1"
      }
    ],
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

在这里,查询块查询电子邮件为user@company1.io的用户。它将用户的uid存储在变量v中,然后突变块使用uid函数从变量v中提取uid,从而更新用户的年龄。

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'
{
  "query": "{ q(func: eq(email, \\"user@company1.io\\")) {v as uid} }",
  "set":{
    "uid": "uid(v)",
    "age": "28"
  }
}' | jq

如果我们只想在用户存在的时候执行变更,我们可以使用条件Upsert

val 函数示例

假设我们想把断言年龄迁移到其他年龄。我们可以使用以下突变来做到这一点:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    v as var(func: has(age)) {
      a as age
    }
  }

  mutation {
    # we copy the values from the old predicate
    set {
      uid(v) <other> val(a) .
    }

    # and we delete the old predicate
    delete {
      uid(v) <age> * .
    }
  }
}' | jq

返回结果:

{
  "data": {
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

在这里,变量a将存储从所有uid到其年龄的映射。然后,变更块将每个UID对应的年龄值存储在另一个谓词中,并删除年龄谓词。

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'{
  "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }",
  "delete": {
    "uid": "uid(v)",
    "age": null
  },
  "set": {
    "uid": "uid(v)",
    "other": "val(a)"
  }
}' | jq

批量删除示例 (Bulk Delete Exmaple)

假设我们想从数据库中删除company1的所有用户。这可以通过upsert块在一个查询中实现,如下所示:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    v as var(func: regexp(email, /.*@company1.io$/))
  }

  mutation {
    delete {
      uid(v) <name> * .
      uid(v) <email> * .
      uid(v) <age> * .
    }
  }
}' | jq

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{
  "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }",
  "delete": {
    "uid": "uid(v)",
    "name": null,
    "email": null,
    "age": null
  }
}' | jq