Skip to content

Conversation

@pileks
Copy link

@pileks pileks commented Sep 30, 2025

Summary

  • Upgrades mpl-core dependency to 0.10.1-alpha.2 to support MIP-11 Groups plugin
  • Implements indexing for Groups plugin data in the asset_grouping table
  • Adds logic to track and update asset groupings under the "group" key based on slot ordering
  • Clears stale groupings and inserts new ones when Groups plugin is present on assets

Changes

  • Updated mpl-core version in Cargo.toml and Cargo.lock
  • Modified v1_asset.rs to handle Groups plugin indexing with proper slot-based update logic
  • Added .gitignore for integration tests .env file

Future work

  • Add integration test once core is deployed to devnet

@pileks pileks requested review from danenbm and stranzhay September 30, 2025 15:39
@pileks pileks self-assigned this Sep 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 30, 2025

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved asset grouping data consistency by refining uniqueness constraints to better handle collection and non-collection groupings separately.
    • Added automatic removal of stale asset grouping entries to maintain data accuracy across asset operations.
  • Chores

    • Updated mpl-core dependency to the latest version.
    • Enhanced environment file handling in integration tests for improved security practices.

Walkthrough

Adds a Postgres migration replacing a unique constraint with two partial unique indexes for asset_grouping; updates transformer upserts/inserts to use partial-index-aware ON CONFLICT semantics and bulk inserts; prunes stale asset_grouping entries in DAO; bumps a workspace dependency; ignores .env in integration tests. (≤50 words)

Changes

Cohort / File(s) Change summary
Workspace dependency
Cargo.toml
Bump mpl-core from "0.10.0" to =0.10.1-alpha.2, preserving the serde feature.
Test ignore
integration_tests/.gitignore
Add .env to ignore local environment files.
Transformer — asset grouping save/upsert
program_transformers/src/mpl_core_program/v1_asset.rs, program_transformers/src/bubblegum/db.rs, program_transformers/src/token_metadata/v1_asset.rs
Switch to bulk insert for asset_grouping and replace prior OnConflict columns-based upsert with partial-index-aware ON CONFLICT clauses (conflict target differs for group_key = 'collection' vs others) that conditionally DO UPDATE when incoming slot_updated is newer or equal; execute within existing transactions and map DB errors.
DAO — stale grouping pruning
digital_asset_types/src/dao/scopes/asset.rs
Add filter_out_stale_asset_groupings and invoke it in get_related_for_assets and get_by_id (multiple paths) to remove stale/non-max group entries and require non-null values for retained collection/group entries.
DB migration: partial unique indexes
migration/src/m20251008_145049_fix_asset_grouping_constraints.rs, migration/src/lib.rs
Add migration that drops the old unique constraint on (asset_id, group_key) and creates two partial unique indexes: one for group_key = 'collection' on (asset_id, group_key), and one for group_key != 'collection' on (asset_id, group_key, group_value); register new Migration implementing MigrationTrait.

Sequence Diagram(s)

sequenceDiagram
    participant Transformer
    participant DB as Postgres
    note right of Transformer #E8F6FF: High-level save/upsert flow
    Transformer->>DB: BEGIN TRANSACTION
    Transformer->>DB: INSERT MANY asset_grouping rows
    alt collection conflict (partial index)
        DB-->>Transformer: ON CONFLICT (asset_id, group_key) WHERE group_key='collection'\nDO UPDATE ... WHEN EXCLUDED.slot_updated >= asset_grouping.slot_updated
    else other-group conflict (partial index)
        DB-->>Transformer: ON CONFLICT (asset_id, group_key, group_value) WHERE group_key!='collection'\nDO UPDATE ... WHEN EXCLUDED.slot_updated >= asset_grouping.slot_updated
    end
    Transformer->>DB: COMMIT
    DB-->>Transformer: OK / Error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • pmantica11
  • Nagaprasadvr

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Add MIP-11 Support to DAS Indexer" directly and clearly summarizes the primary objective of the changeset. According to the PR description and raw summary, the changes upgrade the mpl-core dependency to support the MIP-11 Groups plugin and implement indexing for Groups plugin data in the asset_grouping table. The title accurately captures this main feature addition without using vague language or noise, making it immediately understandable to reviewers scanning the project history.
Description Check ✅ Passed The pull request description is well-structured and comprehensive, providing clear context for understanding the changes without requiring deep code review. It includes a summary of the key objective (upgrading mpl-core and implementing Groups plugin indexing), a detailed changes section listing the modified files and their purposes, and mentions future work regarding integration tests. The description satisfies the template's expectation of helping reviewers understand the PR's purpose and is sufficiently detailed to explain the rationale and scope of the changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jure/met-880-add-mip-11-support-to-das-indexer

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37104b4 and be82120.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • Cargo.toml (1 hunks)
  • integration_tests/.gitignore (1 hunks)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: build-api (ubuntu-22.04)
🔇 Additional comments (6)
integration_tests/.gitignore (1)

1-1: LGTM!

Adding .env to .gitignore is a best practice to prevent accidental commits of sensitive environment variables and credentials.

program_transformers/src/mpl_core_program/v1_asset.rs (5)

29-29: LGTM!

The Expr import is correctly added to support query construction in the asset grouping logic.


369-380: LGTM!

The logic correctly fetches the last slot_updated for the "group" key, with appropriate error handling and safe defaults.


369-423: Verify transaction isolation and error handling.

The Groups plugin indexing logic is correctly wrapped within the existing transaction (started at line 108, committed at line 507), ensuring atomicity with other asset updates. Error handling properly propagates database errors through map_err.

One consideration: if the Groups plugin update fails, the entire asset save transaction rolls back. Confirm this is the desired behavior vs. allowing the asset to save without group indexing.


382-396: Verify slot ordering guarantees and reorg handling
Confirm that your upstream event source guarantees monotonically increasing slot values per asset (to satisfy the last_group_slot_updated < slot_i assumption) or introduce reorg-safe logic to prevent stale or out-of-order deletes/inserts when slots are replayed.


398-422: Verify Groups plugin verification flag and duplicate handling

  • Hardcoding verified = true assumes all Groups entries are always verified; confirm this aligns with the MIP-11 Groups spec.
  • Ensure group.to_string() produces the correct on-chain identifier (pubkey) format, not a debug or alternate representation.
  • Although old groupings are cleared in the same transaction, duplicates within groups.groups could still cause insertion errors—verify input de-duplication.

mpl-bubblegum = "2.0.0"
mpl-account-compression = "0.4.2"
mpl-core = {version = "0.10.0", features = ["serde"]}
mpl-core = {version = "=0.10.1-alpha.2", features = ["serde"]}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify alpha version stability and consider version constraint.

Using an exact-pinned alpha version (=0.10.1-alpha.2) in the dependency may cause issues:

  1. Alpha version risk: Alpha releases are typically unstable and may contain bugs or breaking changes.
  2. Exact version pinning: The = constraint prevents any updates, even patch fixes. Consider using ~ for patch-level flexibility once a stable version is available.

Verify the stability of this alpha version and check if a stable release is available:


🌐 Web query:

What is the latest stable version of mpl-core Rust crate?

💡 Result:

The latest stable mpl-core crate version is 0.10.1 (checked Sep 30, 2025). [1][2]

Sources:
[1] docs.rs — mpl-core 0.10.1.
[2] lib.rs — mpl-core (shows version 0.10.1 in Cargo features).


Update mpl-core to the stable 0.10.1 release and loosen the version constraint.

Replace the exact alpha pin (=0.10.1-alpha.2) with a caret requirement (^0.10.1) to use the latest stable release and receive future patch fixes.

🤖 Prompt for AI Agents
In Cargo.toml at line 72, the mpl-core dependency is pinned to the exact alpha
release "=0.10.1-alpha.2"; change this to a caret requirement "^0.10.1" so the
project uses the stable 0.10.1 release and allows compatible patch upgrades
(update the version string accordingly in that dependency entry).

Comment on lines 382 to 383
// Only perform updates if the last asset_grouping for group plugin slot_updated is less than the current slot for the asset update.
if last_group_slot_updated < slot_i {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Document the different slot comparison strategies.

There's an intentional difference in slot comparison logic between Groups and Collection groupings:

  • Line 383 (Groups): Uses strict less-than (<) to gate the entire update block, preventing unnecessary deletes/inserts when slots are equal.
  • Line 452 (Collection): Uses greater-or-equal (>=) in the WHERE clause, allowing updates when slots are equal.

This difference is likely intentional - Groups plugin requires delete-then-insert (no ON CONFLICT), while Collection uses upsert semantics. Consider adding a comment explaining this design choice for future maintainers.

-    // Only perform updates if the last asset_grouping for group plugin slot_updated is less than the current slot for the asset update.
+    // Only perform updates if the last asset_grouping for group plugin slot_updated is less than the current slot for the asset update.
+    // Note: We use strict < comparison here (vs >= for collections) because Groups requires delete-then-insert,
+    // so we skip the entire operation when slots are equal to avoid unnecessary database operations.
     if last_group_slot_updated < slot_i {

Also applies to: 452-453

🤖 Prompt for AI Agents
In program_transformers/src/mpl_core_program/v1_asset.rs around lines 382-383
and 452-453, the slot comparison behavior differs: Groups uses strict less-than
(`<`) to skip updates when slots are equal, while Collection uses
greater-or-equal (`>=`) to allow updates when slots are equal; add a concise
comment at each location explaining this deliberate design choice — that Groups
performs delete-then-insert (no ON CONFLICT) so we must avoid touching records
when slots are equal, whereas Collection uses upsert semantics so equal-slot
updates are allowed — include the intent and the relation to delete-vs-upsert
behavior so future maintainers understand why the comparisons differ.

Comment on lines 394 to 396
txn.execute(query)
.await
.map_err(|db_err| ProgramTransformerError::AssetIndexError(db_err.to_string()))?;
Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to have the delete and insert for this part of the same txn so if there's a failure they are reverted together

Copy link
Author

Choose a reason for hiding this comment

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

I have updated the indexer to only fire off a single query for any of the cases - asset without group/empty group just deletes old groupings, while asset with groups now performs deletion and insertion using CTEs.

…ex constraint to allow multiple relations that are not collection relations
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be82120 and 5420abb.

📒 Files selected for processing (3)
  • migration/src/lib.rs (2 hunks)
  • migration/src/m20251008_145049_fix_asset_grouping_constraints.rs (1 hunks)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
🧰 Additional context used
🪛 GitHub Actions: Check lock file, fmt, clippy
program_transformers/src/mpl_core_program/v1_asset.rs

[error] 368-368: rustfmt: formatting differences detected in block building the group variable; please run 'cargo fmt' to fix.


[error] 389-389: rustfmt: formatting differences detected around the formatted SQL value construction; please run 'cargo fmt' to fix.


[error] 430-430: rustfmt: formatting differences detected in statement execution; please run 'cargo fmt' to fix.


[error] 455-455: rustfmt: formatting differences detected in transaction error handling; please run 'cargo fmt' to fix.

migration/src/m20251008_145049_fix_asset_grouping_constraints.rs

[error] 29-29: rustfmt: formatting differences detected. The diff shows misplaced string concatenation around the WHERE clause; please run 'cargo fmt' to fix.


[error] 41-41: rustfmt: formatting differences detected. The diff shows misplaced string concatenation around the WHERE clause; please run 'cargo fmt' to fix.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-api (ubuntu-22.04)
🔇 Additional comments (3)
program_transformers/src/mpl_core_program/v1_asset.rs (1)

29-30: LGTM: Import additions support the new SQL-based grouping logic.

The Statement and additional sea_query imports are necessary for the raw SQL operations used in the asset_grouping handling block below.

migration/src/lib.rs (1)

54-54: LGTM: Migration registration is correct.

The new migration module is properly registered in both the module declaration and the migrations list.

Also applies to: 115-115

migration/src/m20251008_145049_fix_asset_grouping_constraints.rs (1)

51-85: LGTM: Rollback logic properly reverses the migration.

The down() method correctly drops the partial indexes and recreates the original unique constraint, providing a clean rollback path.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5420abb and 0f721e3.

📒 Files selected for processing (1)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
🧰 Additional context used
🪛 GitHub Actions: Check lock file, fmt, clippy
program_transformers/src/mpl_core_program/v1_asset.rs

[error] 1-1: cargo fmt --all -- --check failed. Rust source files require formatting changes in this file.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-api (ubuntu-22.04)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5420abb and eb69912.

📒 Files selected for processing (2)
  • migration/src/m20251008_145049_fix_asset_grouping_constraints.rs (1 hunks)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-api (ubuntu-22.04)
  • GitHub Check: test

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb69912 and 889014d.

📒 Files selected for processing (1)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: build-api (ubuntu-22.04)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 889014d and cb378a6.

📒 Files selected for processing (1)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-api (ubuntu-22.04)
  • GitHub Check: test

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb378a6 and bc38abd.

📒 Files selected for processing (4)
  • digital_asset_types/src/dao/scopes/asset.rs (4 hunks)
  • program_transformers/src/bubblegum/db.rs (1 hunks)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
  • program_transformers/src/token_metadata/v1_asset.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
digital_asset_types/src/dao/scopes/asset.rs (1)
program_transformers/src/mpl_core_program/v1_asset.rs (2)
  • asset (727-732)
  • groups (480-491)
program_transformers/src/mpl_core_program/v1_asset.rs (1)
integration_tests/tests/integration_tests/general_scenario_tests.rs (1)
  • vec (45-84)
🪛 GitHub Actions: Check lock file, fmt, clippy
program_transformers/src/bubblegum/db.rs

[error] 407-407: cargo fmt --check failed: Rust formatting changes detected in db.rs. Run 'cargo fmt' to fix.

digital_asset_types/src/dao/scopes/asset.rs

[error] 639-639: cargo fmt --check failed: Rust formatting changes detected in asset.rs. Run 'cargo fmt' to fix.


[error] 650-650: cargo fmt --check failed: Rust formatting changes detected in asset.rs. Run 'cargo fmt' to fix.

program_transformers/src/mpl_core_program/v1_asset.rs

[error] 491-491: cargo fmt --check failed: Rust formatting changes detected in v1_asset.rs. Run 'cargo fmt' to fix.

program_transformers/src/token_metadata/v1_asset.rs

[error] 342-342: cargo fmt --check failed: Rust formatting changes detected in v1_asset.rs. Run 'cargo fmt' to fix.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-api (ubuntu-22.04)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc38abd and 299e9e7.

📒 Files selected for processing (4)
  • digital_asset_types/src/dao/scopes/asset.rs (4 hunks)
  • program_transformers/src/bubblegum/db.rs (1 hunks)
  • program_transformers/src/mpl_core_program/v1_asset.rs (2 hunks)
  • program_transformers/src/token_metadata/v1_asset.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
digital_asset_types/src/dao/scopes/asset.rs (1)
program_transformers/src/mpl_core_program/v1_asset.rs (2)
  • asset (722-727)
  • groups (477-488)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: build-api (ubuntu-22.04)
🔇 Additional comments (1)
digital_asset_types/src/dao/scopes/asset.rs (1)

646-662: Handle NULL slots when pruning stale groups.

Rows inserted earlier can have slot_updated = NULL. max() skips NULL, so once any non-NULL row exists we never drop the NULL entries, even though they’re stale. They’ll keep surfacing in asset.groups. Please treat NULL as “lowest” by retaining only rows where slot_updated is NULL and every non-collection row was NULL, or simply drop NULL rows once a concrete max exists. Example fix:

-    if let Some(max_slot_updated) = max_slot_updated {
-        asset_groupings_with_date
-            .retain(|ag| ag.0.group_key != "group" || ag.0.slot_updated == max_slot_updated);
-    }
+    if let Some(max_slot_updated) = max_slot_updated {
+        asset_groupings_with_date.retain(|ag| {
+            ag.0.group_key != "group"
+                || ag.0.slot_updated.map_or(false, |slot| slot == max_slot_updated)
+        });
+    }

This keeps only the freshest 'group' rows.

Likely an incorrect or invalid review comment.

danenbm
danenbm previously approved these changes Oct 15, 2025
Copy link
Contributor

@danenbm danenbm left a comment

Choose a reason for hiding this comment

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

Change LGTM just had the one question and then also I assume you will want to add testing at some point after its deployed to devnet

Comment on lines 646 to 662
fn filter_out_stale_asset_groupings(
asset_groupings_with_date: &mut Vec<(asset_grouping::Model, Option<asset_data::Model>)>,
) {
// For core assets, any asset groupings that do not have the max
// `slot_updated` value are stale and should be removed.
// However, only filter out groupings where group_key is "group".
// All other groupings should be kept.
let max_slot_updated = asset_groupings_with_date
.iter()
.filter(|ag| ag.0.group_key == "group")
.map(|ag| ag.0.slot_updated)
.max();
if let Some(max_slot_updated) = max_slot_updated {
asset_groupings_with_date
.retain(|ag| ag.0.group_key != "group" || ag.0.slot_updated == max_slot_updated);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to do any specific handling with the empty_groups empty Vec?

Copy link
Author

Choose a reason for hiding this comment

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

You're right - since I'm inserting a grouping with a null value, I should filter out that grouping, so that nothing renders in cases where all groups were removed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 299e9e7 and 70a268f.

📒 Files selected for processing (1)
  • digital_asset_types/src/dao/scopes/asset.rs (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pileks
PR: metaplex-foundation/digital-asset-rpc-infrastructure#268
File: program_transformers/src/mpl_core_program/v1_asset.rs:493-505
Timestamp: 2025-10-13T14:09:58.534Z
Learning: In the MIP-11 Groups plugin implementation for program_transformers/src/mpl_core_program/v1_asset.rs, the team has decided not to delete stale group rows before upserting because same-slot group changes are not expected to be common. They will adjust if analysis shows it becomes a problem.
📚 Learning: 2025-10-13T14:09:58.534Z
Learnt from: pileks
PR: metaplex-foundation/digital-asset-rpc-infrastructure#268
File: program_transformers/src/mpl_core_program/v1_asset.rs:493-505
Timestamp: 2025-10-13T14:09:58.534Z
Learning: In the MIP-11 Groups plugin implementation for program_transformers/src/mpl_core_program/v1_asset.rs, the team has decided not to delete stale group rows before upserting because same-slot group changes are not expected to be common. They will adjust if analysis shows it becomes a problem.

Applied to files:

  • digital_asset_types/src/dao/scopes/asset.rs
🧬 Code graph analysis (1)
digital_asset_types/src/dao/scopes/asset.rs (1)
program_transformers/src/mpl_core_program/v1_asset.rs (2)
  • asset (722-727)
  • groups (477-488)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-api (ubuntu-22.04)
  • GitHub Check: test
🔇 Additional comments (2)
digital_asset_types/src/dao/scopes/asset.rs (2)

403-407: LGTM! Stale grouping filtering correctly positioned.

The call to filter_out_stale_asset_groupings now correctly runs after groups have been populated (lines 384-401), ensuring stale "group" entries are actually pruned before returning assets. This addresses the issue raised in the previous review where filtering occurred too early when asset.groups was still empty.


498-513: LGTM! Consistent stale grouping filtering for single-asset queries.

The stale grouping filter is correctly applied after groups are assembled (lines 498-510), maintaining consistency with the batch query path in get_related_for_assets.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70a268f and 04fcfc4.

📒 Files selected for processing (1)
  • digital_asset_types/src/dao/scopes/asset.rs (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pileks
PR: metaplex-foundation/digital-asset-rpc-infrastructure#268
File: program_transformers/src/mpl_core_program/v1_asset.rs:493-505
Timestamp: 2025-10-13T14:09:58.534Z
Learning: In the MIP-11 Groups plugin implementation for program_transformers/src/mpl_core_program/v1_asset.rs, the team has decided not to delete stale group rows before upserting because same-slot group changes are not expected to be common. They will adjust if analysis shows it becomes a problem.
📚 Learning: 2025-10-13T14:09:58.534Z
Learnt from: pileks
PR: metaplex-foundation/digital-asset-rpc-infrastructure#268
File: program_transformers/src/mpl_core_program/v1_asset.rs:493-505
Timestamp: 2025-10-13T14:09:58.534Z
Learning: In the MIP-11 Groups plugin implementation for program_transformers/src/mpl_core_program/v1_asset.rs, the team has decided not to delete stale group rows before upserting because same-slot group changes are not expected to be common. They will adjust if analysis shows it becomes a problem.

Applied to files:

  • digital_asset_types/src/dao/scopes/asset.rs
🧬 Code graph analysis (1)
digital_asset_types/src/dao/scopes/asset.rs (1)
program_transformers/src/mpl_core_program/v1_asset.rs (2)
  • asset (722-727)
  • groups (477-488)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-api (ubuntu-22.04)
  • GitHub Check: test
🔇 Additional comments (3)
digital_asset_types/src/dao/scopes/asset.rs (3)

407-410: LGTM! Correct placement of stale-group filtering.

The filter is correctly invoked after group population completes, addressing the previous review concern. However, this will only become effective once the query filters at lines 367-371 are updated to fetch "group" entries (see separate comment).

Based on learnings: The team decided not to delete stale rows in the transformer, so filtering happens here at query time instead.


506-520: LGTM! Consistent stale-group filtering in both branches.

The filter is correctly applied after groups are assembled, whether show_collection_metadata is true or false. This will work properly once the query at lines 492-496 is fixed to fetch "group" entries.


654-672: LGTM! Stale-group filtering logic is sound.

The function correctly:

  • Filters only group_key = "group" entries by slot_updated, preserving all other groupings
  • Retains the most recent "group" entries (max slot) and drops stale ones
  • Filters out null-valued "group" entries (used to mark group removal)

Note: This function is currently unreachable because the queries at lines 367-371 and 492-496 exclude "group" entries. It will become functional once those filters are fixed.

Optional: The ag.0.group_value.is_some() check on line 669 is redundant since upstream queries already filter group_value.is_not_null(). However, this defensive check is harmless and guards against future code paths that might skip the query filter.

Based on learnings: The team decided not to delete stale group rows in the transformer because same-slot group changes aren't expected to be common, so pruning happens here at query time instead.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7310f57 and a2a3e13.

📒 Files selected for processing (1)
  • digital_asset_types/src/dao/scopes/asset.rs (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pileks
PR: metaplex-foundation/digital-asset-rpc-infrastructure#268
File: program_transformers/src/mpl_core_program/v1_asset.rs:493-505
Timestamp: 2025-10-13T14:09:58.534Z
Learning: In the MIP-11 Groups plugin implementation for program_transformers/src/mpl_core_program/v1_asset.rs, the team has decided not to delete stale group rows before upserting because same-slot group changes are not expected to be common. They will adjust if analysis shows it becomes a problem.
📚 Learning: 2025-10-13T14:09:58.534Z
Learnt from: pileks
PR: metaplex-foundation/digital-asset-rpc-infrastructure#268
File: program_transformers/src/mpl_core_program/v1_asset.rs:493-505
Timestamp: 2025-10-13T14:09:58.534Z
Learning: In the MIP-11 Groups plugin implementation for program_transformers/src/mpl_core_program/v1_asset.rs, the team has decided not to delete stale group rows before upserting because same-slot group changes are not expected to be common. They will adjust if analysis shows it becomes a problem.

Applied to files:

  • digital_asset_types/src/dao/scopes/asset.rs
🧬 Code graph analysis (1)
digital_asset_types/src/dao/scopes/asset.rs (1)
program_transformers/src/mpl_core_program/v1_asset.rs (2)
  • asset (722-727)
  • groups (477-488)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-api (ubuntu-22.04)
  • GitHub Check: test
🔇 Additional comments (1)
digital_asset_types/src/dao/scopes/asset.rs (1)

413-416: LGTM: stale-group pruning now occurs after population.

Correct placement; this ensures stale rows are actually removed before returning assets.

Comment on lines +367 to +377
.filter(
Condition::any()
// Include collection groupings only if they have a non-null value
.add(
asset_grouping::Column::GroupKey
.eq("collection")
.and(asset_grouping::Column::GroupValue.is_not_null()),
)
// Include all other groupings (regardless of value)
.add(asset_grouping::Column::GroupKey.ne("collection")),
)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Whitelist group keys instead of using ne("collection") (clearer and more index-friendly).

Using GroupKey.ne("collection") may pull unrelated future keys and can prevent partial index use. If the intent is to return only legacy collections and MIP‑11 groups, explicitly match those keys.

Apply in both queries:

@@
-        .filter(
-            Condition::any()
-                // Include collection groupings only if they have a non-null value
-                .add(
-                    asset_grouping::Column::GroupKey
-                        .eq("collection")
-                        .and(asset_grouping::Column::GroupValue.is_not_null()),
-                )
-                // Include all other groupings (regardless of value)
-                .add(asset_grouping::Column::GroupKey.ne("collection")),
-        )
+        .filter(
+            Condition::any()
+                // Include collection groupings only if they have a non-null value
+                .add(
+                    asset_grouping::Column::GroupKey
+                        .eq("collection")
+                        .and(asset_grouping::Column::GroupValue.is_not_null()),
+                )
+                // Include MIP-11 group rows (including nulls to detect removals)
+                .add(asset_grouping::Column::GroupKey.eq("group")),
+        )

Repeat the same change in the single-asset query:

@@
-        .filter(
-            Condition::any()
-                // Include collection groupings only if they have a non-null value
-                .add(
-                    asset_grouping::Column::GroupKey
-                        .eq("collection")
-                        .and(asset_grouping::Column::GroupValue.is_not_null()),
-                )
-                // Include all other groupings (regardless of value)
-                .add(asset_grouping::Column::GroupKey.ne("collection")),
-        )
+        .filter(
+            Condition::any()
+                // Include collection groupings only if they have a non-null value
+                .add(
+                    asset_grouping::Column::GroupKey
+                        .eq("collection")
+                        .and(asset_grouping::Column::GroupValue.is_not_null()),
+                )
+                // Include MIP-11 group rows (including nulls to detect removals)
+                .add(asset_grouping::Column::GroupKey.eq("group")),
+        )

Also consider extracting this predicate into a small helper to keep both call sites in sync.

Also applies to: 498-508

🤖 Prompt for AI Agents
In digital_asset_types/src/dao/scopes/asset.rs around lines 367-377 (and
similarly at lines 498-508), the current predicate uses
GroupKey.ne("collection") which is broad and harms index usage; replace the
negative check with an explicit whitelist of allowed group keys (e.g., legacy
collection key and the MIP-11 group keys you expect) by changing the
.add(asset_grouping::Column::GroupKey.ne("collection")) clause to a
.add(Condition::any().add(asset_grouping::Column::GroupKey.eq("legacy_collection_key")).add(asset_grouping::Column::GroupKey.eq("mip11_key1")).add(...))
or equivalent whitelist checks, and extract that predicate into a small helper
function used by both query sites so the logic stays consistent.

Comment on lines +498 to +508
.filter(
Condition::any()
// Include collection groupings only if they have a non-null value
.add(
asset_grouping::Column::GroupKey
.eq("collection")
.and(asset_grouping::Column::GroupValue.is_not_null()),
)
// Include all other groupings (regardless of value)
.add(asset_grouping::Column::GroupKey.ne("collection")),
)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Deduplicate grouping-query construction to avoid drift between code paths.

Both the batch and single-asset paths build near-identical predicates and then apply the same stale pruning. Extract a helper (e.g., fn grouping_key_filter() -> Condition) and reuse it in both places to prevent future inconsistencies.

Example helper (outside these ranges):

fn grouping_key_filter() -> Condition {
    Condition::any()
        .add(
            asset_grouping::Column::GroupKey
                .eq("collection")
                .and(asset_grouping::Column::GroupValue.is_not_null()),
        )
        .add(asset_grouping::Column::GroupKey.eq("group"))
}

Also applies to: 518-518, 532-533

🤖 Prompt for AI Agents
In digital_asset_types/src/dao/scopes/asset.rs around lines 498-508 (and
similarly at 518 and 532-533), the grouping predicate is duplicated across the
batch and single-asset query paths; extract a helper function fn
grouping_key_filter() -> Condition that returns the shared Condition
construction (collection with non-null value OR the other grouping key), place
it outside these ranges (e.g., near other helper fns), and replace the inline
Condition::any() blocks at lines 498-508, 518, and 532-533 with calls to
grouping_key_filter() so both paths reuse the exact same predicate and avoid
drift.

Comment on lines +666 to +684
fn filter_out_stale_asset_groupings(
asset_groupings_with_date: &mut Vec<(asset_grouping::Model, Option<asset_data::Model>)>,
) {
// For core assets, any asset groupings that do not have the max
// `slot_updated` value are stale and should be removed.
// However, only filter out groupings where group_key is "group".
// All other groupings should be kept.
let max_slot_updated = asset_groupings_with_date
.iter()
.filter(|ag| ag.0.group_key == "group")
.map(|ag| ag.0.slot_updated)
.max();
if let Some(max_slot_updated) = max_slot_updated {
asset_groupings_with_date.retain(|ag| {
ag.0.group_key != "group"
|| (ag.0.slot_updated == max_slot_updated && ag.0.group_value.is_some())
});
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Simplify stale-group filtering; avoid Option<Option<_>> max while preserving behavior.

Minor readability refactor that keeps semantics identical (including the “null value at max slot means removal” rule).

 fn filter_out_stale_asset_groupings(
     asset_groupings_with_date: &mut Vec<(asset_grouping::Model, Option<asset_data::Model>)>,
 ) {
-    // For core assets, any asset groupings that do not have the max
-    // `slot_updated` value are stale and should be removed.
-    // However, only filter out groupings where group_key is "group".
-    // All other groupings should be kept.
-    let max_slot_updated = asset_groupings_with_date
-        .iter()
-        .filter(|ag| ag.0.group_key == "group")
-        .map(|ag| ag.0.slot_updated)
-        .max();
-    if let Some(max_slot_updated) = max_slot_updated {
-        asset_groupings_with_date.retain(|ag| {
-            ag.0.group_key != "group"
-                || (ag.0.slot_updated == max_slot_updated && ag.0.group_value.is_some())
-        });
-    }
+    // Keep only the latest "group" rows; allow null at max slot to signal removal.
+    let max_slot_updated = asset_groupings_with_date
+        .iter()
+        .filter(|ag| ag.0.group_key == "group")
+        .filter_map(|ag| ag.0.slot_updated)
+        .max();
+    if let Some(max_slot_updated) = max_slot_updated {
+        asset_groupings_with_date.retain(|ag| {
+            ag.0.group_key != "group"
+                || (ag.0.slot_updated == Some(max_slot_updated) && ag.0.group_value.is_some())
+        });
+    } else {
+        // No slot info; keep only non-null "group" values.
+        asset_groupings_with_date
+            .retain(|ag| ag.0.group_key != "group" || ag.0.group_value.is_some());
+    }
 }

Based on learnings

🤖 Prompt for AI Agents
digital_asset_types/src/dao/scopes/asset.rs lines 666-684: simplify the
stale-group filtering by computing the maximum slot_updated across group_key ==
"group" entries directly and then retaining only non-"group" entries or "group"
entries that both match that max slot and have a non-None group_value; replace
any mapping that produces an Option<Option<_>> for the max with a direct max on
slot_updated and then use a retain closure that checks ag.0.group_key != "group"
|| (ag.0.slot_updated == max_slot && ag.0.group_value.is_some()) so behavior
(including dropping null group_value at the max slot) is preserved.

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.

3 participants