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:
1type Book @model {2 id: ID!3 title: String!4 description: String5}
You can customize the primary key by adding the @primaryKey
directive to a field:
1type Book @model {2 isbn: ID! @primaryKey3 title: String!4 description: String5}
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:
1type Book @model {2 isbn: ID! @primaryKey(sortKeyFields: ["title"])3 title: String!4 description: String5}
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:
1Amplify.DataStore.query(Book.class, Where.identifier(Book.class, "12345"),2 matches -> {3 if (matches.hasNext()) {4 Book book = matches.next();5 Log.i("MyAmplifyApp", "Title: " + book.getTitle());6 }7 },8 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, "12345"),2 { allBooks: Iterator<Book> ->3 while (allBooks.hasNext()) {4 val book = allBooks.next()5 Log.i("MyAmplifyApp", "Title: " + book.title)6 }7 },8 { failure: DataStoreException? -> Log.e("MyAmplifyApp", "Query failed.", failure) })
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, "12345"))2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }3 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, "12345")).subscribe(2 book -> Log.i("MyAmplifyApp", "Title: " + book.getTitle()),3 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)4);
With the values of the composite key:
1Amplify.DataStore.query(Book.class, Where.identifier(Book.class,2 new BookIdentifier( "12345", "My Title")),3 matches -> {4 if (matches.hasNext()) {5 Book book = matches.next();6 Log.i("MyAmplifyApp", "Title: " + book.getTitle());7 }8 },9 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java,2 BookIdentifier( "12345", "My Title")),3 { allBooks: Iterator<Book> ->4 while (allBooks.hasNext()) {5 val book = allBooks.next()6 Log.i("MyAmplifyApp", "Title: " + book.title)7 }8 },9 { failure: DataStoreException? -> Log.e("MyAmplifyApp", "Query failed.", failure) })
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java,2 BookIdentifier( "12345", "My Title")))3 .catch { Log.e("MyAmplifyApp", "Query failed", it) }4 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(Post.class, Where.identifier(Book::class.java,2 new BookIdentifier( "12345", "My Title"))).subscribe(3 post -> Log.i("MyAmplifyApp", "Title: " + post.getTitle()),4 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)5);
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
1Amplify.DataStore.query(Book.class, Where.identifier(Book.class,2 Where.identifier(Book.class, "12345")),3 matches -> {4 if (matches.hasNext()) {5 Book book = matches.next();6 Amplify.DataStore.delete(book,7 deleted -> Log.i("MyAmplifyApp", "Deleted a book."),8 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)9 );10 }11 },12 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, "12345"),2 { matches ->3 if (matches.hasNext()) {4 val post = matches.next()5 Amplify.DataStore.delete(post,6 { Log.i("MyAmplifyApp", "Deleted a book.") },7 { Log.e("MyAmplifyApp", "Delete failed.", it) }8 )9 }10 },11 { Log.e("MyAmplifyApp", "Query failed.", it) }12)
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, "12345"))2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }3 .onEach { Amplify.DataStore.delete(it) }4 .catch { Log.e("MyAmplifyApp", "Delete failed", it) }5 .collect { Log.i("MyAmplifyApp", "Deleted a book") }
1RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, "12345"))2 .flatMapCompletable(RxAmplify.DataStore::delete)3 .subscribe(4 () -> Log.i("MyAmplifyApp", "Deleted a book."),5 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)6 );
Composite Key
1Amplify.DataStore.query(Book.class, Where.identifier(Book.class, Where.identifier(Book::class.java,2 new BookIdentifier( "12345", "My Title")),3 matches -> {4 if (matches.hasNext()) {5 Book book = matches.next();6 Amplify.DataStore.delete(book,7 deleted -> Log.i("MyAmplifyApp", "Deleted a book."),8 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)9 );10 }11 },12 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java,2 BookIdentifier( "12345", "My Title")),3 { matches ->4 if (matches.hasNext()) {5 val post = matches.next()6 Amplify.DataStore.delete(post,7 { Log.i("MyAmplifyApp", "Deleted a book.") },8 { Log.e("MyAmplifyApp", "Delete failed.", it) }9 )10 }11 },12 { Log.e("MyAmplifyApp", "Query failed.", it) }13)
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java,2 BookIdentifier( "12345", "My Title")))3 .catch { Log.e("MyAmplifyApp", "Query failed", it) }4 .onEach { Amplify.DataStore.delete(it) }5 .catch { Log.e("MyAmplifyApp", "Delete failed", it) }6 .collect { Log.i("MyAmplifyApp", "Deleted a book") }
1RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java,2 new BookIdentifier( "12345", "My Title")))3 .flatMapCompletable(RxAmplify.DataStore::delete)4 .subscribe(5 () -> Log.i("MyAmplifyApp", "Deleted a book."),6 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)7 );