Uh oh!
There was an error while loading. Please reload this page.
sqlite: handle exceptions in filter callback database.applyChangeset() - #56903
sqlite: handle exceptions in filter callback database.applyChangeset()#56903louwers wants to merge 4 commits into
Conversation
| item.c_str(), | ||
| NewStringType::kNormal) | ||
| .ToLocalChecked()}; | ||
| MaybeLocal<Value> maybe_result = |
There was a problem hiding this comment.
I don't think you need the TryCatch here. The problem is the use of ToLocalChecked(). Take a look at this code. You can tell if V8 has an exception pending if the ToLocal() call does not succeed.
@jasnell@cjihrig Actually I don't think we should propagate the exception. Because if different exceptions are thrown only the last is propagated. You can't really abort the whole application of the changeset from the filter callback. So I just interpret an exception as "do not include changes from this table". |
cjihrig
commented
Feb 3, 2025
Can't we stop attempting more work once the first exception is detected? |
jasnell
commented
Feb 3, 2025
Unfortunately we need to. For instance, the exception thrown could be something like a fatal out of memory or some other condition where we really shoiuld not just ignore and proceed. Also, not throwing the error can lead to subtle bugs in user code where their filter may be throwing an error that gets swallowed. Any error thrown by the filter callback needs to be propagated. |
Not from the filter callback. SQLite is calling us at that point.
@jasnell What should we do when multiple errors are thrown? |
cjihrig
commented
Feb 3, 2025
It looks like there is already a transaction in place, we just need to verify:
Once the first error is thrown we should avoid trying to do any more work that calls into JavaScript so that no more exceptions occur. |
louwers
commented
Feb 3, 2025
@cjihrig In that case we should change the API. Instead of a function we can think about passing a Set of table names or a regular expression. |
cjihrig
commented
Feb 3, 2025
Once the first exception is detected, wouldn't it make sense to return |
@cjihrig The filter callback cannot abort, that is the conflict handler callback (only called when a conflict is detected, not for every change). |
Maybe the filter callback is called prior to applying any changes. If that is the case it would be possible. I doubt it though, because that would probably require more memory. |
cjihrig
commented
Feb 3, 2025
I think you'll need some state in the C++ code that can be shared between the filter and conflict functions. If the filter function creates a JS error, let it be thrown, but also roll back in the conflict function. |
@cjihrig That won't work, because the conflict handler is not called if there are no conflicts. Sorry, edited my comment too late. |
cjihrig
commented
Feb 3, 2025
OK, I'll have to look into the code in more detail when I'm not at work 😄. In the worst case, once the first exception is encountered, we should probably return |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@## main #56903 +/- ##
=======================================
Coverage 89.17% 89.18% =======================================
Files 665 665 Lines 192602 192609 +7 Branches 37057 37057 =======================================
+ Hits 171755 171771 +16
Misses 13657 13657 + Partials 7190 7181 -9
|
cjihrig
commented
Feb 4, 2025
I looked into this a bit more, and it appears that the tables in the changeset are looped over when we call What I believe we need to do is:
Does that make sense? If my understanding of the control flow is correct, that should prevent us from ever getting in a situation where there are multiple exceptions. |
As I said in my previous comment, the conflict handler is not called when there are no conflicts... So that approach will not work. |
Then I think the only options are:
We will still need some logic like what I laid out in my previous comment though because we need to stop executing JS when there is an exception. The only difference is maybe |
louwers
commented
Feb 4, 2025
OK this is doable. We could use |
Uh oh!
There was an error while loading. Please reload this page.
addaleax
left a comment
There was a problem hiding this comment.
Looks good!
I think it would be an improvement to apply the same steps to the conflict callback, i.e. remove the TryCatch there and instead handle an empty MaybeLocal value, just like you're doing here.
If you want to improve on it even more, I think ideally we'd want to keep track of whether one of the callbacks has seen an exception (i.e. empty MaybeLocal result) on the context object, and not try to call any JS function if a previous call had already thrown.
| if (maybe_result.IsEmpty()) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Not technically wrong, but you don't need this because you already handle the empty case in the next conditional
| if (maybe_result.IsEmpty()) { | |
| returnfalse; | |
| } |
There was a problem hiding this comment.
I will still add some logic here, I think this branch is taken when an exception is thrown, correct?
There was a problem hiding this comment.
Yes, it should be taken when filterFunc throws an exception. But the point of the comment here is that in the next condition maybe_result.ToLocal(&result) returns false in the same case.
cjihrig
commented
Apr 5, 2025
@louwers are you still working on this (it's marked as a draft)? |
louwers
commented
Apr 6, 2025
@cjihrig Yes, I still intend to finish this. I got stuck trying to write a RAII wrapper to create and roll back a savepoint. Do we have something like that already? Otherwise I think I will handle it manually. |
cjihrig
commented
Apr 7, 2025
OK, thanks. No, nothing like that exists yet as far as I know. |
Renegade334
commented
Apr 21, 2025
@louwers: I completely missed that you'd been working on this, and drafted up d017d7735de233a0dd9550d8061325a9a841c7c8. If it's worthless to you then no worries, but figured I'd share just in case! |
louwers
commented
Apr 21, 2025
@Renegade334 OK thx I can use that. |
louwers
commented
Sep 28, 2025
Part of this PR was merged with #59848. Will make another PR. |
Resolves#56890
Also adds additional test coverage for filter callback and clarifies behavior in documentation.