Skip to content

Follow-ups from #6304: a two-pattern MATCH that drops rows when both shared variables are labelled, five more first-label-only push-downs, a futile COMMIT RETRY, and a CI lane that hides which test failed #6322

Description

@lvca

Four independent findings turned up while fixing #6304 (PR #6308, merged as 383a82169). None of them belonged in that PR, and they are unrelated to each other beyond having been found in the same neighbourhood. Filed together for triage; each is self-contained and can be split out or closed on its own.

Line references are against bdf46f9cc. The item 1 repro below was run on that commit and reproduces exactly as tabulated.


1. A two-pattern MATCH returns no rows when both shared variables carry a label

The one possible wrong answer here, and the only item that is not about a fast path: it reproduces through the ordinary materialization pipeline, with no aggregate and no Graph Analytical View.

Two comma-separated patterns sharing their endpoint variables return 0 rows whenever both shared variables are labelled and at least one of those labels is written on the second pattern:

CREATE (p1:Person {k:'p1'}), (p2:Person {k:'p2'}), (b1:Bot {k:'b1'}),
       (c1:Comment {k:'c1'}), (c2:Comment {k:'c2'});
MATCH (c1:Comment {k:'c1'}), (p1:Person {k:'p1'}) CREATE (c1)-[:AUTHORED]->(p1);
MATCH (c1:Comment {k:'c1'}), (p2:Person {k:'p2'}) CREATE (c1)-[:MENTIONS]->(p2);
MATCH (c2:Comment {k:'c2'}), (b1:Bot   {k:'b1'}) CREATE (c2)-[:AUTHORED]->(b1);
MATCH (c2:Comment {k:'c2'}), (p2:Person {k:'p2'}) CREATE (c2)-[:MENTIONS]->(p2);
MATCH (p1:Person {k:'p1'}), (p2:Person {k:'p2'}) CREATE (p1)-[:KNOWS]->(p2);
MATCH (b1:Bot {k:'b1'}),    (p2:Person {k:'p2'}) CREATE (b1)-[:KNOWS]->(p2);
query rows expected
MATCH (p1)-[:KNOWS]->(p2), (p1:Person)<-[:AUTHORED]-(c:Comment)-[:MENTIONS]->(p2:Person) RETURN c 0 1 (c1)
MATCH (p1)-[:KNOWS]->(p2:Person), (p1:Person)<-[:AUTHORED]-(c:Comment)-[:MENTIONS]->(p2) RETURN c 0 1
MATCH (p1:Person)-[:KNOWS]->(p2:Person), (p1)<-[:AUTHORED]-(c:Comment)-[:MENTIONS]->(p2) RETURN c 1 1
MATCH (p1)-[:KNOWS]->(p2), (p1)<-[:AUTHORED]-(c:Comment)-[:MENTIONS]->(p2:Person) RETURN c 2 2

Both labels on the first pattern is correct; splitting them across the two patterns, or putting both on the second, empties the result. The label is not merely being ignored - it is rejecting rows that satisfy it, so it is not the same shape as the dropped-filter bugs #6304 fixed.

RETURN c rather than count(*) is deliberate: no count push-down is involved, so this is the materialization pipeline binding a variable that the other pattern already bound.

Impact. Silent under-count on an ordinary read, on a pattern shape that is idiomatic (MATCH (a)-[:R]->(b), (a)<-[:S]-(x)-[:T]->(b)), with no error and no warning.

Where this was noticed. CypherPairJoinLabelFilterIssue6304Test.bothArmEndpointLabelsAreAppliedTogether had to use the OLTP count as its reference instead of the pipeline, and says so in its javadoc - that comment should be reverted to the ordinary three-way cross-check once this is fixed.


2. Five more count push-downs read only the first of a node's labels

engine/src/main/java/com/arcadedb/query/opencypher/executor/CypherExecutionPlan.java

(a:A:B) keeps only what carries both and (a:A|B) keeps what carries either. Taking getLabels().get(0) turns each of them into (a:A), which is neither - so an operator built on that filter counts a set the pattern did not describe. #6304 fixed this for the pair-join detector by declining the push-down (hasPushDownRepresentableLabel), and tryCreateTypeCountOptimization already had the guard at :3820. These five do not:

line method what the label feeds
:3659 tryOptimizeMatchCountReturn the counted node's type
:4178, :4179 nodeLabelsAreTypeDisjoint both nodes of a disjointness test
:4627 tryDetectChainCountStar per-hop label array
:4847 tryDetectStarCountStar the star's central label
:5338 tryDetectAntiJoinChainCountStar per-hop label array

nodeLabelsAreTypeDisjoint is the one worth looking at first, because it is the only one where reading the wrong label can flip a decision in either direction: it exists to prove two positions cannot be the same vertex, so a wrong "disjoint" verdict drops matches and a wrong "not disjoint" keeps a pattern that cannot match.

Suggested shape. Reuse hasPushDownRepresentableLabel - it is already in this file and already the convention at :3820. The conservative direction is to decline the push-down and let the ordinary pipeline answer, exactly as #6304 did; representing an intersection is not something these operators have room for.

Worth a regression test per detector along the lines of CypherPairJoinLabelFilterIssue6304Test.aMultiLabelledNodeDeclinesThePushDown, which asserts both that the plan declines and that the answer matches the pipeline.


3. COMMIT RETRY n retries a deadline that has already passed

engine/src/main/java/com/arcadedb/query/sql/executor/RetryStep.java:89

} catch (final NeedRetryException | TimeoutException ex) {

The comment there explains the intent, and it is sound for what it names: the usual source of a TimeoutException inside a commit is TransactionManager's file-lock timeout, which is transient contention and clears after a backoff. But the same catch now also takes a TimeoutException raised by a WorkGuard on the command deadline - arcadedb.command.timeout or a TIMEOUT clause - and that kind cannot be cured by waiting: the instant is fixed for the whole command (#6266), so every remaining attempt aborts on its first check. A COMMIT RETRY 10 block spends all ten attempts plus nine backoff sleeps re-reaching a deadline that expired before the first retry, then fails anyway - or, with no ELSE FAIL, returns an empty result set instead of the error.

Same futility this project already addressed for chunked reads in #6258: a retry whose precondition cannot change is not a retry.

Suggested shape. Distinguish the two at the catch: a TimeoutException whose deadline is the command's own (the guard's, which names the bound in its message and could carry a marker instead) should propagate rather than consume the retry budget, while a lock-acquisition timeout keeps retrying. The existing comment then documents a real distinction instead of a blanket policy.

Deliberately left alone in #6308 because the behaviour is documented in that comment and changing it is a decision, not a fix.


4. A failing CI lane does not say which test failed

.github/workflows/mvn-test.yml

When vector-unit-tests and ha-integration-tests both went red on #6308, the failing test names were not recoverable from GitHub at all:

  • the dorny/test-reporter step reports success, but no Vector Unit Tests Report check run is ever published - GET /commits/<sha>/check-runs?per_page=100 returns 28 entries and none of them is a reporter;
  • no annotations on the failing check run, and output.title/output.summary are null;
  • the per-job log endpoint returns only runner setup and cleanup (~500 lines), omitting the Maven output entirely;
  • the run-level log archive cannot be downloaded while any job of the run is still in flight;
  • the uploaded artifact for these lanes is **/jacoco*.xml only - coverage, not surefire.

The only way to tell a flake from a regression was to wait for the whole run to finish and re-run the failed jobs (gh run rerun <id> --failed). Both then passed on the identical commit - vector-unit-tests fail 21m0s → pass 11m10s, ha-integration-tests fail 1h0m51s → pass 1h1m29s - so both were flakes, but that took an hour to establish and would have taken the same hour if one had been a real regression.

Suggested shape. Upload **/surefire-reports/TEST*.xml as an artifact on failure for the lanes that run tests. check-test-results.py already parses exactly those files to produce the verdict, so the data exists at that point in the job; it is only discarded. That alone makes the failing test names readable without a re-run, independently of why the reporter check is missing.


Suggested triage

Item 1 is the only wrong answer and is the one worth looking at first. Item 2 is the same class of bug #6304 fixed in one place, in five more. Item 3 is a behaviour decision. Item 4 is CI ergonomics, and the cheapest of the four.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions