aws-amplify / amplify-swift

A declarative library for application development using cloud services.
Apache License 2.0
436 stars 190 forks source link

_version field not getting added when using amplify pull #3770

Open amruth-movano opened 1 week ago

amruth-movano commented 1 week ago

Describe the bug

I want to update the model using Amplify.API.Query(request.update), But when i update a model I am getting error -

GraphQLResponseError: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Conflict resolver rejects mutation.", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: Optional([Amplify.JSONValue.string("updateExerciseSession2")]), extensions: Optional(["errorType": Amplify.JSONValue.string("ConflictUnhandled"), "errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.object(["createdAt": Amplify.JSONValue.string("2024-07-03T15:24:01.148Z"),"owner": Amplify.JSONValue.null, "__typename": Amplify.JSONValue.string("ExerciseSession2"), "updatedAt": Amplify.JSONValue.string("2024-07-03T15:24:01.148Z"), "user_id": Amplify.JSONValue.string("***"), "start_time": Amplify.JSONValue.string("2024-07-03T15:23:59.055Z")])]))]

I think it is because we are not sending version but we don't have _version field in generated models.

Our model is like below -

extension ExerciseSession2 { // MARK: - CodingKeys enum CodingKeys: String, ModelKey { case user_id case start_time case owner case createdAt case updatedAt }

static let keys = CodingKeys.self // MARK: - ModelSchema

static let schema = defineSchema { model in let exerciseSession2 = ExerciseSession2.keys

model.authRules = [
  rule(allow: .owner, ownerField: "owner", identityClaim: "cognito:username", provider: .userPools, operations: [.create, .update, .delete, .read])
]

model.listPluralName = "ExerciseSession2s"
model.syncPluralName = "ExerciseSession2s"

model.attributes(
  .index(fields: ["user_id", "start_time"], name: nil),
  .primaryKey(fields: [exerciseSession2.user_id, exerciseSession2.start_time])
)

model.fields(
  .field(exerciseSession2.user_id, is: .required, ofType: .string),
  .field(exerciseSession2.start_time, is: .required, ofType: .dateTime),
  .field(exerciseSession2.owner, is: .optional, ofType: .string),
  .field(exerciseSession2.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime),
  .field(exerciseSession2.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime)
)
}

}

Steps To Reproduce

Create a model using create query
Try to update model using query

Expected behavior

We should get version in generated schema

Amplify Framework Version

2.33.3

Amplify Categories

API, DataStore

Dependency manager

Swift PM

Swift version

5.9.2

CLI version

12.1.1

Xcode version

15.0

Relevant log output

No response

Is this a regression?

Yes

Regression additional context

No response

Platforms

iOS

OS Version

iOS 17.0

Device

iPhone 15 Plus

Specific to simulators

No response

Additional context

No response

5d commented 1 week ago

Hi @amruth-movano ,

The _version model field is a reserved field for the AWS AppSync service. Any attempt to modify the _version field will result in a Bad Request response.

To update a model using the Amplify API plugin, you should use the .mutate API rather than the .query API.

amruth-movano commented 1 week ago

Yes @5d But I am facing issue to get the current version on server. As In my schema version field is not generated, can you guide me how can I access to version field from fetch fro the particular data from db

5d commented 1 week ago

Hi @amruth-movano ,

The _version field is part of the metadata managed by the Amplify library, and there is no public API available to access it. I am interested in understanding the requirements for accessing the _version field.

amruth-movano commented 1 week ago

Actually we want using Amplify API query to create & update the rows. So one row for exercise table, the data can be updated multiple times. So to update the particular row for n number of times we need the version to pass to updatemutation query.

So do we have any idea how can we get the version for a row in dynamodb to update it @5d ?

5d commented 1 week ago

Hi @amruth-movano ,

Thank you for the information.

In the Amplify library, the _version field is managed by the Datastore plugin through real-time updates from AppSync.

However, if you use the API plugin, you will need to handle this metadata manually. Rather than utilizing the convenient APIs we designed for quickly creating a GraphQLRequest, you should use the standard GraphQLRequest APIs.

For example, in your case of updating a model instance, you should:

  1. Query the latest version first.
  2. Use that information to make an update mutation request.
  3. Maintain the version information afterwards.
var version: Int?
let blogOnServer = try await Amplify.API.query(
    request: .query(modelName: Blog.modelName, byId: blogId)
)
version = try? blogOnServer.map(\.?.syncMetadata.version).get()
print("version on server: ", version ?? "N/A")
let newBlog = Blog(id: blogId, name: UUID().uuidString)
let updateResult = try await Amplify.API.mutate(
    request: .updateMutation(of: newBlog, version: version)
)
version = try? updateResult.map(\.syncMetadata.version).get()
print("new version: ", version ?? "N/A")
amruth-movano commented 1 week ago

Hi @5d I am trying the query in below format -

let dataQ = try await Amplify.API.query(request: .query(modelName: ActivityIntensity.modelName, byId: "7BBD68F6-64E2-4507-A792-9E6BCC64FFDE")) let version2 = try? dataQ.map(.?.syncMetadata.version).get() print("version on server :(dataQ): ", version2 ?? "N/A")

Received output => version on server :failure(GraphQLResponseError<Optional<MutationSync>>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Validation error of type MissingFieldArgument: Missing field argument user_id @ \'getActivityIntensity\'", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: nil, extensions: nil), Amplify.GraphQLError(message: "Validation error of type MissingFieldArgument: Missing field argument data_timestamp @ \'getActivityIntensity\'", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: nil, extensions: nil), Amplify.GraphQLError(message: "Validation error of type UnknownArgument: Unknown field argument id @ \'getActivityIntensity\'", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 24)]), path: nil, extensions: nil)] 






So now main thing is for some tables we don’t have ID primary field in table instead of that we have start_time/data_timestamp field which is primary key. 
So how should we use start_time/data_timestamp to fetch that element with version. Also our backend needs to pass the userId as well with the query.

And one more question, can we fetch version in list query? Ex; Amplify.API.query(request: .list)

amruth-movano commented 6 days ago

@5d Can you please guide us in this scenario?

harsh62 commented 4 days ago

@amruth-movano I will take a look at your query and try to get an answer for your question. Appreciate your patience on this.

amruth-movano commented 4 days ago

@harsh62 Yes please take a look and let us know the solution, as it's a blocker for us.

harsh62 commented 4 days ago

Starting off with the last question first:

can we fetch version in list query? Ex; Amplify.API.query(request: .list)

Yes, you should be able to do it.

So how should we use start_time/data_timestamp to fetch that element with version

Your start_time becomes the id

let version2 = try? dataQ.map(.?.syncMetadata.version).get()

You are missing a backslash \ when accessing version in the map.

Also our backend needs to pass the userId as well with the query.


You should be able to use predicates to query data. https://docs.amplify.aws/gen1/swift/build-a-backend/graphqlapi/query-data/

Hope these answers your question.

amruth-movano commented 3 days ago

Hi @harsh62 I am not able to use start_time as Id, if I use that I am getting error for missing userID field

let dataQuery = try await Amplify.API.query(request: .get(ExerciseSession2.self, byId: startTime.temporalDateTime.iso8601String))

Error - GraphQLResponseError<Optional>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Validation error of type MissingFieldArgument: Missing field argument user_id @ \'getExerciseSession2\'", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: nil, extensions: nil), Amplify.GraphQLError(message: "Validation error of type MissingFieldArgument: Missing field argument start_time @ \'getExerciseSession2\'", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: nil, extensions: nil), Amplify.GraphQLError(message: "Validation error of type UnknownArgument: Unknown field argument id @ \'getExerciseSession2\'", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 23)]), path: nil, extensions: nil)]

And for list query getting error there is no field called syncMetadata

harsh62 commented 3 days ago

Are you able to use a predicate to pass both the fields?

You should be able to use predicates to query data. https://docs.amplify.aws/gen1/swift/build-a-backend/graphqlapi/query-data/

amruth-movano commented 3 days ago

@harsh62 if we pass through predicate also, it is giving same error as our backend needs userID directly & not from the predicate, so we are ovverriding it directly

amruth-movano commented 3 days ago

@harsh62 Can you give me a example to fetch version from list query? This will be easy for me if you can guide me in list query