-
Notifications
You must be signed in to change notification settings - Fork 363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Relationship regression: Removing and inserting an object breaks the relationship #274
Comments
Also, if I copy and paste the last part of the implementation of handleInsertObject:forCollectionKey:withMetadata:rowid: from 2.7 into 2.8, the behavior returns to what I would expect. |
After looking this over, it appears the problem is much worse than expected. The issue is "rowid recycling". That is, sqlite appears to use a "free rowid pool". So if, for example, rowid 16 is deleted, and then another object is inserted (during the same transaction), then the inserted row will get rowid 16.
It was removed because, as I was updating the code, I noticed the check didn't make any sense. If (collection, key) is removed from the database, and then inserted again, there's no guarantees that it will receive the same rowid. In fact, it's highly likely the rowid will change. You can test this in the following manner: [transaction removeObjectForKey:@"A" inCollection:nil];
[transaction removeObjectForKey:@"B" inCollection:nil];
[transaction setObject:@"B" forKey:@"B" inCollection:nil];
[transaction setObject:@"A" forKey:@"A" inCollection:nil]; In the above scenario, "A" and "B" will swap rowids. This doesn't affect most extensions (or the core database) because deletes are handled immediately. But the relationship extension, with it's queue & flush logic, presents a problem. In other words, its going to require some significant changes to properly fix this problem. I'm looking forward to doing it. Especially since finding a proper solution now will prevent future extensions from going down this flawed architecture road. But it's going to take me a little while to get there (because of other responsibilities). So I've moved it to 2.8.3, as 2.8.2 already has several fixes I'd like to get out the door. |
Thanks for the explanation. Is this something I should worry about in 2.7.x as well? I didn't notice this until I tried out 2.8. |
Unfortunately, all versions of YapDatabaseRelationship have had this bug. |
Thanks, good to know. In case anyone else runs into this, I worked around this by not calling So instead of: [transaction removeObjectForKey:@"A" inCollection:@"x"]; [transaction removeObjectForKey:@"B" inCollection:@"x"]; [transaction removeObjectForKey:@"C" inCollection:@"x"]; [transaction setObject:a forKey:@"A" inCollection:@"x"]; [transaction setObject:b forKey:@"B" inCollection:@"x"]; I'm doing NSMutableArray *keysToRemove = [@[@"A", @"B", @"C"] mutableCopy]; [transaction setObject:b forKey:@"A" inCollection:@"x"]; [keysToRemove removeObject:@"A"]; [transaction setObject:b forKey:@"B" inCollection:@"x"]; [keysToRemove removeObject:@"B"]; [transaction removeObjectsForKeys:keysToRemove]; Thanks for your continued work on YapDatabase, no regrets having switched to it! |
I've run into this issue a second time (in issue #543), and found myself investigating it again. I only wish I could remember these visits, so I could figure out what's going on more quickly :-). This time I wrote some unit tests that expose one version of it: I found another workaround for this issue (it resolves the issue in the unit tests that I wrote). Since the issue is that rowids are being re-used when objects get deleted, why not enable the AUTOINCREMENT keyword on the rowid property? This will prevent a new object from being assigned an old rowid and confusing the relationships extension. This is a bit of a hack, and I don't know if this is a fix for all issues caused by this pattern. It also doesn't fix existing databases, but that would be possible with an update that altered the rowid property. The biggest downside is probably that it burns through ROWIDs faster, but that doesn't seem to be an issue with the intended use cases of YapDatabase. This solution is imperfect, but if a major change is required to fix this properly, ensuring that ROWIDs are unique is an easy solution while a redesign of the relationship extension is contemplated. |
A change in 0b1dba8 broke the following situation:
Before 0b1dba8, this was handled in handleInsertObject:forCollectionKey:withMetadata:rowid: by checking deletedInfo and removing any rows that were reinserted (
YapDatabase/YapDatabase/Extensions/Relationships/YapDatabaseRelationshipTransaction.m
Line 3080 in b5edcb8
I'm not sure why that check was removed, but I believe that's the source of the change in behavior.
The text was updated successfully, but these errors were encountered: