Skip to content

Bind attach relation keys as strings to use the attachment_id index#233

Open
austinderrick wants to merge 1 commit into
wintercms:developfrom
austinderrick:fix/attach-relation-string-key-bindings
Open

Bind attach relation keys as strings to use the attachment_id index#233
austinderrick wants to merge 1 commit into
wintercms:developfrom
austinderrick:fix/attach-relation-string-key-bindings

Conversation

@austinderrick

@austinderrick austinderrick commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Problem

The files table stores attachment_id as an indexed string column, but AttachOneOrMany::addConstraints() and addEagerConstraints() bind the parent key(s) using their native integer type. Comparing a string column against an integer forces the database to coerce the column value for every candidate row, which makes the attachment_id index unusable.

On MySQL/MariaDB this is easy to demonstrate with EXPLAIN: with an integer binding the planner can only use the low-cardinality attachment_type index, so every attachment query scans every file row of that morph type. With a string binding it uses both indexes (ref|filter with rowid intersection) and examines only the matching rows. On installations with large system_files tables this is the difference between a handful of rows examined and tens of thousands — per attachment query, multiplied by however many attachOne/attachMany relations a page touches.

The eager path is affected the same way: for integer parent keys Laravel selects whereIntegerInRaw(), which inlines raw integers into the SQL.

Fix

  • addConstraints() casts the parent key to a string (preserving null, so the = nullwhereNull conversion is unchanged).
  • addEagerConstraints() builds the whereIn with string-cast keys instead of deferring to the parent implementation. Null keys are preserved as-is so they match nothing, rather than being cast to '' (which could match orphaned rows). The morph type and field constraints are applied exactly as before.

getRelationExistenceQuery() already handles this same mismatch for has() queries by casting the parent column to TEXT via DbDongle::cast, so this brings the regular constraint paths in line with the approach already used there.

Result matching is unaffected: PHP normalizes numeric-string array keys, so the eager-load dictionary lookup behaves identically (covered by a new end-to-end test).

Testing

Four new tests in AttachOneTest: string bindings on lazy constraints, string bindings on eager constraints, null-key preservation in eager constraints, and an end-to-end eager load asserting attachments still match their parents. Full test suite passes.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed attachment relationship constraint binding to use string-typed parent keys, ensuring proper index usage and column type matching.
    • Corrected eager-loading behavior for attachment relationships to preserve null parent keys and properly filter results.
  • Tests

    • Added comprehensive test coverage for attachment relationship constraints and eager-loading scenarios.

The files table stores attachment_id as a string, but addConstraints()
and addEagerConstraints() bound the parent key(s) using their native
integer type. Comparing a string column against an integer forces the
database to coerce the column value for every candidate row, which
prevents the attachment_id index from being used - on MySQL/MariaDB an
EXPLAIN shows the query falling back to the low-cardinality
attachment_type index and examining every row of that morph type.

Casting the keys to strings restores index usage for both lazy and
eager attachment loads. getRelationExistenceQuery() already handles
this for has() queries by casting the parent column to TEXT, so this
brings the regular constraint paths in line with that approach.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR modifies the AttachOneOrMany relation class to bind parent keys as strings in both constraint and eager-constraint methods, ensuring type consistency with the attachment_id column and preserving null values. The addConstraints() method now applies string-cast binding for non-null parent keys, and addEagerConstraints() replaces the parent implementation with custom whereIn logic that string-casts multiple parent keys while applying the morph-type constraint. Four new tests validate this behavior across basic constraint binding, eager loading with multiple parents, null key preservation, and end-to-end eager load matching.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: binding attachment relation keys as strings to use the attachment_id index, which is the primary objective of the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/Database/Relations/Concerns/AttachOneOrMany.php (1)

45-50: 💤 Low value

Consider simplifying the null-preserving ternary.

The ternary is_null($parentKey) ? $parentKey : (string) $parentKey correctly preserves null values while casting non-null keys to strings. For clarity, consider:

$this->query->where($this->foreignKey, '=', $parentKey !== null ? (string) $parentKey : null);

This makes the intent slightly more explicit: "if not null, cast to string; otherwise, use null."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Relations/Concerns/AttachOneOrMany.php` around lines 45 - 50,
Replace the null-preserving ternary used when binding the parent key to the
query so the intent is clearer: in the block that calls $this->getParentKey()
and then $this->query->where($this->foreignKey, '=', ...), change the expression
is_null($parentKey) ? $parentKey : (string) $parentKey to the clearer form
$parentKey !== null ? (string) $parentKey : null so non-null keys are cast to
string and nulls remain null; keep references to $parentKey, $this->foreignKey,
$this->query->where and getParentKey() unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/Database/Relations/Concerns/AttachOneOrMany.php`:
- Around line 45-50: Replace the null-preserving ternary used when binding the
parent key to the query so the intent is clearer: in the block that calls
$this->getParentKey() and then $this->query->where($this->foreignKey, '=', ...),
change the expression is_null($parentKey) ? $parentKey : (string) $parentKey to
the clearer form $parentKey !== null ? (string) $parentKey : null so non-null
keys are cast to string and nulls remain null; keep references to $parentKey,
$this->foreignKey, $this->query->where and getParentKey() unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68cf2ae7-c5c6-47a3-96b3-64a896b50610

📥 Commits

Reviewing files that changed from the base of the PR and between 989e5b4 and 3e418ce.

📒 Files selected for processing (2)
  • src/Database/Relations/Concerns/AttachOneOrMany.php
  • tests/Database/Relations/AttachOneTest.php

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