-
Notifications
You must be signed in to change notification settings - Fork 445
fix: prevent stale errors #1717
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
base: master
Are you sure you want to change the base?
Conversation
) When reusing a prepared statement request object for multiple executions, the error from a failed execution would persist and incorrectly be reported for subsequent successful executions. The fix resets `request.error = undefined` alongside the other request properties (rowCount, rows, rst) at the start of makeRequest(). Adds an integration test that verifies: 1. First execution succeeds 2. Second execution fails with divide-by-zero error 3. Third execution succeeds without the stale error from step 2
Convert the integration test from callback-based to async/await with Promises for improved readability.
Use reject/try-catch instead of resolving with error values for a more idiomatic async/await pattern. Also extract execute helper function.
Code Review: PR #1717 - Fix: Prevent Stale ErrorsOverviewThis PR fixes issue #1712 where prepared statement request objects retain error state between executions. The fix is well-targeted and includes comprehensive test coverage. ✅ Code Quality & Best PracticesStrengths:
Code location consistency: ✅ Potential Bugs or IssuesNone identified. The fix is sound:
✅ Performance ConsiderationsNo performance concerns:
✅ Security ConcernsNo security issues identified. The fix actually improves reliability by preventing error state confusion, which could theoretically lead to incorrect error handling in application code that reuses prepared statements. ✅ Test CoverageExcellent test coverage: The new test at
Test quality observations:
Minor Suggestions1. Consider edge case documentation (optional): request.error = undefined; // Reset error state for request reuse (GH#1712)2. Test assertion message clarity (very minor): // Current:
assert.strictEqual(results[1], 0, 'Third execution should return 0 (1/2 truncated to int)');
// Suggestion (more precise):
assert.strictEqual(results[2], 0, 'Third execution should return 0 (1/2 truncated to int)');Wait - actually checking the test logic: results[1] is correct since the second execution (divisor=0) doesn't add a result due to the error. The third execution is indeed results[1]. So this is correct as-is. SummaryRecommendation: ✅ APPROVE This is a high-quality fix that:
The fix demonstrates good understanding of the codebase architecture by placing the error reset exactly where other request state is initialized. Great work! Related: Closes #1712 |
Code Review - PR #1717: Fix Prevent Stale ErrorsSummaryThis PR fixes issue #1712 where prepared statements would get stuck in an error state after a failed execution. The fix correctly resets the Code Quality ✅Strengths:
Test Coverage ✅Excellent test design:
Minor suggestion: Implementation Analysis ✅Correctness:
Why this works:
Performance Considerations ✅No performance concerns:
Security Considerations ✅No security concerns:
Potential Issues / Questions1. Complete coverage of request types?
All code paths are covered ✓ 2. Edge case consideration: RecommendationsRequired: None - the PR is ready to merge as-is. Optional enhancement ideas (not blockers):
ConclusionThis is a well-crafted fix that:
Recommendation: Approve and merge 🚀 Great work identifying and fixing this subtle state management bug! |
Code ReviewThank you for this fix! I've reviewed the changes and they correctly address the stale error state issue reported in #1712. Here's my detailed feedback: Positive Aspects
Observations and Minor Issues1. Test Structure Issue (Bug in test)The test has a scoping problem at line 168-179. The execute helper function is defined before the request variable is declared (line 192), which means it's capturing an undefined variable. This will cause a runtime error. Current order:
Fix needed: Move the execute function definition to after both request and results are declared (after line 198). 2. Error Message AssertionLine 218 uses assert.include to check for "Divide by zero" in the error message. This works, but SQL Server's actual error message is "Divide by zero error encountered." - consider using the exact string or making it more flexible depending on SQL Server version differences. Additional Considerations3. DocumentationConsider whether this subtle behavior should be documented. Users might wonder why errors don't persist across executions, though this is likely the expected/desired behavior. 4. Other Request MethodsThe fix in makeRequest covers execSql, execute, callProcedure, and newBulkLoad. Have you verified that unprepare (which also had the issue per the bug report) goes through this code path? Looking at the code, unprepare should also call makeRequest, so this should be covered, but confirming with a test would be valuable. 5. Edge Case: Multiple ErrorsFrom src/token/handler.ts:398-400, I see that when multiple errors occur, request.error is set to an AggregateError. Resetting to undefined handles this correctly, but it's worth noting that any error aggregation from a previous request is properly cleared. RecommendationOverall: Approve with minor fix required The fix is correct and well-tested, but the test will fail in its current form due to the variable scoping issue. Please fix the order of declarations before merging. Required changes:
Optional improvements:
Once the scoping issue is fixed, this PR is ready to merge! |
Revert to the original callback-based test style for consistency with the rest of the test file.
Code ReviewThank you for this PR! This is a well-crafted fix for issue #1712. Here's my detailed review: ✅ Code Quality & Best PracticesExcellent:
Test Quality:
✅ Bug Fix CorrectnessThe fix correctly addresses the root cause:
Verified by searching the codebase - ✅ Performance ConsiderationsNo performance concerns:
✅ Security ConcernsNo security issues identified:
✅ Test CoverageStrengths:
Minor Suggestions:
📋 Additional Observations
✅ RecommendationApprove with minor suggestions. The fix is correct, minimal, and well-tested. The suggestions above are truly minor enhancements and don't block merging. The PR effectively solves the reported issue and prevents stale error state from affecting subsequent prepared statement executions. Great work! 🎉 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1717 +/- ##
==========================================
- Coverage 79.92% 79.78% -0.14%
==========================================
Files 90 90
Lines 4887 4888 +1
Branches 920 920
==========================================
- Hits 3906 3900 -6
- Misses 676 681 +5
- Partials 305 307 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
|
||
| // Unprepare and close | ||
| connection.unprepare(request); | ||
| connection.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling close() without waiting for the unprepare() to complete cancels the unprepare() immediatelly so that is has no effect. While unprepare is not necessary before a close anyway, the test case is not entirely correct in this form and it's a bad example for the users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@claude Can you fix this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude finished @arthurschreiber's task in 1m 27s —— View job
Fixed test to wait for unprepare() before close()
I've fixed the issue where close() was called without waiting for unprepare() to complete.
Todo List:
- Read the test file to understand current implementation
- Fix the test to properly chain unprepare() and close()
- Commit and push the changes
Changes made:
- Modified the test at
test/integration/prepare-execute-statements-test.ts:199-202to listen for therequestCompletedevent before callingclose() - This ensures
unprepare()completes before the connection is closed - Prevents the unprepare operation from being cancelled prematurely
The fix follows the same event-driven pattern used throughout the test file, where operations are chained using requestCompleted event handlers. The test now properly waits for unprepare to finish before closing, making it a correct example for users.
Commit: ff14d7d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chdh Claude fixed this, but looking at how the prepare/unprepare API is supposed to be used, it makes very little sense to me. 😟
Looks like the callback to new Request is called when the request is unprepared? But how is the user supposed to understand the difference between a request being unprepared and a request being executed?
This seems to be a major design flaw in the current API - prepare, execute, unprepare, execSqlBatch, execSql all should take their own callbacks, and new Request should not even have a callback set.
Also I guess there should be a PreparedRequest object that's returned by prepare and that can be passed into execute and unprepare.
Or do I misunderstand the current API? Do you think the "future" API I'm laying out above is not going in the right direction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be a major design flaw in the current API -
prepare,execute,unprepare,execSqlBatch,execSqlall should take their own callbacks, andnew Requestshould not even have a callback set.
Probably execSql, execute and execSqlBatch should actually return a Response object which can be iterated over / events will be emitted on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the callback to
new Requestis called when the request is unprepared? But how is the user supposed to understand the difference between a request being unprepared and a request being executed?This seems to be a major design flaw in the current API -
prepare,execute,unprepare,execSqlBatch,execSqlall should take their own callbacks, andnew Requestshould not even have a callback set.
Yes, I had exactly this problem when I wrote my promise-based wrapper. I solved it by routing the request completion callback through a function pointer in aPreparedStatement object.
export interface PreparedStatement {
_dbConnection: DbConnection;
_request: Tedious.Request;
_requestCompletionCallback?: (err: Error, rowCount: number) => void;
parmDefs: DbParmDef[];
isPrepared: boolean;
isDisposed: boolean;
}
|
@arthurschreiber thanks. One single added statement solves the whole problem. 😉 I have added a comment for the unit test. Claude also mentioned I saw that you first attempted a promise-based test. Do you have any concrete plans to add a promisified API for Tedious? I thought about publishing a wrapper-package with a promise-based API for Tedious. I use this already internally for my projects since several years. Another epochal but due project would be to improve the Tedious API documentation with AI. But it's maybe a bit too early for that? |
The test was calling close() immediately after unprepare(), which would cancel the unprepare operation. Now the test properly waits for the unprepare requestCompleted event before closing the connection. Co-authored-by: Arthur Schreiber <arthurschreiber@users.noreply.github.com>
This is great feedback! I'm trying to see how far I can push Claude in being helpful maintaining tedious. Sorry for pulling you into this experiment. 😅
I'd love to have a promise based API! So far, I've always failed to find a way to introduce a promise based API and fix all the ugliness / mistakes in the existing API at the same time. I'm wondering if it would make sense to introduce a
Yes, I'd definitely look to investigate in that direction. I started with very basic cleanups over in #1721 - but I'd love to push this even further. |
Code Review for PR #1717: Fix Prevent Stale ErrorsSummaryThis PR fixes issue #1712 where prepared request objects retained error state across multiple executions. The fix adds ✅ Code Quality and Best PracticesStrengths:
✅ Bug Fix ValidationThe fix correctly addresses the issue:
✅ Test CoverageExcellent test design:
✅ Performance ConsiderationsNo performance impact:
✅ Security ConsiderationsSecurity improvement:
📝 SuggestionsMinor observation (not blocking): The fix is in the right place, but you might consider documenting this behavior. The // Reset request state to ensure clean execution
request.rowCount! = 0;
request.rows! = [];
request.rst! = [];
request.error = undefined;However, this is truly optional - the code is clear enough as-is. ✅ Overall AssessmentLGTM! 🎯 This is a well-crafted fix that:
The PR is ready to merge. Great work on the thorough test case that validates the fix! Review generated by Claude Code |
Yes, I think that these kind of problems can be solved by writing a sophisticated promise-based wrapper around the old API. It's also important to ensure that the promise of an active request is ended when the whole connection is closed. Another problem is that, in some places in the API, values can be passed as arrays or as Such a wrapper could be written and published as a separate NPM package as a proof of concept. Later it could be integrated into the main package as |
IIRC, |
Fixes #1712.
@chdh Mind taking a look?