Releasing Dgraph v1.1.1: upsert blocks, facets, and encryption at rest

It’s been a bit over three months since we released Dgraph v1.1.0 (see the announcement) but the wait and hard work were well worth it because today we announce that Dgraph v1.1.1 is released!

This blog post will cover the biggest changes since v1.1.0, including new features, bug fixes, and other improvements.

For those in a hurry, these are the changes covered in this article.

Breaking changes

While this is not a patch release, to fix some of the issues identified with v1.1.0, we had to incorporate some tiny although breaking changes. This section covers both of them.

Removing expand _reverse_ and _forward_

With Dgraph v1.1.0, the behavior of expand(_all_) changed to include the predicates appearing in the types associated with any of the nodes in the context. At that point, we realized that the behavior of expand(_forward_) and expand(_reverse_) wasn’t well defined anymore.

We decided the best option was to remove it, after having published an announcement long ago on our discuss board.

Read below for a new way to expand nodes that might interest you.

No more @type directive

In our previous release, we added the possibility of tagging nodes with types thanks to the <dgraph.type> predicate and also added a function type(T), which returns true if the node in the context has T as one of its values in dgraph.type.

All of this remains, but we also added a @type(T) directive, which was simply an alias of @filter(type(T)). We realized that simplicity in the language was to be kept, so we decided to simply remove it before it became a more well-spread feature.

New features

Done with the bad news, let’s talk about what are the new things we are bringing to you with v1.1.1! Most of these features come as a result of listening to the amazing Dgraph community of users, thanks for helping us identify and prioritize improvement opportunities.

Use @cascade and @normalize anywhere

Until now, the @cascade and @normalize were only supported at the top query level.

With this latest version, you can use @normalize or @cascade at any other level, so you can now perform queries such as the following.

{
  roles(func: eq(name@en, "Jaws")) {
    name@en
    initial_release_date
    starring (first: 5) @normalize {
      performance.actor {
        actor: name@en
      }
      performance.character {
        character: name@en
      }
    }
  }
}

The query fetches the name and release date for the movie Jaws and then, for five roles, it requests the role name and the actor that played it. Thanks to normalize, the role info is flattened into an array.

{
  "data": {
    "roles": [
      {
        "name@en": "Jaws",
        "initial_release_date": "1975-06-20T00:00:00Z",
        "starring": [
          {
            "actor": "Lee Fierro",
            "character": "Mrs. Kintner"
          },
          {
            "actor": "Chris Anastasio",
            "character": "Out of Towner"
          },
          {
            "actor": "Murray Hamilton",
            "character": "Mayor Larry Vaughn"
          },
          {
            "actor": "Christopher Sands",
            "character": "Lifeguard"
          },
          {
            "actor": "Beardsley Graham",
            "character": "Mainlander"
          }
        ]
      }
    ]
  }
}

Improved support for upsert blocks

We have removed some limitations existing in upsert blocks. The documentation has also been revamped and contains a bunch of examples.

Multiple mutations per block

With this new version, upsert blocks can have multiple mutation blocks that are executed in the order they appear. Thanks to this feature, we can remove the previous types assigned to a set of nodes and then set a new type.

upsert {
  # Fetch all the nodes with a name predicate.
  query {
    people as p(func: has(name)) {
      name
    }
  }

  # Delete the previous values of dgraph.type if any.
  mutation {
    delete {
      uid(people) <dgraph.type> * .
    }
  }

  # Set dgraph.type to be Person.
  mutation {
    set {
      uid(people) <dgraph.type> "Person" .
    }
  }
}
Support for val in upsert block mutations

Thanks to this, you can now obtain not only the UIDs for a variable but also the values associated with them. One easy application is to be able to rename a predicate in a single upsert block.

upsert {
  # First, fetch all the values.
  query {
    var(func: has(initial_release_date)) {
      date as initial_release_date
    }
  }

  # Then, delete the existing predicate.
  mutation {
    delete {
      uid(date) <initial_release_date> * .
    }
  }

  # Finally, create the new predicate with the same values.
  mutation {
    set {
      uid(date) <release_date> val(date) .
    }
  }
}
Queries results are now returned too

In previous versions, it was impossible to know which UIDs had been impacted by an upsert. Only the new UIDs were reported in the response, but this has changed since v1.1.1.

In the query right above this paragraph, you can see that the query block is named var, which is how in Dgraph you request that block be omitted from the response. If you change that name to something else (like q), you will see the list of release dates appear in the response to your upsert block.

Once you rename var to q, the response would look like the following:

{
  "data": {
    "code": "Success",
    "message": "Done",
    "queries": {
      "q": [
        {
          "initial_release_date": "2005-09-01T00:00:00Z"
        },
        {
          "initial_release_date": "1971-01-01T00:00:00Z"
        },
        ...
      ],
    "uids": {}
  },
  ...
}

Changes on types

Types were added to Dgraph with v1.1.0, but since then we’ve identified some points that could easily be improved.

Simplified type definition

In v1.1.0, it was possible to provide a different type for a field in a type vs. the type of the predicate with the same name. This was a source of confusion, so we decided to simplify the type definitions to only have a field name, not a type.

To avoid a backward-incompatible change, we now simply ignore any type that might be given in the schema for any fields in a type after warning the user.

A simple schema defining two predicates and a type now looks like this:

<name>: string .
<age>: int .

type Person {
    name
    age
}
Expanding nodes by type name

Some users in the community requested the capacity to expand the predicates declared in a given type, rather than all of the types of the nodes in a given context as expand(_all_) does.

We added the support for type names in expand, so given the schema right above, defining the Person type, you can expand name and age by running the following query.

{
  people(func: has(name)) {
    expand(Person)
  }
}

Notice that if some nodes had a name predicate but were associated types other than Person, the behavior is different from using expand(_all_).

Better facets

Facets are a very convenient way to attach information to a predicate rather than a node. For instance, you can store for how long a person has played an instrument with the following mutation:

{
  set {
    _:p <name> "Diggy" .
    _:p <plays> "Drums" (since=2010-01-01) .
    _:p <plays> "Piano" (since=2015-01-01) .
    _:p <plays> "Violin" (since=2019-01-01) .
  }
}

Some new things have been improved in the language since v1.1.0, and more improvements are coming soon!

Filtering on facets of value predicates

You can fetch the instruments that Diggy has been playing since before 2016 with the following query.

{
  q(func: has(plays)) {
    name
    plays @facets(lt(since, 2016-01-01))
  }
}

Filtering on facets values was only possible with uid predicates before this version.

Support for facets in GraphQL variables

Let’s say you’d like to build a query that receives the year in the previous query as a variable. In previous versions, this wouldn’t have been possible as GraphQL variables weren’t supported as facet values. GraphQL variables as facet values are now supported, but since the type date is not supported you’ll need to use a string.

query long_played($date: string = "2016-01-01") {
  q(func: has(plays)) {
    name
    plays @facets(lt(since, $date))
  }
}

Transparent data encryption

Last, but definitely not least, our Enterprise users have now the possibility of encrypting their data at rest with Transparent Data Encryption. This new feature is part of our effort to improve Dgraph’s security standards for Enterprise users.

Enabling encryption is as simple as passing a flag --encryption_key_file to your Dgraph Alpha nodes. It is important to note that enabling encryption will not encrypt your existing dataset all at once. Instead, new files will be encrypted - so as the compaction process for badger advances more of your dataset will be encrypted.

If you wish to encrypt your whole database instead, you should:

  1. export your dataset
  2. start a new empty Dgraph cluster with encryption enabled
  3. reimport your dataset

Thanks

As always, we want to thank the community for the amazing source of feedback and contributions. We wanted to give a shout-out to the eight amazing external contributors that got at least one commit in this release!

Try it now

You can get the newest version of Dgraph already:

  • Run curl https://get.dgraph.io -sSf | bash
  • Use Docker: dgraph/dgraph:v1.1.1 and dgraph/standalone:v1.1.1
  • Or give it a try by visiting https://play.dgraph.io.