Customize primary keys
Customize primary keys
By default, DataStore models have an id
field that is automatically populated on the client with a UUID v4, allowing DataStore to generate non-colliding globally unique identifiers in a scalable way. While UUIDs have desirable properties (they are large, non-sequential and opaque), there are times when a custom primary key, also known as custom identifier, is needed. For instance, to:
- Have friendly/readable identifiers (surrogate/opaque vs. natural keys)
- Define composite primary keys
- Customize data partitioning to optimize for scale (especially important when planning to handle large amounts of data in short periods of time)
- Selectively synchronize data to clients (e.g. by fields like
deviceId
,userId
or similar) - Prioritize the sort order in which objects are returned by the sync queries
- Make existing data consumable and syncable by DataStore
A schema with the typical id
field looks like this:
type Book @model { id: ID! title: String! description: String}
You can customize the primary key by adding the @primaryKey
directive to a field:
type Book @model { isbn: ID! @primaryKey title: String! description: String}
You can also require multiple fields to define your primary key. When your primary key references multiple fields, it's called a composite key. In the example below, the primary key is defined by the isbn
and title
fields:
type Book @model { isbn: ID! @primaryKey(sortKeyFields: ["title"]) title: String! description: String}
Determine when the primary key field is auto-populated upon record creation
When you create a record with DataStore, a UUID is automatically populated for the default id: ID!
primary key field. When working with custom primary keys, DataStore will automatically populate the key fields in the following conditions:
Description | Type | Autopopulated with UUID |
---|---|---|
Without |
| ✅ Yes |
Without |
| ✅ Yes |
|
| ❌ No |
Explicit |
| ✅ Yes |
Explicit |
| ✅ Yes |
Explicit |
| ✅ Yes |
@primaryKey with no |
| ❌ No |
Querying records with custom primary keys
With the value of the primary key:
Amplify.DataStore.query(Book.class, Where.identifier(Book.class, "12345"), matches -> { if (matches.hasNext()) { Book book = matches.next(); Log.i("MyAmplifyApp", "Title: " + book.getTitle()); } }, failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, "12345"), { allBooks: Iterator<Book> -> while (allBooks.hasNext()) { val book = allBooks.next() Log.i("MyAmplifyApp", "Title: " + book.title) } }, { failure: DataStoreException? -> Log.e("MyAmplifyApp", "Query failed.", failure) })
Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, "12345")) .catch { Log.e("MyAmplifyApp", "Query failed", it) } .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, "12345")).subscribe( book -> Log.i("MyAmplifyApp", "Title: " + book.getTitle()), failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
With the values of the composite key:
Amplify.DataStore.query(Book.class, Where.identifier(Book.class, new BookIdentifier( "12345", "My Title")), matches -> { if (matches.hasNext()) { Book book = matches.next(); Log.i("MyAmplifyApp", "Title: " + book.getTitle()); } }, failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, BookIdentifier( "12345", "My Title")), { allBooks: Iterator<Book> -> while (allBooks.hasNext()) { val book = allBooks.next() Log.i("MyAmplifyApp", "Title: " + book.title) } }, { failure: DataStoreException? -> Log.e("MyAmplifyApp", "Query failed.", failure) })
Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, BookIdentifier( "12345", "My Title"))) .catch { Log.e("MyAmplifyApp", "Query failed", it) } .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
RxAmplify.DataStore.query(Post.class, Where.identifier(Book::class.java, new BookIdentifier( "12345", "My Title"))).subscribe( post -> Log.i("MyAmplifyApp", "Title: " + post.getTitle()), failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Deleting records with custom primary keys
In addition to deleting by the model type (or Predicates.ALL
), a record with a custom primary key can be deleted the following ways:
With a predicate:
Custom Primary Key
Amplify.DataStore.query(Book.class, Where.identifier(Book.class, Where.identifier(Book.class, "12345")), matches -> { if (matches.hasNext()) { Book book = matches.next(); Amplify.DataStore.delete(book, deleted -> Log.i("MyAmplifyApp", "Deleted a book."), failure -> Log.e("MyAmplifyApp", "Delete failed.", failure) ); } }, failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, "12345"), { matches -> if (matches.hasNext()) { val post = matches.next() Amplify.DataStore.delete(post, { Log.i("MyAmplifyApp", "Deleted a book.") }, { Log.e("MyAmplifyApp", "Delete failed.", it) } ) } }, { Log.e("MyAmplifyApp", "Query failed.", it) })
Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, "12345")) .catch { Log.e("MyAmplifyApp", "Query failed", it) } .onEach { Amplify.DataStore.delete(it) } .catch { Log.e("MyAmplifyApp", "Delete failed", it) } .collect { Log.i("MyAmplifyApp", "Deleted a book") }
RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, "12345")) .flatMapCompletable(RxAmplify.DataStore::delete) .subscribe( () -> Log.i("MyAmplifyApp", "Deleted a book."), failure -> Log.e("MyAmplifyApp", "Delete failed.", failure) );
Composite Key
Amplify.DataStore.query(Book.class, Where.identifier(Book.class, Where.identifier(Book::class.java, new BookIdentifier( "12345", "My Title")), matches -> { if (matches.hasNext()) { Book book = matches.next(); Amplify.DataStore.delete(book, deleted -> Log.i("MyAmplifyApp", "Deleted a book."), failure -> Log.e("MyAmplifyApp", "Delete failed.", failure) ); } }, failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, BookIdentifier( "12345", "My Title")), { matches -> if (matches.hasNext()) { val post = matches.next() Amplify.DataStore.delete(post, { Log.i("MyAmplifyApp", "Deleted a book.") }, { Log.e("MyAmplifyApp", "Delete failed.", it) } ) } }, { Log.e("MyAmplifyApp", "Query failed.", it) })
Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, BookIdentifier( "12345", "My Title"))) .catch { Log.e("MyAmplifyApp", "Query failed", it) } .onEach { Amplify.DataStore.delete(it) } .catch { Log.e("MyAmplifyApp", "Delete failed", it) } .collect { Log.i("MyAmplifyApp", "Deleted a book") }
RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, new BookIdentifier( "12345", "My Title"))) .flatMapCompletable(RxAmplify.DataStore::delete) .subscribe( () -> Log.i("MyAmplifyApp", "Deleted a book."), failure -> Log.e("MyAmplifyApp", "Delete failed.", failure) );