Never save objects which don't pass a test (was: searching for a weird deletion)
Never save objects which don't pass a test (was: searching for a weird deletion)
- Subject: Never save objects which don't pass a test (was: searching for a weird deletion)
- From: OC <email@hidden>
- Date: Fri, 20 Feb 2015 18:27:04 +0100
Chuck,
>> Actually _this_ should not be weird, this should be quite a normal code; the only requirement is that check-and-save, i.e., conceptually,
>>
>> ===
>> if (TEST(eo.someRelationship().someAttribute(),newAttributeValue)) {
>> def new=EOUtilities.createAndInsertInstance(eo.editingContext(),'SomeRelationshipTarget')
>> new.someAttribute=newAttributeValue
>> eo.editingContext().saveChanges()
>> }
>> ===
>>
>> so that I can be absolutely sure that nobody stores an attribute value which -- at the moment of COMMITTing the appropriate INSERT -- would not pass the TEST
Alas, since the TEST is (slightly) more complex than A!=B, I can't use a UNIQUE db restraint.
One way would be to twist the DB to do the complete restraint to me, something like to a pseudo-code “inserted_row.attr>=MAX(SELECT attr FROM this_table WHERE another_attr.isValid) AND couple more similar conditions” -- frankly I am not sure whether the database (FrontBase) can do that at all, and if it can, definitely I don't know how to.
Nevertheless this is an interesting idea which I am going to pursue (if anybody happens to know the solution, either how to, or that it is not possible at all, of course I'll be grateful for an advice, before I dive into that).
Another way, the one I've tried to exploit so far, was implement the behaviour app-side:
> I believe that is the only way to absolutely ensure this. I don’t think you can make a rock-solid guarantee from the app code level.
Can't I? That's bad.
So far, I thought this very simple concept should be rock-solid, but probably I am overlooking something of importance, as so often:
=== eo's entity locks on the someRelationship FK (among others) ===
OSC.lock() // possibly superfluous; simplifies situation by serializing intra-instance
try {
ec.unlock(); ec.lock() //* to make sure we get changes from other ECs now, by your excellent advice
def rel=eo.someRelationship() // in DB there might be a newer value (saved meantime by another instance)...
def attr=rel.someAttribute() // ... but it is not possible that in DB is an _older_ value than this
if (TEST(attr,newAttributeValue)) {
def new=EOUtilities.createAndInsertInstance(eo.editingContext(),'SomeRelationshipTarget')
new.setSomeAttribute(newAttributeValue) // once set, I NEVER change this value
eo.addObjectToBothSidesOfRelationshipWithKey(new,'someRelationship')
eo.editingContext().saveChanges() // catching optimistic exceptions and repeating the process if they happen
}
} finally {
OSC.unlock()
}
===
My reasoning is that
- intra-instance, consistency is ensured by locked (single) OSC and by //* -- I am sure that I see the latest eo.someRelationship and its rel.someAttribute before saving, and thus the TEST is reliable; my own instance, even with concurrent requests, can't do anything wrong
- inter-instance, the optimistic locking based on someRelationship FK should prevent saving in case any other instance succeeded to save its own new attribute meantime.
What am I overlooking, how can this pattern fail?
In fact, to decrease the probability of the optimistic locking exception, I force re-fetch (in my new multi-instance code, not the single-instance old one), like this:
===
OSC.lock() // precisely same as above
try {
ERXEC tempec=ERXEC.newEditingContext()
tempec.fetchTimestamp=System.currentTimeMillis() // due to this, I don't need "ec.unlock(); ec.lock()", for...
def tempeo=eo.localInstanceIn(tempec)
def rel=tempeo.someRelationship() // ... whatever was cached in ECs, current FK from eo's table gets fetched now
def attr=rel.someAttribute() // ... just like its attribute from rel target's table
if (TEST(attr,newAttributeValue)) { // precisely same as above (the only difference is that the values are newer...
def new=EOUtilities.createAndInsertInstance(tempec,'SomeRelationshipTarget')
new.setSomeAttribute(newAttributeValue)
tempeo.addObjectToBothSidesOfRelationshipWithKey(new,'someRelationship')
tempec.saveChanges() // ... and thus the probability of optimistic locking exception (caused by different relationship FK) is smaller (though of course not zero)
}
} finally {
OSC.unlock()
}
===
Are even these patters unsafe? Why?
Thanks a big lot,
OC
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Webobjects-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden