Repository navigation
fix(engine) #5628: classify client errors on the postgres, redis, mongodb and graphql wire paths - #5700
Conversation
…godb and graphql wire paths #5602 gave arithmetic failures their own type so a wire layer could report them as client errors, but only HTTP and Bolt were wired. The other protocols reported every execution failure through one generic arm, so a caller who divided by zero, hit a unique index or lost an optimistic-concurrency race got a code meaning the server had broken. Adds com.arcadedb.exception.ErrorCategory, which walks the cause chain once with CauseChain so every protocol classifies the same way, and gives each module the table that translates a category into its own vocabulary: a SQLSTATE for Postgres, a RESP kind word for Redis, an error code for MongoDB. RETRY is decided before ARITHMETIC, matching the precedence BoltNetworkExecutor already documents. GraphQL additionally rewrapped every failure raised by the statement a directive delegates to as a CommandParsingException, reporting runtime errors as invalid syntax; a failure the engine already classified now keeps that classification. HTTP and Bolt keep the ladders they already had.
|
Tick the box to add this pull request to the merge queue (same as
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 38 |
🟢 Coverage 86.59% diff coverage · -6.84% coverage variation
Metric Results Coverage variation ✅ -6.84% coverage variation Diff coverage ✅ 86.59% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (83448ea) 150078 114508 76.30% Head commit (aa10fad) 182318 (+32240) 126636 (+12128) 69.46% (-6.84%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#5700) 82 71 86.59% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|
Review: wire-protocol client-error classification (issue 5628) Nicely scoped change with genuinely excellent documentation and test coverage. The core idea - one shared Substantive:
The GraphQL test Consideration:
Minor
Things I verified and liked
Overall this is high-quality work; the only item I would want addressed before merge is the Reviewed by Claude Code. |
…tegory) Claude's review found that SchemaException fell through to SERVER, so SELECT FROM NonExistentType - the most common caller mistake there is - reported as Postgres XX000 and an uncoded MongoDB server fault: the same misdirection this change exists to remove, for a different exception. Adds a SCHEMA category mapping to Postgres 42P01 undefined_table and MongoDB 26 NamespaceNotFound. The GraphQL test asserted only isNotEqualTo(PARSING), which passed while the classification was still SERVER; it now asserts the exact category. Also records in the javadoc why IllegalArgumentException is mapped to VALIDATION despite being raised for internal invariant violations too: the HTTP handler has answered it with 400 since long before this enum.
Review: wire-protocol client-error classification (#5700)Solid, well-scoped change. Centralizing the "whose fault is it" question in A few things worth a look: 1. Postgres bypasses the } catch (final CommandParsingException e) {
...writeError(..., "42601"); // never reaches sqlStateFor()
} catch (final Exception e) {
...writeError(..., sqlStateFor(e)); // ErrorCategory ordering only applies here
}The whole point of ordering 2. 3. Postgres 4. Behavior change to confirm at the HTTP layer. GraphQL now propagates the underlying Nit: the None of these block the change - (1) is the one I would most like addressed before merge, the rest are polish. Nice work on the docs and the exhaustive test matrix. |
…ow the graphql passthrough Review cycle 2 found that both Postgres execution arms caught CommandParsingException first and hardcoded 42601, which made the ARITHMETIC-before-PARSING ordering in ErrorCategory dead on that path. All four arms now route through sqlStateFor: genuine parse errors still report 42601 via the PARSING arm, while a wrapped arithmetic, conflict or schema error wins. The review also flagged that propagating the underlying exception from a GraphQL directive changes what the HTTP handler sees. It does, and the broad ArcadeDBException passthrough regressed the unknown-type case from 400 to 500, because SchemaException has no arm in that ladder. Narrowed the passthrough to CommandParsingException | CommandExecutionException, which the ladder does model. SchemaException is deliberately left alone: the class also carries genuine server faults (issue #4122), so mapping it to 400 would relabel a server bug as the caller's fault. Recorded as a follow-up instead. Also drops isClientError(), which had no production caller, and changes NOT_FOUND from 02000 to P0002 - SQLSTATE class 02 is a completion condition, not an error class.
|
Review: wire-protocol client-error classification (PR 5700) Reviewed the full diff. This is a high-quality, well-scoped change. The single-point A few observations, all minor - nothing blocking. 1. Postgres: message prefix can contradict the SQLSTATE (fragile coupling). In both execution arms the 2. 3. Redis RETRY -> TRYAGAIN. 4. MongoDB hardcoded numeric codes. Nits. Overall: solid, well-tested, and the deliberate non-changes (HTTP/Bolt ladders, gremlin passthrough, Schema-as-server-fault follow-up) are the right calls and are documented rather than silently assumed. Nice work. |
Review cycle 3 raised no blocking items. Four documentation points: the Postgres 'Syntax error' wording now records why it agrees with the SQLSTATE today, IllegalArgumentException's widened surface is stated as a conscious trade, Redis TRYAGAIN is marked as a weaker retry hint than Postgres 40001, and MongoDB's four literal codes note that the bundled de.bwaldvogel ErrorCode enum does not define them. The suggestion to collapse ErrorCategory.of into a single cause-chain walk is not taken: it is not equivalent. Classification is by category priority, not by which type the walk meets first, so a chain whose NeedRetryException sits below an ArithmeticErrorException must still answer RETRY. Added categoryPriorityBeatsPositionInTheChain so the javadoc claim has a test behind it.
Review: client-error classification across the wire protocols (#5628)Reviewed the full diff plus the surrounding code in A few observations, none blocking. Correctness - verified, no issues found
Points worth considering (non-blocking)
Style / conventionsConsistent with the codebase: Nice work - the classifier is a good abstraction and the change removes a real, user-facing misdirection (client mistakes reported as server faults, and conflicts reported as non-retryable). My only genuine suggestion is the MongoDB Automated review by Claude. Verified against the repo's exception hierarchy and wire-module sources. |
A de.bwaldvogel MongoServerError is not an ArcadeDBException, so it classified as SERVER and wireException re-wrapped it into a plain MongoServerException - dropping the code the client needs. It is now returned untouched. The 16459 insert case is caught locally and never reaches this path, but the bundled aggregation code raises coded errors that do. Pre-existing behaviour rather than a regression from this PR, fixed because the guard is one line and loses nothing.
|
Review: #5628 client-error classification across wire protocols Thorough, well-structured piece of work. Centralising the "which category of failure is this?" question in a single A few points worth discussing before merge, none of them blockers. 1. This is the one real inconsistency. 2.
3. RETRY precedence is by category, not chain position
4. Redis reply format change is a (minor) wire-compat change Error replies go from 5. Minor
Verification note I reviewed the final state across all 5 commits (the passthrough was correctly narrowed from Nice work overall - the classifier is clean, the precedence is deliberate and tested, and the two deliberate non-changes (HTTP/Bolt ladders, gremlin) are well justified. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #5700 +/- ##
==========================================
+ Coverage 67.49% 67.52% +0.03%
==========================================
Files 1770 1772 +2
Lines 150078 150326 +248
Branches 31809 31882 +73
==========================================
+ Hits 101290 101504 +214
- Misses 35607 35618 +11
- Partials 13181 13204 +23 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Closes #5628
Summary
#5602(PR#5612) gave arithmetic failures - 64-bit overflow, division and modulo by zero - their own type so a wire layer could report them as the client errors they are, but only HTTP (400) and Bolt (Neo.ClientError.Statement.ArithmeticError) were wired.postgresw,mongodbwandrediswended their query paths in a single generic arm, so a caller who divided by zero, hit a unique index or lost an optimistic-concurrency race received a code meaning the server had broken - and, for a conflict, one no driver retries. This addscom.arcadedb.exception.ErrorCategory, which walks the cause chain once withCauseChainso every protocol answers the classification question the same way, and gives each module only the table that translates a category into its own vocabulary.graphqlhad a related defect of its own: it rewrapped every failure raised by the statement a directive delegates to as aCommandParsingException, reporting a runtime error as invalid syntax; a failure the engine already classified now keeps that classification.ErrorCategoryRETRY40001serialization_failureTRYAGAINWriteConflictARITHMETIC22012division_by_zero /22003numeric_value_out_of_rangeERRBadValueDUPLICATED_KEY23505unique_violationERRDuplicateKeyNOT_FOUND02000no_dataERRSECURITY42501insufficient_privilegeNOPERMUnauthorizedVALIDATION22023invalid_parameter_valueERRBadValuePARSING42601syntax_errorERRFailedToParseTIMEOUT57014query_canceledERRMaxTimeMSExpiredSERVERXX000internal_errorERRRETRYis decided beforeARITHMETIC, matching the precedenceBoltNetworkExecutor.classifyExecutionErroralready documents: a chain carrying both must keep the transient classification, because that is the one a driver acts on.Two deliberate non-changes:
ErrorCategory's javadoc records this so the next person does not assume it is the single source.gremlinneeds nothing.GremlinQueryEngine.commandalready passesCommandExecutionExceptionthrough, so anArithmeticErrorExceptionsurvives to the HTTP layer intact. Confirmed by the gremlin-directive test in the graphql suite.Redis error replies also gain a message that cannot end the reply early: a RESP simple error is one line, and an embedded CR/LF would desynchronise the connection. The
-Command not foundreply is left byte-identical, since an existing test pins it.Test plan
ErrorCategoryTest(engine, 10 tests) - every category, the three wrapping shapes the engine produces,RETRYbeatingARITHMETIC, theCommandParsingExceptionwrapper not hiding an arithmetic error, and a cyclic cause chain terminating.PostgresErrorClassificationTest(8 tests) - the division/overflow SQLSTATE split, an unrecognised arithmetic message still landing in class 22, a wrapped arithmetic error, conflict precedence, and a genuine server fault still reportingXX000.RedisErrorClassificationTest(5 tests) -TRYAGAIN/NOPERM/ERR, an empty message still naming the failure, and a multi-line message flattened to one line.MongoDBErrorClassificationTest(5 tests) - each coded category, a wrapped arithmetic error, and server faults staying uncoded rather than being given a misdirecting client-error code.GraphQLExecutionErrorClassificationTest(4 tests) - an execution failure no longer arriving as a syntax error, an arithmetic error keeping its own type through a directive, a genuine GraphQL syntax error still being a parsing error, and a working directive still returning rows.anExecutionFailureIsNotReportedAsASyntaxErrorfails while the two guard tests still pass.graphql30/30 (includes the gremlin and cypher directive tests),mongodbw78/78,postgreswunit 258/258,rediswRedisWTest13/13 (the RESP wire path),boltBoltErrorClassificationTest13/13,enginecom.arcadedb.exception42/42.RedisQueryLanguageTestand the server-backed ITs are unverifiable on this machine - a local ArcadeDB holds ports 2480-2482, so they connect to the wrong server. Confirmed identical failures on unmodifiedmain. CI is the gate for these.🤖 Generated with Claude Code