Skip to content

Port /data/query/enhanced/{id} to node; map results depth first as SDN does - #475

Merged
adamjohnwright merged 3 commits into
mainfrom
port/enhanced-v1
Oct 15, 2026
Merged

adamjohnwright merged 3 commits into
mainfrom
port/enhanced-v1

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

Port /data/query/enhanced/{id} to node; place the root as SDN does

/query/enhanced/{id} is graph-core's findOldEnhancedObjectById, written by
the domain serialiser. It returns the object, everything related to it,
those objects' species, and one step more of their regulations, physical
entities, references and markers. The entity popup, the website's content
pages and the IDG panel read it.

Rules, each measured against Java:

  • Relationships go both ways unless needsIncomingRelationship says not.
    That reads the id as it came: a padded or overflowing dbId fails
    Long.valueOf and goes outgoing only.
  • Java collects every row's nodes, repeats included. Node collects each
    list DISTINCT, which keeps the only thing the mapping uses, each node's
    first appearance.
  • /query/enhanced/{id} ranks above /query/{id}/{attribute}, so
    /query/enhanced/stId is the enhanced object with the id stId. node
    registers it first; a test pins that and fails on the old order.

One mapping rule was wrong for every endpoint and only showed here.
SDN's extractNodes lists the nodes of list columns first, then
single-node columns. So the root sits where it first appears in a list,
and otherwise last; node had put it first. A reaction regulated by its
own root regulation and three others lists the root third, and node
listed it first (16 of 3,000). A single-valued field also takes the
first value found, not the last.

Against Java, byte for byte after the fix:

  • enhanced: 3,000 of 3,000 and another 3,000 of 3,000. Node takes 25s
    in all against Java's 46s.
  • Re-checked: /query/{id} 20,279 of 20,279; in-depth 1,179 of 1,179.
    Tests failed before the change; the root-position and precedence tests
    fail on the old code. nginx -t passes.

domain: map a result as SDN does -- depth first, from the field that reaches it

A node's class is decided by the field that first reaches it, and SDN's
DefaultNeo4jEntityConverter goes depth first: an entity met in a field
has its own fields filled before that field moves on. mapResult had
filled every entity flatly and chosen classes up front from the root's
fields. That was right over 20,279 /query/{id} answers, but wrong in
enhanced answers. Deleted 9034526's two replacement regulations are both
plain PositiveRegulations:

  • the first is reached through replacementInstances, an interface
    (Deletable), so SDN takes its store's first fitting class:
    PositiveGeneExpressionRegulation;
  • mapping the first reaches its reaction, whose regulatedBy (a class)
    reaches the second before the deleted object's loop does, so the second
    keeps its labels' class.
    Java writes them that way and the flat mapper did not.

mapResult now follows createInstanceOfRelationships:

  • candidates by the target's label in result order;
  • each field's relationships counted per direction, dropped from the
    result after a second use;
  • each new entity filled before its parent field goes on.
    It runs on a stack of frames, not recursion: a recursive version
    overflowed on a 20,000-deep chain.

Against Java, byte for byte:

  • enhanced: 6 of 6 hard cases, 9034526 among them, and 500 of 500.
  • /query/{id}: 500 of 500.
  • in-depth at depth 4: 99 of 100. The last differs only in the
    getter-only key order Java changed on today's restart.
    The test for the two regulations fails on the flat mapper. The deep-chain
    test overflowed on the recursive one.

🤖 Generated with Claude Code

adamjohnwright and others added 3 commits October 15, 2026 14:19
/query/enhanced/{id} is graph-core's findOldEnhancedObjectById, written by
the domain serialiser. It returns the object, everything related to it,
those objects' species, and one step more of their regulations, physical
entities, references and markers. The entity popup, the website's content
pages and the IDG panel read it.

Rules, each measured against Java:
- Relationships go both ways unless needsIncomingRelationship says not.
  That reads the id as it came: a padded or overflowing dbId fails
  Long.valueOf and goes outgoing only.
- Java collects every row's nodes, repeats included. Node collects each
  list DISTINCT, which keeps the only thing the mapping uses, each node's
  first appearance.
- /query/enhanced/{id} ranks above /query/{id}/{attribute}, so
  /query/enhanced/stId is the enhanced object with the id `stId`. node
  registers it first; a test pins that and fails on the old order.

One mapping rule was wrong for every endpoint and only showed here.
SDN's extractNodes lists the nodes of list columns first, then
single-node columns. So the root sits where it first appears in a list,
and otherwise last; node had put it first. A reaction regulated by its
own root regulation and three others lists the root third, and node
listed it first (16 of 3,000). A single-valued field also takes the
first value found, not the last.

Against Java, byte for byte after the fix:
- enhanced: 3,000 of 3,000 and another 3,000 of 3,000. Node takes 25s
  in all against Java's 46s.
- Re-checked: /query/{id} 20,279 of 20,279; in-depth 1,179 of 1,179.
Tests failed before the change; the root-position and precedence tests
fail on the old code. nginx -t passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reaches it

A node's class is decided by the field that first reaches it, and SDN's
DefaultNeo4jEntityConverter goes depth first: an entity met in a field
has its own fields filled before that field moves on. mapResult had
filled every entity flatly and chosen classes up front from the root's
fields. That was right over 20,279 /query/{id} answers, but wrong in
enhanced answers. Deleted 9034526's two replacement regulations are both
plain PositiveRegulations:
- the first is reached through replacementInstances, an interface
  (Deletable), so SDN takes its store's first fitting class:
  PositiveGeneExpressionRegulation;
- mapping the first reaches its reaction, whose regulatedBy (a class)
  reaches the second before the deleted object's loop does, so the second
  keeps its labels' class.
Java writes them that way and the flat mapper did not.

mapResult now follows createInstanceOfRelationships:
- candidates by the target's label in result order;
- each field's relationships counted per direction, dropped from the
  result after a second use;
- each new entity filled before its parent field goes on.
It runs on a stack of frames, not recursion: a recursive version
overflowed on a 20,000-deep chain.

Against Java, byte for byte:
- enhanced: 6 of 6 hard cases, 9034526 among them, and 500 of 500.
- /query/{id}: 500 of 500.
- in-depth at depth 4: 99 of 100. The last differs only in the
  getter-only key order Java changed on today's restart.
The test for the two regulations fails on the flat mapper. The deep-chain
test overflowed on the recursive one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #475: the mapper's comment said SDN's processed-count removal
"decides what some fields of highly linked objects hold". On this model
it cannot fire. Only PhysicalEntity has fields sharing a relationship type
and direction (three INCOMING `regulator`), a node matches at most two,
and each owner is mapped once. It is kept for fidelity, and the comment
now says so. It also records the one SDN behaviour not reproduced: a list
column's relationships iterate in HashSet order. That only matters for
parallel same-type edges between two nodes, and whether any exist is
unchecked, because finding out needs a scan of the shared graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adamjohnwright
adamjohnwright merged commit 552fd46 into main Oct 15, 2026
6 checks passed
@adamjohnwright
adamjohnwright deleted the port/enhanced-v1 branch October 15, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant