How to extract the first row in a DynamoDB table? - ios

I am using (AWS) DynamoDB for the first time, so my question is rather basic.
I have set up a table (myTable) containing one field (theField) and I am able to fill it up one record at a time.
Here is what I want to do: make a query to extract the first element of the sorted table. I guess it could hardly be simpler.
This is my code, based on what I could find in the AWS documentation and on some example from the net:
let queryExpression = AWSDynamoDBQueryExpression()
queryExpression.scanIndexForward = false
queryExpression.limit = 1
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDbObjectMapper.query(myTable.self, expression: queryExpression) {
(output: AWSDynamoDBPaginatedOutput?, error: Error?) in
if error != nil {
print("The request failed. Error: \(String(describing: error))")
}
if output != nil {
// Process the output.
}
}
When I run this code I get the error message below:
The request failed. Error: Optional(Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=0 "(null)"
UserInfo={__type=com.amazon.coral.validate#ValidationException, message=Either the KeyConditions or
KeyConditionExpression parameter must be specified in the request.})
First what is this KeyConditions or KeyConditionExpression business?
The query is clear why do I need any condition?
Second, I of course tried to fill some dummy condition (based on what I could find on some other post) to see what happens, but it never worked.
Could someone tell me how I need to write queryExpression to do what I want?

You have no query criteria. For a query you need to provide at least the PartitionKey. Try a scan instead of a query.

You cannot extract first item from a dynamodb. Not with scan or query. You have to scan the table entirely or create a GSI with createdAt as hashKey/sortKey

Related

Delete members of list when deleting list in Core Data / iOS / Swift 5

I have two entities: Item (which keeps track of lists) and Tasks (which are task items within lists). In one view of the app, there is a swipe to delete feature which removes the list. This works with the following code:
offsets.map { items[$0] }.forEach(viewContext.delete)
I would like to delete now obsolete tasks for the list being deleted. I first tried this code:
tasks.filter{$0.listID! == listsID}.forEach(viewContext.delete)
I was proud of myself. What an elegant solution. Swift hated it. I get an error that says
Reference to member 'listID' cannot be resolved without a contextual type
I Googled and SOd and got nowhere. I don't know what that error means and can't figure out how to fix it in XCode 12 / iOS 14. So then I came up with the following not so elegant code:
let listsID = offsets.map {items[$0].id!}
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Tasks.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "listID == %#", listsID)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try viewContext.execute(deleteRequest)
guard
let deleteResult = result as? NSBatchDeleteResult,
let ids = deleteResult.result as? [NSManagedObjectID]
else {return}
let changes = [NSDeletedObjectsKey: ids]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes,
into: [viewContext])
}
catch {
print(error as Any)
}
listsID captures the UUID of the list and stores it as a variable. However, you'll note that it is stored as an ARRAY (ugh). The fetchRequest.predicate code filters the tasks so that only those that have the attribute of 'listID' (which helps connect the task to the list it belongs to) matching the id of the list being deleted is pulled.
The code compiles (yay!). Then I get the following error when trying to delete a list:
Exception NSException * "Unexpected or improperly formatted UUID parameter with type Swift.__SwiftDeferredNSArray, expected NSUUID or well-formed NSString" 0x0000600003145b30
I'm sure there is a simple way to do this. I played with inverse relationships in Core Data but got nowhere. I didn't know how to tell it that 'id' (UUID) in 'Item' == 'listID' (UUID) in 'Tasks', so deleting the list didn't do anything to the tasks that belong to it.
I tried to create a ForEach loop, but ran into various errors that I couldn't resolve. I'd prefer to use the elegant code that I wrote at the top to make the deletion happen. Any ideas?
Thank you.
The credit for this goes to pbasdf for his super helpful responses. I used this link to learn about setting up relationships in Core Data: Seneca SDDS. I was missing one line in my persistence model:
newTask.list = newItem
How I solved this problem (in case it helps someone else) is that I created a one to many relationship for Item (the list of lists), and one to one relationship for Tasks (tasks are a subset of a list). Now, when I create a new task in the persistence script, I have a pointer back to the list it belongs to - hence the single line of code above.
The additional background that helped me understand Core Data relationships can be found in the link above. This snippet in particular was very helpful (in case the page goes 404):
Adding a new object, and setting the relationship
Assume that you have a reference to a Company object already; its variable name is c. How do you add a new Employee or Product? Create the new object and set its relationship. The relationship can be configured from either direction. In this section, we will configure it from the perspective of the just-added new object. For example:
// As noted above, assume that you have a reference
// "c" to an existing Company object...
// Create and configure a new employee
let peter = Employee(context: m.ds_context)
peter.name = "Peter McIntyre"
peter.age = 23
// etc.
// Now, set the relationship
peter.company = c
m.ds_save()
That’s it. If it seems too easy, well, it is easy.

Check if a Firestore query (whereField isEqualTo) did find no documents

I want to check if my Firestore query did find any documents with the specific fields I want or not. If not I would like to proceed to some other code.
Unfortunately I haven't found a solution myself to this problem. Can you help?
Code:
Firestore.firestore().collection("conversations").whereField("mainUserID", isEqualTo: MainUID)
.whereField("otherUserID", isEqualTo: otherUserId).getDocuments { (snapshot, err) in
if snapshot.exists == true { // Value of type 'QuerySnapshot?' has no member 'exists'
} else {
}
}
From the docs
A FIRQueryDocumentSnapshot contains data read from a document in your
Firestore database as part of a query. The document is guaranteed to
exist and its data can be extracted with the data property or by using
subscript syntax to access a specific field.
A FIRQueryDocumentSnapshot offers the same API surface as a
FIRDocumentSnapshot. As deleted documents are not returned from
queries, its exists property will always be true and data: will never
return nil.
with the important bit being this
The document is guaranteed to exist
so therefore a .exists option would not make sense due to the guaranteed existence of the snapshot.
One approach is to check how many documents are in the snapshot
if docs.count > 0 {
//there are docs
} else {
//there are no docs
}

Parse Platform on iOS: best way to replace changed local values with more-recently changed server values?

So imagine the following scenario, using the Parse platform on iOS:
I get a PFObject from the server, let's call it GlassChalice.
Someone else, let's say Bill Blofeld, changes GlassChalice from a different location.
Later, I make some changes to my local GlassChalice, but don't save them to the server.
Still later, I want to update GlassChalice, but I want to update it to the current server values, in other word Bill Blofeld's values. I do not want to replace the server values with my local values, and also do not want to reset my local values to the values GlassChalice was loaded with.
So if I use revert(), will I get what I want?
According to the Parse docs:
- revert Clears any changes to this object made since the last call to save and sets it back to the server state.
...but, as in my example, clearing "changes made since the last call to save" and setting it "back to the server state" aren't always the same thing.
So far this seems like the only way to guarantee the results I want, but it has one obvious problem:
public func updateObjectFromServer(_ objectToUpdate: PFObject, then doThis: (()->Void)? = nil) {
let query = PFObject.query()
query?.whereKey("objectId", equalTo: objectToUpdate.objectId!)
query?.getFirstObjectInBackground (block: {
(serverObject, error) in
if error.isNil() {
objectToUpdate["numberOfLimbs"] = serverObject?["numberOfLimbs"]
objectToUpdate["eyePlacement"] = serverObject?["eyePlacement"]
objectToUpdate["crossStitchingTalentRating"] = serverObject?["crossStitchingTalentRating"]
objectToUpdate["clamsEaten"] = serverObject?["clamsEaten"]
} else {
//handle error...
}
doThis?()
})
}
But the huge problem here is that I have to know all the key names, and type them in explicitly, for this to work.
Is there a better, more generic, way?

Swift 3 "updateChildValues" method fails with: Invalid key in object. Keys must be non-empty and cannot contain '.' '#' '$' '[' or ']''

I'm trying to update Firebase Database with email as value.
But Swift 3 "updateChildValues" method fails with an error: Invalid key in object. Keys must be non-empty and cannot contain '.' '#' '$' '[' or ']''
The code is:
let key = emailQueueRef.childByAutoId()
if user.email != nil {
inviteUpdate["\(key)"] = user.email!
}
emailQueueRef.updateChildValues(inviteUpdate)
Possibly, it fail because of dots in email. In other hand, encoding it into Base64 didn't fix it.
In the same time using "setValue" method works:
emailQueueRef.childByAutoId().setValue(user.email!)
But this approach is not quite good for me as I make it with several paths simultaneously.
I didn't find anything about that case in Firebase guides and docs
Is it a Firebase bug, or I misunderstood something?
The issue is the inviteUpdate string.
The string contains illegal characters but it's not the period . causing the problem. It's the ["(key)"] portion.
the var key is a Firebase DatabaseReference
The key you are trying to write to Firebase looks like this
["https://your_app.firebaseio.com/email_node/-KorK9Io5Y2XejgLInvj": "dude#someemail.com"]
It's not clear from the question what the actual Firebase structure should be but I think what you want is this
let key = emailQueueRef.childByAutoId()
if user.email != nil {
inviteUpdate[key.key] = user.email! //note key.key
}
emailQueueRef.updateChildValues(inviteUpdate)
which then writes this to Firebase
"-KorOLfUa9HvgDIcpIyg": "dude#thing.com"
I am going to urge you to not use email addresses as keys as it can lead to a lot of work later on.
For example, if a user changes their email address. You then need to totally delete the user node that uses it as a key, and re-write it. At the same time, anywhere else that references that key will also have to be changed.
It's usually much better to include the email as a child node
uid_0
email: "dude#someemail.com"
and then other areas can simply refer to uid_0.
After some attempt I've found my main error. I got DatabaseReference with childByAutoId() and I just missed one additional step in the guide, to get the key itself.
The resulting code is:
let keyRef = emailQueueRef.childByAutoId()
let key = keyRef.key
if user.email != nil {
inviteUpdate[key] = user.email!
}
emailQueueRef.updateChildValues(inviteUpdate)
Finally that helped.

Documentdb ReadDocumentAsync doesn't work

I have the following F# code attempting to get a user document from my documentdb database and it doesn't work. Is anyone else experiencing problems using the ReadDocumentAsync method? I am able to successfully to query my user documents with the CreateDocumentQuery method. Any help is greatly appreciated. I use my database and collection ids in place of the empty strings in the code snippet
let getUserDatabaseModel (documentClient : DocumentClient) originiatorId =
async {
let databaseId = ""
let collectionId = ""
let documentUri = UriFactory.CreateDocumentUri(databaseId, collectionId, originiatorId)
let! userDatabaseModel =
documentClient.ReadDocumentAsync(documentUri)
|> Async.AwaitTask
return userDatabaseModel
}
|> Async.RunSynchronously
Update
If I use the _rid instead of the id I get the data back. To clarify the ReadDocumentAsync seems to work using the _rid but throws the error below when using the id.
mscorlib: Exception has been thrown by the target of an invocation.
FSharp.Core: One or more errors occurred. Microsoft.Azure.Documents.Client:
The value 'left blank intentionally' specified for
query '$resolveFor' is invalid
I posted a while back on the documentdb github issues page, problems I was having with ReplaceDocumentAsync.
https://github.com/Azure/azure-documentdb-dotnet/issues/113
I wasn't too concerned about the fix because there was UpsertDocumentAsync. This issue seems to be related, or maybe their relation is just me! not being able to figure out what I'm doing wrong.
CreateDocumentUri needs consistent ids, either all ids or all _rids for the database, collection, and document

Resources