Anyway to specify a unique constraint in Cloudkit record?

I see in CKError.h there is an error: CKErrorConstraintViolation.


Is there a way to specify a unique constraint for a column in a CloudKit record?


The only part of a CloudKit record that enforces uniqueness as far as I can is recordName (which is specified via CKRecordID). This has limitations though. One being recordName must be 255 characters or less. For the most part, using user input on some field which must be unique in a user created record can be used as a record name to ensure uniqueness. In most cases, this will be under 255 characters but not always though.


During testing, I'm just using a hash string for the record name, which will fit in 255 characters:


NSString *someStringThatRepresentsUniqueValue = //...

NSString *ckRecordName = [NSString stringWithFormat:@"%lu",someStringThatRepresentsUniqueValue.hash];


But my concern with this is if that the implementation of hash could change in an OS update. Yea, I could hash myself, but my question still stands: is there no way to just make a unique constraint in the database for a custom field?

Replies

If you are looking for something unique use the UUIDString of NSUUID:

https://developer.apple.com/documentation/foundation/nsuuid/1416585-uuidstring?language=objc


The hash of the same string "JaneDoe", no matter how long, will always be the same. So hashing something doesn't give you uniqueness unless the something is unique.

Thanks for the reply.


>The hash of the same string "JaneDoe", no matter how long, will always be the same. So hashing something doesn't give you uniqueness unless the something is unique.


Indeed, that something is considered unique. In my local SQLite database, the table column which maps to this has a unique constraint, so the local database will not allow duplicate entries with the same value for this column. It would be a nice feature if the Cloudkit server could do the same thing, but I guess it can't.


Using the hash of a string for a record name (which is intended to be unique) works in my particular case right now, but I think I'll use another hashing algorithm instead of using the -hash method on NSString, since a different version of iOS or macOS may return a different hash value for the same string.


In a more complex case where other fields ought to be unique on the server, I guess you have to query before inserting to ensure you don't get duplicates.

1) "Indeed, that something is considered unique" - why do you need to hash it in the first place?


2) "I guess you have to query before inserting to ensure you don't get duplicates" This actually works quite well if you want to have a simple number. For example, 6 simple digits will give you 1,000,000 possible entries if you check for uniqueness but, at most, 1,000 if you don't check.


3) "I guess you have to query ....to ensure......" This is a general philosophy of databases - it was the basis of the answer I proposed for your 'change token' problem.

>why do you need to hash it in the first place?


Because a CKRecordID's recordName has a limit of 255 characters, and this unique field is user generated. It's unlikely that the user would enter something over 255 characters in this unique area, but not impossible and I shouldn't assume it to always be the case. Hashing will ensure the recordName fits in 255 characters.


>3) "I guess you have to query ....to ensure......" This is a general philosophy of databases - it was the basis of the answer I proposed for your 'change token' problem.


Not sure what change token problem you are referring to? I'm not against querying but in Sql there is a concept called a unique constraint. If you use it, and try to insert a duplicate row that violates the unique constraint, the database won't let you. It's a nice feature to have so I don't think this is an unreasonable thread. This feature I think would be even more useful if you're talking to a database on a server (like CloudKit), since it can reduce the number network requests.


So this isn't my app's model but say for example say you have a table of social security numbers with 1 column: numbers. There can be no duplicate entries on this table. Social security numbers are unique. So you make a unique constraint.


Example with Unique Constraint Feature:


Create a social security record and send it to the server -> Server determines that this doesn't violate unique constraint -> Server responds with success


Example with No with no unique constaint feature:


Query the server for the existance of the social security number -> Server responds that no such record exists -> Create a social security record send it to the server.

--


There's an extra trip over the network without the unique constraint feature.

I don't think .hash will work for you. If you want uniqueness, use NSUUID or query the database


    NSLog(@"the hash of %lu   is equal to the hash of  %lu",
@"1234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345
7
7890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
.hash,
@"1234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345
6
7890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
.hash);

.hash won't work if the implementation of NSString's hash is different on different OSes (or different versions of an OS). But I can use a hashing algorithm, and all clients will generate the same hash for the given input data if there is a collision.


How would you propose I use NSUUID for this? Isn't NSUUID randomly generated? How would that protect against duplicate records being sent to the server from different devices?


Device 1 creates record with some unique string: @"1234"

Device 2 creates record with some unique string: @"1234"


If i hash the @"1234" string, i'll achieve what I'm after (a sort of a unique constraint). Of course there are limitations to this (you can only have 1 per record type, because CKRecordID's recordName is the only 'unique thing' enforced by the server).


Of course the above is just an example, no reason I would need to hash if all strings were that short. If I just dump a NSUUID's string as the record name in the record id, from both devices, don't see how this accomplishes anything. Two @"1234" records will get inserted in the database, and they'll have a different value for recordId.recordName.

I am simply answering your question about getting a unique identifier. I thought you were proposing to use the .hash value of a string that you know to be unique. I am simply pointing out that the .hash of a unique string is not unique. (You would need to use a better hash method than .hash) I am suggesting that to get a unique identifier you just generate an NSUUID - it will be unique.

> thought you were proposing to use the .hash value of a string that you know to be unique.


I'm proposing to use the hash value of a string that should be unique. I cannot know whether or not the string is unique, because the user makes the data. If the user enters something that collides with record name on the server that should be unique, the server will send my applicatioin back an error, which is what I want, like a unique constraint in SQL.

Hi everyone! Is this a feature yet? Here is my use case...

I have an iOS app using CloudKit for the backend. I have a record for custom user called MyUser. Each MyUser record has a field called username. I want to let the users edit their usernames on the app but I also want to make sure there are no duplicate usernames. If a user tries to change their username to a username that is already taken I want them to see an error. If I could mark the username field as must be unique on CloudKit, then when I send the network request to change the username CloudKit will send an error back if it is taken. Without this, I can implement my own check to see if a username is taken by querying the MyUser records by username. But what if two different apps query for the same username at the same time... yes there is only a very small chance of this happening but it is NOT impossible. The queries would return with no users ( aka username is available ) and then both users would send the next network request to change their usernames to the same username. Is there a better way to achieve this? I think marking the username field on the MyUser record as "isUnique" would be very handy.