FrontBase triggers howto (was: Never save objects which don't pass a test (was: searching for a weird deletion))
Hello there, after having played for a time with constraints, I have decided to use a trigger instead -- main reason is that I really want the validation performed on INSERT only; a constraint might get evaluated in other occassions, too. Nevertheless, I can't find a decent way of doing that in FrontBase. Is here anybody who knows its dialect well enough? Ideally, the trigger would look more or less like this: === create trigger TEST_PO_TRIGGER before insert on T_PRICE_OFFER referencing new po for each row when (... my complex validation condition ...) raise exception 'Price offer not valid' === presumed RAISE EXCEPTION worked in FrontBase, which it does not, nor I was able to find any other statement/function to rollback the transaction and report an error. Presumed there's no such function at all, the first fallback would be === create trigger TEST_PO_TRIGGER before insert on T_PRICE_OFFER referencing new po for each row when (... my complex validation condition ...) set po.C_UID = null === which would exploit the fact C_UID (which happens to be the PK) can't be null. Alas, again, whatever I tried, I haven't been able to find any SET syntax which would work :( Eventually, the only trigger I found working was === create trigger TEST_PO_TRIGGER after insert on T_PRICE_OFFER referencing new po for each row when (... my complex validation condition ...) update T_PRICE_OFFER set C_UID = null where C_UID=po.C_UID === but darn, that seems to be a _terribly_ convoluted work-around for the desired behaviour! As always, I'll be grateful for any advice, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
Chuck, On 20. 2. 2015, at 19:37, Chuck Hill ch...@gevityinc.com wrote: 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. I think you can do it as a Check constraint. It needs to be a boolean expression, but I am not sure it can include selects. A quick experiment would show if it can. It looks like FrontBase allows me to use SELECT in check constraints, but I've bumped into another problem -- I need also to join to access values from another table, and I don't know how to express „column from the row just being inserted, not from any other table in the SELECT“? My constraint expression (considerably simplified for readability) looks like this: (select max(po.C_PRICE) from T_PRICE_OFFER po, T_AUCTION auc where po.C_AUCTION_ID=auc.C_UID and po.C_PRICE=auc.C_MAX_PRICE and po.C_CREATION_DATEC_CREATION_DATE)C_PRICE to express a condition “PRICE of the inserted PRICE_OFFER must be higher than all PRICEs of all older PRICE_OFFERs belonging to the same auction, whose PRICE does not exceed the auction's MAX_PRICE”. The problem is with the “older PRICE_OFFERs” part (“WHERE ... po.C_CREATION_DATEC_CREATION_DATE”) -- FrontBase complains that C_CREATION_DATE is ambiguous. Well it is -- all the tables in question contain such a column -- but how should I prefix it to tell the server „this is the CREATION_DATE of the row which is being inserted“? Thanks a lot, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
Anyway, back to the original question -- On 20. 2. 2015, at 18:27, OC o...@ocs.cz wrote: 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 I don’t think you can make a rock-solid guarantee from the app code level. Can't I? That's bad. -- I have succeeded to consult with my client, and the option of „allowing to save any bid, determine whether it was valid or not in future“ is out. On the other hand, he again suggests pessimistic locking: „why don't we simply lock the auction row when user reads the data to determine whether his bid is valid, and unlock when he saves the valid bid or when he determines the bid is not valid“? I can see only one slight drawback -- unnecessary locks in case the bid validation fails -- but that should be a negligible problem, most bid attempts are valid (and _if_ they become invalid, then since other bid was entered shortly before, which would lead to optimistic exception anyway). Far as I understand though based on your === On 24. 1. 2015, at 0:12, Chuck Hill ch...@gevityinc.com wrote: I doubt that lockObject() code in EOF has been run in… maybe forever. It is highly possible that it is causing EOF to get confused and resulting in the errors below. Get rid of the lockObject() calls and see if the problem below goes away. === I guess the proper answer is „Well we can't use pessimistic row locking at all, since EOF does not support it reliably, and that's that.“ Is that right? Thanks again, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
I think it is a fair expectation that EOF may not properly support optimistic locking. I am not even sure how to set it up properly. There are some methods on EOEditingContext and EODatabaseContext, and one the EODatabaseContext Delegate. Chuck On 2015-02-21, 3:42 AM, OC wrote: Anyway, back to the original question -- On 20. 2. 2015, at 18:27, OC o...@ocs.czmailto:o...@ocs.cz wrote: 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 I don’t think you can make a rock-solid guarantee from the app code level. Can't I? That's bad. -- I have succeeded to consult with my client, and the option of „allowing to save any bid, determine whether it was valid or not in future“ is out. On the other hand, he again suggests pessimistic locking: „why don't we simply lock the auction row when user reads the data to determine whether his bid is valid, and unlock when he saves the valid bid or when he determines the bid is not valid“? I can see only one slight drawback -- unnecessary locks in case the bid validation fails -- but that should be a negligible problem, most bid attempts are valid (and _if_ they become invalid, then since other bid was entered shortly before, which would lead to optimistic exception anyway). Far as I understand though based on your === On 24. 1. 2015, at 0:12, Chuck Hill ch...@gevityinc.commailto:ch...@gevityinc.com wrote: I doubt that lockObject() code in EOF has been run in… maybe forever. It is highly possible that it is causing EOF to get confused and resulting in the errors below. Get rid of the lockObject() calls and see if the problem below goes away. === I guess the proper answer is „Well we can't use pessimistic row locking at all, since EOF does not support it reliably, and that's that.“ Is that right? Thanks again, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
I don’t know, I suggest asking on the FrontBase developers list. On 2015-02-21, 2:55 AM, OC wrote: Chuck, On 20. 2. 2015, at 19:37, Chuck Hill ch...@gevityinc.commailto:ch...@gevityinc.com wrote: 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. I think you can do it as a Check constraint. It needs to be a boolean expression, but I am not sure it can include selects. A quick experiment would show if it can. It looks like FrontBase allows me to use SELECT in check constraints, but I've bumped into another problem -- I need also to join to access values from another table, and I don't know how to express „column from the row just being inserted, not from any other table in the SELECT“? My constraint expression (considerably simplified for readability) looks like this: (select max(po.C_PRICE) from T_PRICE_OFFER po, T_AUCTION auc where po.C_AUCTION_ID=auc.C_UID and po.C_PRICE=auc.C_MAX_PRICE and po.C_CREATION_DATEC_CREATION_DATE)C_PRICE to express a condition “PRICE of the inserted PRICE_OFFER must be higher than all PRICEs of all older PRICE_OFFERs belonging to the same auction, whose PRICE does not exceed the auction's MAX_PRICE”. The problem is with the “older PRICE_OFFERs” part (“WHERE ... po.C_CREATION_DATEC_CREATION_DATE”) -- FrontBase complains that C_CREATION_DATE is ambiguous. Well it is -- all the tables in question contain such a column -- but how should I prefix it to tell the server „this is the CREATION_DATE of the row which is being inserted“? Thanks a lot, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
Chuck, On 21. 2. 2015, at 20:09, Chuck Hill ch...@gevityinc.com wrote: I think it is a fair expectation that EOF may not properly support optimistic pessimistic, I guess? locking. I am not even sure how to set it up properly. There are some methods on EOEditingContext and EODatabaseContext, and one the EODatabaseContext Delegate. Thanks! One closely related question -- I suppose there is no way to explicitly set up the isolation level and locking discipline for a particular transaction (differently from the default of the JDBC connexion URL)? Or is there one? Thanks again and all the best, OC On 2015-02-21, 3:42 AM, OC wrote: Anyway, back to the original question -- On 20. 2. 2015, at 18:27, OC o...@ocs.cz wrote: 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 I don’t think you can make a rock-solid guarantee from the app code level. Can't I? That's bad. -- I have succeeded to consult with my client, and the option of „allowing to save any bid, determine whether it was valid or not in future“ is out. On the other hand, he again suggests pessimistic locking: „why don't we simply lock the auction row when user reads the data to determine whether his bid is valid, and unlock when he saves the valid bid or when he determines the bid is not valid“? I can see only one slight drawback -- unnecessary locks in case the bid validation fails -- but that should be a negligible problem, most bid attempts are valid (and _if_ they become invalid, then since other bid was entered shortly before, which would lead to optimistic exception anyway). Far as I understand though based on your === On 24. 1. 2015, at 0:12, Chuck Hill ch...@gevityinc.com wrote: I doubt that lockObject() code in EOF has been run in… maybe forever. It is highly possible that it is causing EOF to get confused and resulting in the errors below. Get rid of the lockObject() calls and see if the problem below goes away. === I guess the proper answer is „Well we can't use pessimistic row locking at all, since EOF does not support it reliably, and that's that.“ Is that right? Thanks again, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
On 2015-02-21, 11:17 AM, OC wrote: Chuck, On 21. 2. 2015, at 20:09, Chuck Hill ch...@gevityinc.commailto:ch...@gevityinc.com wrote: I think it is a fair expectation that EOF may not properly support optimistic pessimistic, I guess? Cough. Yes. locking. I am not even sure how to set it up properly. There are some methods on EOEditingContext and EODatabaseContext, and one the EODatabaseContext Delegate. Thanks! One closely related question -- I suppose there is no way to explicitly set up the isolation level and locking discipline for a particular transaction (differently from the default of the JDBC connexion URL)? Or is there one? You could set up a second EOF stack and set the JDBC url isolation level and locking discipline to what you want and use that stack for these transactions. Chuck Thanks again and all the best, OC On 2015-02-21, 3:42 AM, OC wrote: Anyway, back to the original question -- On 20. 2. 2015, at 18:27, OC o...@ocs.czmailto:o...@ocs.cz wrote: 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 I don’t think you can make a rock-solid guarantee from the app code level. Can't I? That's bad. -- I have succeeded to consult with my client, and the option of „allowing to save any bid, determine whether it was valid or not in future“ is out. On the other hand, he again suggests pessimistic locking: „why don't we simply lock the auction row when user reads the data to determine whether his bid is valid, and unlock when he saves the valid bid or when he determines the bid is not valid“? I can see only one slight drawback -- unnecessary locks in case the bid validation fails -- but that should be a negligible problem, most bid attempts are valid (and _if_ they become invalid, then since other bid was entered shortly before, which would lead to optimistic exception anyway). Far as I understand though based on your === On 24. 1. 2015, at 0:12, Chuck Hill ch...@gevityinc.commailto:ch...@gevityinc.com wrote: I doubt that lockObject() code in EOF has been run in… maybe forever. It is highly possible that it is causing EOF to get confused and resulting in the errors below. Get rid of the lockObject() calls and see if the problem below goes away. === I guess the proper answer is „Well we can't use pessimistic row locking at all, since EOF does not support it reliably, and that's that.“ Is that right? Thanks again, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
On Feb 20, 2015, at 12:09 PM, OC o...@ocs.cz wrote: it must pass a slightly more complex test than uniqueness: - when inserted into PRICE_OFFERS table, - the newly inserted object must have value in column PRICE, which is higher, - than any (max) of the already existing objects in that table, which - have YES in a boolean column VALID, and - at the same moment, have same value in AUCTION_ID column as the inserted object. (In fact the real condition is even more complex, but this is the gist of it: consider an auction system, where a new bid added to a particular auction must be higher than all previous valid bids for the same auction.) Nevertheless, I believe that when we are pursuing the implement-the-behaviour-at-the-application-level way (unlike the check restraint at the DB level), the particular TEST is actually irrelevant. The gist is that it must not be possible to store an object which does not pass TEST -- whatever the TEST tests. Why is this a requirement? If highest price wins, then you only need to select the max price row where price offer date is less than auction end. If a few offers get thrown in there out of order, how does that break anything? See please again the [1] above -- the code must make sure that (a) when TESTing, the participating objects are a proper snapshot of database contents of some moment in the past (b) when saving, the code must make sure that if the values of the snapshot did change, the saving won't happen That should be sufficient, should it not? Does it make sense? Thanks a lot, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Never save objects which don't pass a test (was: searching for a weird deletion)
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 (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
On 2015-02-20, 12:30 PM, Ramsey Gurley wrote: On Feb 20, 2015, at 12:09 PM, OC o...@ocs.czmailto:o...@ocs.cz wrote: it must pass a slightly more complex test than uniqueness: - when inserted into PRICE_OFFERS table, - the newly inserted object must have value in column PRICE, which is higher, - than any (max) of the already existing objects in that table, which - have YES in a boolean column VALID, and - at the same moment, have same value in AUCTION_ID column as the inserted object. (In fact the real condition is even more complex, but this is the gist of it: consider an auction system, where a new bid added to a particular auction must be higher than all previous valid bids for the same auction.) Nevertheless, I believe that when we are pursuing the implement-the-behaviour-at-the-application-level way (unlike the check restraint at the DB level), the particular TEST is actually irrelevant. The gist is that it must not be possible to store an object which does not pass TEST -- whatever the TEST tests. Why is this a requirement? If highest price wins, then you only need to select the max price row where price offer date is less than auction end. If a few offers get thrown in there out of order, how does that break anything? That seems to make sense. You can save the bid, whatever it is, then immediately fetch any higher bid. If there was one, show the user an you were just outbid message. Chuck See please again the [1] above -- the code must make sure that (a) when TESTing, the participating objects are a proper snapshot of database contents of some moment in the past (b) when saving, the code must make sure that if the values of the snapshot did change, the saving won't happen That should be sufficient, should it not? Does it make sense? Thanks a lot, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
On 2015-02-20, 9:27 AM, OC wrote: 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. I think you can do it as a Check constraint. It needs to be a boolean expression, but I am not sure it can include selects. A quick experiment would show if it can. 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? I am not quite following the requirements. Is it unique only for this object or for all objects? Chuck 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 (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: Never save objects which don't pass a test (was: searching for a weird deletion)
Chuck, On 20. 2. 2015, at 19:37, Chuck Hill ch...@gevityinc.com wrote: Actually _this_ should not be weird, this should be quite a normal code; the only requirement is that check-and-save, i.e., conceptually, === [1] 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 ... 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? I am not quite following the requirements. Is it unique only for this object or for all objects? Sorry I have mislead you by using the term “UNIQUE“. It does not have to be unique; it must pass a slightly more complex test than uniqueness: - when inserted into PRICE_OFFERS table, - the newly inserted object must have value in column PRICE, which is higher, - than any (max) of the already existing objects in that table, which - have YES in a boolean column VALID, and - at the same moment, have same value in AUCTION_ID column as the inserted object. (In fact the real condition is even more complex, but this is the gist of it: consider an auction system, where a new bid added to a particular auction must be higher than all previous valid bids for the same auction.) Nevertheless, I believe that when we are pursuing the implement-the-behaviour-at-the-application-level way (unlike the check restraint at the DB level), the particular TEST is actually irrelevant. The gist is that it must not be possible to store an object which does not pass TEST -- whatever the TEST tests. See please again the [1] above -- the code must make sure that (a) when TESTing, the participating objects are a proper snapshot of database contents of some moment in the past (b) when saving, the code must make sure that if the values of the snapshot did change, the saving won't happen That should be sufficient, should it not? Does it make sense? Thanks a lot, OC ___ Do not post admin requests to the list. They will be ignored. Webobjects-dev mailing list (Webobjects-dev@lists.apple.com) Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com