amplify-flutter icon indicating copy to clipboard operation
amplify-flutter copied to clipboard

`IllegalStateException: Invalid Primary Key` when querying models with nullable nested models

Open RobsonT opened this issue 2 years ago • 15 comments

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 avatar May 20 '23 21:05 RobsonT

@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.

Jordan-Nelson avatar May 22 '23 16:05 Jordan-Nelson

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()];
    }

RobsonT avatar May 22 '23 17:05 RobsonT

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.

RobsonT avatar May 23 '23 14:05 RobsonT

Thanks. Can you share the definitions for Talhao and Subdivisao as well?

Jordan-Nelson avatar May 24 '23 13:05 Jordan-Nelson

@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 avatar May 29 '23 01:05 RobsonT

@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.

Jordan-Nelson avatar Jun 14 '23 18:06 Jordan-Nelson

Hi @RobsonT - Please let me know if you are still experiencing this issue.

Jordan-Nelson avatar Jun 20 '23 15:06 Jordan-Nelson

@Jordan-Nelson, Sorry for the delay.

The error occurs in both android and iOS.

RobsonT avatar Jun 20 '23 18:06 RobsonT

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:

  1. Add Plot (Talhao)
  2. Run query, observe no issues
  3. Add Crop (Safra) with Talhao and Subdivisao
  4. Run query, observe no issues
  5. Add Crop (Safra) with Talhao but without Subdivisao
  6. 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 avatar Jun 23 '23 14:06 Jordan-Nelson

@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.

RobsonT avatar Jun 28 '23 19:06 RobsonT

@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

RobsonT avatar Jul 04 '23 18:07 RobsonT

@Jordan-Nelson

Any updates on this issue?

RobsonT avatar Jul 16 '23 00:07 RobsonT

@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?

Jordan-Nelson avatar Jul 18 '23 14:07 Jordan-Nelson

FYI - Amplify Android issue - https://github.com/aws-amplify/amplify-android/issues/2488

Jordan-Nelson avatar Oct 05 '23 18:10 Jordan-Nelson

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.

khatruong2009 avatar Feb 01 '24 21:02 khatruong2009

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.

NikaHsn avatar Apr 17 '24 22:04 NikaHsn