`IllegalStateException: Invalid Primary Key` when querying models with nullable nested models
Description
I'm using the datastore to query a table, filtering by a foreign key. when passing the foreign key, some manage to bring the data, but others generate the error: Invalid Primary Key, It should either be single field or of type composite primary key
Categories
- [ ] Analytics
- [ ] API (REST)
- [ ] API (GraphQL)
- [ ] Auth
- [ ] Authenticator
- [X] DataStore
- [ ] Storage
Steps to Reproduce
The error occurs in some tables with a 1:n relationship, when making the query filtering by the foreign key.
Error:
E/amplify:flutter:datastore(24262): at java.lang.Thread.run(Thread.java:923) E/amplify:flutter:datastore(24262): Caused by: java.lang.IllegalStateException: Invalid Primary Key, It should either be single field or of type composite primary key Primary Key.java.lang.NullPointerException E/amplify:flutter:datastore(24262): at com.amplifyframework.core.model.ModelIdentifier$Helper.getUniqueKey(ModelIdentifier.java:128) E/amplify:flutter:datastore(24262): at com.amplifyframework.core.model.SerializedModel$Builder.serializedData(SerializedModel.java:340) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.createSerializedModel(SQLiteStorageAdapter.java:900) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.createSerializedModel(SQLiteStorageAdapter.java:872) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.lambda$query$5$com-amplifyframework-datastore-storage-sqlite-SQLiteStorageAdapter(SQLiteStorageAdapter.java:444) E/amplify:flutter:datastore(24262): ... 6 more E/amplify:flutter:datastore(24262): Query operation failed. E/amplify:flutter:datastore(24262): DataStoreException{message=Error in querying the model., cause=java.lang.IllegalStateException: Invalid Primary Key, It should either be single field or of type composite primary key Primary Key.java.lang.NullPointerException, recoverySuggestion=See attached exception for details.} E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.lambda$query$5$com-amplifyframework-datastore-storage-sqlite-SQLiteStorageAdapter(SQLiteStorageAdapter.java:450) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter$$ExternalSyntheticLambda8.run(Unknown Source:10) E/amplify:flutter:datastore(24262): at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) E/amplify:flutter:datastore(24262): at java.util.concurrent.FutureTask.run(FutureTask.java:266) E/amplify:flutter:datastore(24262): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) E/amplify:flutter:datastore(24262): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) E/amplify:flutter:datastore(24262): at java.lang.Thread.run(Thread.java:923) E/amplify:flutter:datastore(24262): Caused by: java.lang.IllegalStateException: Invalid Primary Key, It should either be single field or of type composite primary key Primary Key.java.lang.NullPointerException E/amplify:flutter:datastore(24262): at com.amplifyframework.core.model.ModelIdentifier$Helper.getUniqueKey(ModelIdentifier.java:128) E/amplify:flutter:datastore(24262): at com.amplifyframework.core.model.SerializedModel$Builder.serializedData(SerializedModel.java:340) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.createSerializedModel(SQLiteStorageAdapter.java:900) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.createSerializedModel(SQLiteStorageAdapter.java:872) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.lambda$query$5$com-amplifyframework-datastore-storage-sqlite-SQLiteStorageAdapter(SQLiteStorageAdapter.java:444) E/amplify:flutter:datastore(24262): ... 6 more E/amplify:flutter:datastore(24262): Query operation failed. E/amplify:flutter:datastore(24262): DataStoreException{message=Error in querying the model., cause=java.lang.IllegalStateException: Invalid Primary Key, It should either be single field or of type composite primary key Primary Key.java.lang.NullPointerException, recoverySuggestion=See attached exception for details.} E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.lambda$query$5$com-amplifyframework-datastore-storage-sqlite-SQLiteStorageAdapter(SQLiteStorageAdapter.java:450) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter$$ExternalSyntheticLambda8.run(Unknown Source:10) E/amplify:flutter:datastore(24262): at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) E/amplify:flutter:datastore(24262): at java.util.concurrent.FutureTask.run(FutureTask.java:266) E/amplify:flutter:datastore(24262): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) E/amplify:flutter:datastore(24262): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) E/amplify:flutter:datastore(24262): at java.lang.Thread.run(Thread.java:923) E/amplify:flutter:datastore(24262): Caused by: java.lang.IllegalStateException: Invalid Primary Key, It should either be single field or of type composite primary key Primary Key.java.lang.NullPointerException E/amplify:flutter:datastore(24262): at com.amplifyframework.core.model.ModelIdentifier$Helper.getUniqueKey(ModelIdentifier.java:128) E/amplify:flutter:datastore(24262): at com.amplifyframework.core.model.SerializedModel$Builder.serializedData(SerializedModel.java:340) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.createSerializedModel(SQLiteStorageAdapter.java:900) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.createSerializedModel(SQLiteStorageAdapter.java:872) E/amplify:flutter:datastore(24262): at com.amplifyframework.datastore.storage.sqlite.SQLiteStorageAdapter.lambda$query$5$com-amplifyframework-datastore-storage-sqlite-SQLiteStorageAdapter(SQLiteStorageAdapter.java:444) E/amplify:flutter:datastore(24262): ... 6 more
Screenshots
No response
Platforms
- [X] iOS
- [X] Android
- [ ] Web
- [ ] macOS
- [ ] Windows
- [ ] Linux
Flutter Version
3.3.10
Amplify Flutter Version
0.6.13
Deployment Method
Amplify CLI
Schema
No response
@RobsonT - Could you share your schema (at least the portion that includes the model you are query, and any models it is related to) and the code that you are running that causes this exception? Thanks.
Hello @Jordan-Nelson ,
Schema:
type Safra @model @auth(rules: [{allow: public}]) {
id: ID!
data_plantio: AWSDate
finalizada: Boolean
dataFinalizacao: AWSDate
talhaoID: ID! @index(name: "byTalhao")
subdivisaoID: ID @index(name: "bySubdivisao")
propriedadecultivoID: ID! @index(name: "byPropriedadeCultivo")
Manejos: [Manejo] @hasMany(indexName: "bySafra", fields: ["id"])
LoteSafras: [LoteSafra] @hasMany(indexName: "bySafra", fields: ["id"])
Perdas: [Perda] @hasMany(indexName: "bySafra", fields: ["id"])
OcorrenciaSafras: [OcorrenciaSafra] @hasMany(indexName: "bySafra", fields: ["id"])
responsavel_atualizacao_id: String
responsavel_cadastro_id: String
talhao: Talhao @belongsTo(fields: ["talhaoID"])
subdivisao: Subdivisao @belongsTo(fields: ["subdivisaoID"])
propriedadeCultivo: PropriedadeCultivo @belongsTo(fields: ["propriedadecultivoID"])
}
type Manejo @model @auth(rules: [{allow: public}]) {
id: ID!
observacao: String
dataAplicacao: AWSDate
horario: AWSTime
duracaoMinutos: Int
quantidadeVolumeCalda: Float
dataSolicitacao: AWSDate
pendente: Boolean
safraID: ID! @index(name: "bySafra")
propriedadeservicoID: ID @index(name: "byPropriedadeServico")
valorServico: Float
Solicitante: String
responsavel: String
ManejoProdutos: [ManejoProduto] @hasMany(indexName: "byManejo", fields: ["id"])
equipamentoaplicacaoID: ID @index(name: "byEquipamentoAplicacao")
modoaplicacaoID: ID @index(name: "byModoAplicacao")
responsavel_cadastro_id: String
responsavel_atualizacao_id: String
safra: Safra @belongsTo(fields: ["safraID"])
propriedadeServico: PropriedadeServico @belongsTo(fields: ["propriedadeservicoID"])
modoAplicacao: ModoAplicacao @belongsTo(fields: ["modoaplicacaoID"])
equipamentoAplicacao: EquipamentoAplicacao @belongsTo(fields: ["equipamentoaplicacaoID"])
}
Code:
for (var talhao in talhoes) {
final safra = await Amplify.DataStore.query(Safra.classType, where: Safra.TALHAO.eq(talhao.id)); // Error in this line
safras = [...safras, ...safra.map((e) => e.id).toList()];
}
I did some tests and managed to run the project. Apparently the error occurs when "subdivisaoID" in "Safra" is null, working correctly when this field is filled. However, this field is marked as "not required" precisely because it does not need a subdivisaoID in some cases.
Thanks. Can you share the definitions for Talhao and Subdivisao as well?
@Jordan-Nelson, These are the models:
type Talhao @model @auth(rules: [{allow: public}]) { id: ID! nome: String propriedadeID: ID! @index(name: "byPropriedade") Subdivisaos: [Subdivisao] @hasMany(indexName: "byTalhao", fields: ["id"]) Safras: [Safra] @hasMany(indexName: "byTalhao", fields: ["id"]) Coordenadas: [Coordenada] @hasMany(indexName: "byTalhao", fields: ["id"]) OutrasAreasPropriedades: [OutrasAreasPropriedade] @hasMany(indexName: "byTalhao", fields: ["id"]) responsavel_cadastro_id: String responsavel_atualizacao_id: String ativo: Boolean propriedade: Propriedade @belongsTo(fields: ["propriedadeID"]) area_hectares: Float }
type Subdivisao @model @auth(rules: [{allow: public}]) { id: ID! complemento: String cateroriasubdivisaoID: ID! @index(name: "byCategoriaSubdivisao") talhaoID: ID! @index(name: "byTalhao") propriedadeID: ID! @index(name: "byPropriedade") Safras: [Safra] @hasMany(indexName: "bySubdivisao", fields: ["id"]) Coordenadas: [Coordenada] @hasMany(indexName: "bySubdivisao", fields: ["id"]) responsavel_atualizacao_id: String responsavel_cadastro_id: String subdivisao_pai: String ativo: Boolean propriedade: Propriedade @belongsTo(fields: ["propriedadeID"]) talhao: Talhao @belongsTo(fields: ["talhaoID"]) categoriaSubdivisao: CategoriaSubdivisao @belongsTo(fields: ["cateroriasubdivisaoID"]) area_hectares: Float }
@RobsonT - Apologies for the delay in a response.
I see you indicated that you are using iOS and Android in the issue. The logs provided are from Android. Can you confirm you are experiencing this issue on iOS? Note: you may need to run the app from xcode to see the native iOS logs.
Hi @RobsonT - Please let me know if you are still experiencing this issue.
@Jordan-Nelson, Sorry for the delay.
The error occurs in both android and iOS.
Hi @RobsonT - I was able to create a minimal reproduction of this issue. However, I am only seeing the issue on Android. The exception is not thrown on iOS. Can you confirm you are also seeing this on iOS?
Here is the schema, app, and steps I took to reproduce:
Repro steps:
- Add Plot (Talhao)
- Run query, observe no issues
- Add Crop (Safra) with Talhao and Subdivisao
- Run query, observe no issues
- Add Crop (Safra) with Talhao but without Subdivisao
- Run query, observe the exception
Invalid Primary Key, It should either be single field or of type composite primary key Primary Key
input AMPLIFY {
globalAuthRule: AuthRule = { allow: public }
} # FOR TESTING ONLY!
# Crop
type Safra @model {
id: ID!
talhaoID: ID! @index(name: "byTalhao")
subdivisaoID: ID @index(name: "bySubdivisao")
talhao: Talhao @belongsTo(fields: ["talhaoID"])
subdivisao: Subdivisao @belongsTo(fields: ["subdivisaoID"])
}
# Plot
type Talhao @model {
id: ID!
nome: String
Subdivisaos: [Subdivisao] @hasMany(indexName: "byTalhao", fields: ["id"])
Safras: [Safra] @hasMany(indexName: "byTalhao", fields: ["id"])
}
# Subdivision
type Subdivisao @model {
id: ID!
talhaoID: ID! @index(name: "byTalhao")
Safras: [Safra] @hasMany(indexName: "bySubdivisao", fields: ["id"])
talhao: Talhao @belongsTo(fields: ["talhaoID"])
}
App
import 'package:flutter/material.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:amplify_datastore/amplify_datastore.dart';
import 'amplifyconfiguration.dart';
import 'models/ModelProvider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
_configureAmplify();
}
Future<void> _configureAmplify() async {
final datastorePlugin = AmplifyDataStore(
modelProvider: ModelProvider.instance,
);
await Amplify.addPlugin(datastorePlugin);
try {
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
safePrint('Amplify already configured.');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('My App'),
actions: [
FilledButton(
onPressed: Amplify.DataStore.clear,
child: const Text('Clear DB'),
),
],
),
body: const Center(
child: Column(
children: [
FilledButton(
onPressed: addPlot,
child: Text('Add Plot'),
),
FilledButton(
onPressed: addCrop,
child: Text('Add Crop'),
),
FilledButton(
onPressed: addCropWithoutSubdivision,
child: Text('Add Crop without Subdivision'),
),
FilledButton(
onPressed: queryCrops,
child: Text('Query Crops'),
),
],
),
),
),
);
}
}
Future<void> addPlot() async {
final talhao = Talhao(nome: 'New Talhao ${uuid()}');
try {
await Amplify.DataStore.save(talhao);
safePrint('New plot saved!');
} on DataStoreException catch (e) {
safePrint('Something went wrong saving model: ${e.message}');
}
}
Future<void> addCrop() async {
final talhao = Talhao(nome: 'New Talhao ${uuid()}');
final subdivisao = Subdivisao(talhao: talhao);
final safra = Safra(talhao: talhao, subdivisao: subdivisao);
try {
await Amplify.DataStore.save(talhao);
await Amplify.DataStore.save(subdivisao);
await Amplify.DataStore.save(safra);
safePrint('New crop saved!');
} on DataStoreException catch (e) {
safePrint('Something went wrong saving model: ${e.message}');
}
}
Future<void> addCropWithoutSubdivision() async {
final talhao = Talhao(nome: 'New Talhao ${uuid()}');
final safra = Safra(talhao: talhao);
try {
await Amplify.DataStore.save(talhao);
await Amplify.DataStore.save(safra);
safePrint('New crop saved!');
} on DataStoreException catch (e) {
safePrint('Something went wrong saving model: ${e.message}');
}
}
Future<void> queryCrops() async {
final talhoes = await Amplify.DataStore.query(Talhao.classType);
for (var talhao in talhoes) {
safePrint('talhao: ${talhao.id}');
final safras = await Amplify.DataStore.query(
Safra.classType,
where: Safra.TALHAO.eq(talhao.id),
);
for (final safra in safras) {
safePrint('safra: ${safra.id}');
}
}
}
@Jordan-Nelson
We ran some tests and the error did not actually occur on iOS. We probably confused some mistake we made with this inavlid primary key error. So at the moment it only happens on android.
@Jordan-Nelson
I don't know if it's related, but we have an error in another table that only occurs on iOS, apparently also referring to null optional fields.
amplify_datastore/FlutterSerializedModel.swift:233: Fatal error: Unexpectedly found nil while unwrapping an Optional value
@Jordan-Nelson
Any updates on this issue?
@RobsonT - This seems to be an issue that will need to be addressed in Amplify Android. I opened an issue (linked above) in Amplify Android to track this. I don't have a timeline for a fix at the moment.
I don't think the other error you have shared is related. If you have reproducible steps for that, can you open a new issue?
FYI - Amplify Android issue - https://github.com/aws-amplify/amplify-android/issues/2488
Hi @RobsonT, the amplify-android team has a PR open with a fix for this issue. After it is released on their library, the bug fix will subsequently be released on amplify-flutter. Thank you for your patience.
Thank you for your patience. The issue has been fixed in version 1.8.0. and I'm closing it. However, if you encounter any issues after updating to version 1.8.0, please don't hesitate to reopen it.