CKFetchRecordsOperation never returns any keys - swift

I have a single record in my public (development) database, with several populated fields. When I use a CKFetchRecordsOperation to fetch that record, the fetch succeeds, but the resulting CKRecord always contains no keys. This happens whether I explicitly set desiredKeys on my fetch operation, or leave it as nil. The record and its keys have been unchanged for a long time, so it's not a propagation delay.
There's only one record in the public database and its ID matches what I get, so I'm sure the correct record is being fetched; it just never has any keys. For example, the below code always prints keys: [].
let op = CKFetchRecordsOperation(recordIDs:[...])
op.desiredKeys = [...] // commenting this has no effect
op.fetchRecordsCompletionBlock = {records, error in
if let e = error {
print("error:", e)}
for (id, r) in records ?? [:] {
print("keys:", r.allKeys())}}
db.addOperation(op)
I've also tried accessing the individual keys via subscript notation, but they're always nil. I'm at a loss at this point. There are no errors, and the record I want is being found, so why are there never any keys?
Update: I've noticed that the CloudKit Dashboard always says "Reindexing development" in the bottom left corner, even days after my last change. Furthermore, I tried creating a second record (via the Dashboard) and my fetch fails to even find it, after leaving plenty of time for propagation.
Following a link from this SO question to an Apple developer forum thread about this problem, I found one post suggesting it happens only to records with Asset fields. I will try experimenting when I have time, though I can't avoid using Assets in this case so it might not be much help.
Update 2: I tried resetting my development environment and then recreating the same records as before. On the plus side, the "Reindexing" message in the CloudKit Dashboard is finally gone. Unfortunately, my code now fails to find any of my records, even though the code remains unchanged and I've triple-checked everything.
I also tried creating a record type with no Asset fields (see first update above), but it didn't seem to change anything.

Turns out I was querying the private database, when the records I wanted were in the public one.

Related

RTK Query manual cache update without known previous query args

How to make optimistic/pessimistic cache update (probably with updateQueryData), but without knowing the arguments of previous queries?
updateQueryData(
"getItems",
HERE_ARE_THE_ARGUMENTS_I_DONT_HAVE,
(data) => {
...
}
)
For example:
getPosts query with args search: number
updatePosts mutation
Example actions:
Go to the table, first request getPosts with search = "" is cached.
Write in search input abc.
Second request getPosts with search = "abc" is cached.
I update one element within the table with pessimistic update - successfully modifying cache of second request (previous step)
Clear search input
Table shows the same state from first step, even if one element should be modified
But I need universal solution. I don't know how many different cached entries will be there. And also my case is more complex, because I have other args than "search" to worry about (pagination).
Possible solutions??
It would be perfect if there's a method to access all previous cached queries, so I could go with updateQueryData for each of them, but I cannot find simple way to do that.
I thought about accessing getState() within onQueryStarted and retrieving query parameters from there (in order to do above), but it's not elegant way
I thought about looking for a way to invalidate previous requests without invalidating the last request, but also cannot find it
Okay, I found solution here
Is it possible to optimistically update all caches of a endpoint?
Using
api.util.selectInvalidatedBy(getState(), { type, id }) gives you in return list of cached queries with endpointNames and queryArgs. Then you can easily mutate these caches by updateQueryData.
There's also new problem created here, which is situation where modifying some particular properties may change response from API in unpredictable way. For example in a list you may sort by status, which you can modify. Then changing the cache would be bad idea and instead of that I've created new tag which is invalidated with each update { type: "getItems", id: "status" } and for getItems it would be like { type: "getItems", id: sortBy }

How to Be Notified if the Owner Removes Me from a CKShare on CloudKit

Let's say the owner of a record shares it with me. I get sent a share link and I open it and accept the share like this:
let operation = CKAcceptSharesOperation(shareMetadatas: [metadata])
operation.acceptSharesCompletionBlock = { error in
if let error = error{
print("accept share error: \(error)")
}else{
//Share accepted...
}
}
CloudKit.container.add(operation)
I am also previously subscribed to the Shared database already like so:
let subscriptionSharedDatabase = CKDatabaseSubscription(subscriptionID: "subscriptionSharedDatabase")
let sharedInfo = CKSubscription.NotificationInfo()
sharedInfo.shouldSendContentAvailable = true
sharedInfo.alertBody = "" //This needs to be set or pushes don't get sent
subscriptionSharedDatabase.notificationInfo = sharedInfo
let subShared = CKModifySubscriptionsOperation(subscriptionsToSave: [subscriptionSharedDatabase], subscriptionIDsToDelete: nil)
CloudKit.sharedDB.add(subShared)
But now let's say the owner of the CKShare removes me as a participant on that record and saves the updated participants list to CloudKit.
As far as I can tell, the only notification I get is another shared database subscription (subscriptionSharedDatabase) change, but no records are changed or deleted (I looked and there are no changed records when I fetch them).
As far as I know, the only way to be notified of changes to the participants on a CKShare is to subscribe to notifications on the cloudkit.share record type, but that isn't available to me in the shared database, right?
How can I be notified when I am removed from a CKShare?
Interesting how this has no answers. I just implemented some CKShare-related code and it seems to work fairly predictably. Here is the basic approach.
1) at the start of my app, I do CKFetchRecordZonesOperation.fetchAllRecordZonesOperation() on the shared database, to get all the current record zones that are shared with me.
2) I set up CKDatabaseSubscription on the shared database as you suggest.
3) Upon receiving this notification on the shared database, I do a batch CKFetchRecordZoneChangesOperation across all the shared record zones. (You can pass it multiple record zones, together with a server change token for each zone, to do a bulk updates query.)
4) If any shares were unshared but the record zones themselves are still valid, I observe that the recordWithIDWasDeletedBlock is run twice, once with the unshared CKRecord and once with the related CKShare.
5) (I didn’t yet fully figure out this part) if the share was the last one for a given user, the whole shared record zone from that user gets removed from my shared database. I didn’t yet fully figure out how to best handle this part. I could query for fresh record zones every time I get a CKDatabaseNotification, but that seems wasteful. I see that I can run CKFetchDatabaseChanges operation that informs me of changed zones, but I have yet to figure out when is the best time to run it.

Swift and Cloud Firestore Transactions - getDocuments?

Transactions in Cloud Firestore support getting a document using transaction.getDocument, but even though there is a .getDocuments method, there doesn’t seem to be a .getDocuments for getting multiple documents that works with a transaction.
I have a Yelp-like app using a Cloud Firestore database with the following structure:
- Places to rate are called spots.
- Each spot has a document in the spots collection (identified by a unique documentID).
- Each spot can have a reviews collection containing all reviews for that spot.
- Each review is identified by its own unique documentID, and each review document contains a rating of the spot.
Below is an image of my Cloud Firestore setup with some data.
I’ve tried to create a transaction getting data for all of the reviews in a spot, with the hope that I could then make an updated calculation of average review & save this back out to a property of the spot document. I've tried using:
let db = Firestore.firestore()
db.runTransaction({ (transaction, errorPointer) -> Any? in
let ref = db.collection("spots").document(self.documentID).collection("reviews")
guard let document = try? transaction.getDocuments(ref) else {
print("*** ERROR trying to get document for ref = \(ref)")
return nil
}
…
Xcode states:
Value of type ‘Transaction’ has no member ‘getDocuments’.
There is a getDocument, which that one can use to get a single document (see https://firebase.google.com/docs/firestore/manage-data/transactions).
Is it possible to get a collection of documents in a transaction? I wanted to do this because each place I'm rating (spot) has an averageRating, and whenever there's a change to one of the ratings, I want to call a function that:
- starts a transaction (done)
- reads in all of the current reviews for that spot (can't get to work)
- calculates the new averageRating
- updates the spot with the new averageRating value.
I know Google's FriendlyEats uses a technique where each change is applied to the current average rating value, but I'd prefer to make a precise re-calculation with each change to keep numerical precision (even if it's a bit more expensive w/an additional query).
Thanks for advice.
No. Client libraries do not allow you to make queries inside of transactions. You can only request specific documents inside of a query. You could do something really hacky, like run the query outside of the transaction, then request every individual document inside the transaction, but I would not recommend that.
What might be better is to run this on the server side. Like, say, with a Cloud Function, which does allow you to run queries inside transactions. More importantly, you no longer have to trust the client to update the average review score for a restaurant, which is a Bad Thing.
That said, I still might recommend using a Cloud Function that does some of the same logic that Friendly Eats does, where you say something along the lines of New average = Old average + new review / (Total number of reviews) It'll make sure you're not performing excessive reads if your app gets really popular.

Cloud SQL API Explorer, settingsVersion

I'm getting familiarized with Cloud SQL API (v1beta1). I'm trying to update authorizedNetworks (sql.instances.update) and I'm using API explorer. I think my my request body is alright except for 'settingsVersion'. According to the docs it should be:
The version of instance settings. This is a required field for update
method to make sure concurrent updates are handled properly. During
update, use the most recent settingsVersion value for this instance
and do not try to update this value.
Source: https://developers.google.com/cloud-sql/docs/admin-api/v1beta3/instances/update
I have not found anything useful related to settingsVersion. When I try with different srings, instead of receiving 200 and the response, I get 400 and:
"message": "Invalid value for: Expected a signed long, got '' (class
java.lang.String)"
If a insert random number, I get 412 (Precondition failed) and:
"message": "Condition does not match."
Where do I obtain versionSettings and what is a signed long string?
You should do a GET operation on your instance and fetch the current settings, those settings will contain the current version number, you should use that value.
This is done to avoid unintentional settings overwrites.
For example, if two people get the current instance status which has version 1, and they both try to change something different (for example, one wants to change the tier and the other wants to change the pricingPlan) by doing an Update operation, the second one to send the request would undo the change of the first one if the operation was permitted. However, since the version number is increased every time an update operation is performed, once the first person updates the instance, the second person's request will fail because the version number does not match anymore.

memcached sometimes holding corrupt data

I have been using Memcached (AWS Elasticache) for a while now.
Just today I ran into a situation that I hadn't experienced before. Regularly there is a call to the database for a list of countries and I store this in memcached. This time however the data wasn't stored correctly (I'm not sure why as it has worked fine for months) but after looking over the code & trying code based fixes (assuming something was wrong with the site code) a bounce of the cache fixed the issue. Note: I had bounced memcached the day before so maybe it didn't warm up correctly etc.
My Question is - currently I check to see if the memcached key exists and if it does I use the data. Only if the memcached key doesn't exist do I query the database and populate the key. Do I also need to validate the data somehow to so I can be sure its not corrupt or should this be seen as an infrequent issue (which it is) and left at that.
Also I believe the memcached key didn't have any data in it so maybe just checking if the key is empty is good enough...
Code below:
public $countryList = array();
// Countries, Country Code, Zip Enabled --- 'generic::countryList::'.$_SESSION['language']'
public function countryList() {
$elasticache = new elasticache();
if(!$this->countryList = $elasticache->memcached->get('generic::countryList::'.$_SESSION['language'])) {
--- this is where the database query code is
$elasticache->memcached->set('generic::countryList::'.$_SESSION['language'], $this->countryList, 2592000);
}
}
I guess confirming the data in the key is correct would required a database call and therefore would defeat the purpose of memcached....
thoughts & ideas?