Schema migration default values for fields

Is it possible to assign default values for new non-nullable fields in case of a schema migration?

Currently all queries will just return a null object along with an error message for all objects which don’t have the new field.

At the moment, no. There is no concept of default in Dgraph.

As much as it would be nice to have a default directive, it is not the graph way. In a relational model, every row contains data for every column even if the data it contains for that column is nullish. In a graph, only predicates with values exist.

There are two ways to handle this:

  1. Set the default value in the application layer. This saves much work from the database and can usually be done in a single line of code.

  2. JS hooks (aka lambda functions) are expected in the 20.11 release. With a post query hook, you will be able to provide a default value if the value is null.

Thank you. But 1. and 2. only work if I get all the remaining fields of the requested type, correct?

Currently I just receive:
[null, null null, {name: “foo”, addedField: “bar” }]

When I for example request all users (with queryUser). I think it would be better to get:
[{name: “foo1”, addedField: null}, {name: “foo2”, addedField: null}, … {name: “foo”, addedField: “bar”}]

Hi there, just wanted to bump this thread a bit as I think it deserves more attention.

Let’s assume my application is deployed with a “finished” schema and dgraph is running successfully doing it’s thing.

Now we can push new features in very short timeframes like we could never do before (because dgraph-gql is just awesome). New features will ultimately almost always bring new data to the database.

Now of course I could set the default values in the application layer. This however is error prone and in every new *.tsx I write I have to remember for which fields of which types I have to define default values. This can get out of hands very quickly.

Secondly, assume this schema:

type UserPermissions {
   ... 
   canUseFeatures: [String!]! # new field
}

So, I added the field canUseFeatures to my type which is required (!). When I migrate this schema to my existing database, currently all existing nodes will have null on that field. Querying those nodes will result in error: canUseFeatures has to be specified! (or something along these lines). The graphql schema validation fails.

Therefore, I once again propose to add a @default directive that is either used to:

  1. Set the corresponding field when the client is not specifying a value in the addType mutation
  2. Set the corresponding field to the default value for all existing nodes of the type in question.

This is something that we can introduce and it will work with newly added data via GraphQL mutations.

This sounds like a data migration that is best handled by the app developer in my opinion for various reasons. As an app developer, you have the most flexibility and knowledge of what to set this value to and can do at a time that is most suitable for your app.

Well, it’s only a complex migration task if the new field derives it’s value from other existing data and differs from node to node. If that is the case, the migration has to be done of course externally.

However, a lot of times it would be just new added primitive fields (String, Int, Float, Boolean, maybe lists of primitives) that don’t depend on existing data and it should be easy (and useful) to provide default values for them I think. We could save so much time (both downtime and development-time) when these default values would just be inserted on a schema upgrade.

As an alternative to migrating the db and inserting new fields with default data, the @default directive could also work when the type is being queried. If a field is null, the default value is returned instead. I think that this approach is even preferable because it takes less time to implement and doesn’t need to iterate over and change the db. This could be implemented similar to @cascade.

Yes, I was thinking about that as well. This is a possibility that we could look into as it doesn’t require rewriting data in the DB for nodes.

1 Like

So we would have three options here going forward:

Option A:

directive @default(value: AllowedTypes!) on FIELD_DEFINITION

type Foo {
   bar: String! @default(value: "helloworld")
}

Generated mutation:

type Mutation {
   addFoo(bar: String): Foo #note that bar is optional
}

If no bar is specified when adding new nodes (or for existing nodes that didn’t have this field), the query:

query {
   queryFoo {
      bar
   }
}

will return:

{ 
   QueryFoo: [
      {
         bar: "helloworld"
      },
      {
         bar: "helloworld"
      }
   ]
}

Option B:

directive @default(value: AllowedTypes!) on FIELD
type Foo {
   bar: String!
}

Query:

query {
   queryFoo {
      bar @default(value: "lol")
   }
}

This would allow for more flexibility (i.e. different default values in different environments).

Option C:
Both! Combine the ease of mind of Option A with the flexibility of Option B. We just have to figure out good names for the directives then.

Opinions?

@pawan did you have time to discuss this internally?