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
A record with a custom primary key can be queried for in the following ways:
With the value of the primary key:
Amplify.DataStore.query(Book.self, byIdentifier: .identifier(isbn: "12345")) { result in switch result { case .success(let book): guard let book = book else { print("Query was successful with empty result") return } print("Query was successful, retrieved book: \(book)") case .failure(let error): print("Error on query() for type Book with error: \(error)") }}
let sink = Amplify.DataStore.query(Book.self, byIdentifier: .identifier(isbn: "12345")).sink { if case let .failure(error) = $0 { print("Error on query() for type Book with error: \(error)") }} receiveValue:{ book in guard let book = book else { print("Query was successful with empty result") return } print("Query was successful, retrieved book: \(book)")}
With the value of QueryPredicate:
Amplify.DataStore.query(Book.self, where: Book.keys.isbn == "12345") { result in switch result { case .success(let books): print("Query was successful, retrieved books: \(books)") case .failure(let error): print("Error on query() for type Book with error: \(error)") }}
let sink = Amplify.DataStore.query(Book.self, where: Book.keys.isbn == "12345").sink { if case let .failure(error) = $0 { print("Error on query() for type Book with error \(error)") }} receiveValue: { books in print("Query was successful, retrieved books: \(books)")}
Deleting records with custom primary keys
A record with a custom primary key can be deleted in the following ways:
With the value of the primary key:
Amplify.DataStore.delete(Book.self, withIdentifier: .identifier(isbn: "12345")) { result in switch result{ case .success: print("Book deleted!") case .failure(let error): print("Error deleting book: \(error)") }}
let sink = Amplify.DataStore.delete(Book.self, withIdentifier: .identifier(isbn: "12345")).sink { if case let .failure(error) = $0 { print("Error deleting book: \(error)") }} receiveValue: { print("Book deleted!")}
With the value of QueryPredicate:
Amplify.DataStore.delete(Book.self, where: Book.keys.isbn == "12345") { result in switch result { case .success: print("Book deleted!") case .failure(let error): print("Error deleting book: \(error)") }}
let sink = Amplify.DataStore.delete(Book.self, where: Book.keys.isbn == "12345").sink { if case let .failure(error) = $0 { print("Error deleting book: \(error)") }} receiveValue: { print("Book deleted!")}