Skip to main content
Last Updated: 2025-12-14 Status: Implemented - Tier 1 (Automatic Backend Sync)
User-facing guidesThis document covers the architectural rationale for bidirectional sync. For how to declare relationships in your entity classes, see the Relationship DSL guide (belongs_to / habtm / has_many / has_many_through). For how non-updateable fields like Tag.scenes or Gallery.cover get persisted, see the Side Mutations guide.

Executive Summary

Empirical testing confirms that Stash’s backend automatically maintains bidirectional referential integrity for ALL relationship types. No dual mutation coordination is required. This document details the findings and implementation approach.

Empirical Verification Results

All tests passed (8/8) confirming automatic bidirectional synchronization:

Test Details

Test Script: verify_bidirectional_relationships.py Verification Date: 2025-12-14 Stash Version: Latest (as of test date) Each test:
  1. Created entities with relationships
  2. Updated one side of the relationship
  3. Verified the inverse side automatically updated
  4. Cleaned up all test data

Relationship Query Patterns

Pattern A: Direct Nested Fields

Used by: Gallery, Performer, Tag, Group Reading:
Writing:
Python Example:

Pattern B: Filter-Based Queries

Used by: Studio (and potentially others) Why this pattern?
  • More flexible (supports pagination, sorting, complex filters)
  • Studio doesn’t have a direct scenes field
  • Instead, query scenes BY studio using filters
Reading:
Writing:
Python Example:

Pattern C: Complex Objects with Metadata

Used by: Group hierarchies Why this pattern?
  • Relationships have additional metadata (e.g., description)
  • Not just a simple ID list
Schema:
Reading:
Writing:
Python Example:

Implementation Architecture

RelationshipMetadata Class

Usage in Entity Types

Example 1: Scene (Multiple Patterns)

Example 2: Group (Complex Objects)

Convenience Helper Methods

Direct Field Helpers

Filter Query Relationships (Studio, Performer, etc.)

Some inverse relationships — like “all scenes for a studio” — aren’t direct fields on the entity. Studio has a scene_count resolver field but no scenes list. Querying scenes by studio requires a filter query via the client or store. Using the client directly:
Streaming over a large set with populated_filter_iter:
Note: Per-entity lazy-loader methods (studio.get_scenes(), studio.get_scene_count()) are not implemented. Use the client filter queries and store methods shown above instead.

Complex Object Helpers

Migration Notes

Before (Old Pattern)

After (New Pattern)

Migration Complete

All entity types have been migrated to RelationshipMetadata. The old tuple pattern (target_field, is_list) is no longer supported and will raise ValueError.

Key Takeaways

  1. All relationships auto-sync - Backend maintains referential integrity
  2. No dual mutations needed - Single update syncs both sides
  3. Three query patterns - Direct fields, filters, complex objects
  4. Edge cases work - Removals and bulk updates both auto-sync
  5. Metadata for documentation - Not for sync coordination
  6. Convenience helpers - Simplify common operations

Future Enhancements

  • Auto-generate relationship metadata from GraphQL schema
  • Add type hints for forward references (avoid circular imports)
  • Generate convenience helpers automatically
  • Add relationship validation (ensure IDs exist)
  • Performance optimization for bulk relationship queries

See Also

References

  • Verification Script: verify_bidirectional_relationships.py
  • Test Results: See “Empirical Verification Results” above
  • Implementation PR: TBD
  • Related Issues: TBD