Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@baiyuanneko
Copy link
Contributor

@baiyuanneko baiyuanneko commented Oct 31, 2025

Currently in 2.0.0alpha9, I've noticed a bug that Mix Studio will have a high possibility to fail to save correctly.

Reproduce the issue

  1. Create a blank Mix, then add several tracks into the Mix via right clicking any of a track, then click add to Mix in the context menu (not via Mix Studio).
  2. Assumes that you added 4 tracks in the last step.
  3. Open Mix Studio to edit it, now use the Mix studio to add some tracks, then save.
  4. Assumes that you added another 3 tracks in the last step.
  5. Now open Mix Studio again, the number of tracks in Mix will not be 7, but remains 4. And, the tracks in the Mix now will become a random mixture of the tracks added in Step 1 or Step 3.

What this PR fix

Through some investigations, I think Mix Studio save button cannot properly handle query scenarios where the same operator has multiple different parameters

  • Uses operator as the unique key → HashMap<String, mix_queries::Model>
  • Can only store one query per operator
  • If multiple queries with the same operator but different parameters are added, later ones will overwrite earlier ones

I've attempted to operate a fix, and I've tested that it works on my machine, without breaking other functions.

Summary by Sourcery

Improve mix query persistence by switching to a compound operator-parameter key in the database operations, updating identifier generation, and ensuring the front-end correctly passes query data to the save endpoint.

Bug Fixes:

  • Fix Mix Studio save functionality to correctly persist all track queries and prevent query loss

Enhancements:

  • Use a compound key of operator and parameter in replace_mix_queries and update CRUD logic to support multiple queries per operator
  • Include parameter in HLC UUID generation for mix queries to ensure unique identification
  • Refactor UpdateMixRequest and Mix Studio dialog to construct and pass the query list consistently to the API

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 31, 2025

Reviewer's Guide

This PR addresses a bug in Mix Studio save functionality by changing query deduplication from using operator-only keys to composite (operator, parameter) keys in the database layer, and refactors both handler and UI code to extract and consistently pass query mappings.

Sequence diagram for Mix Studio save operation with composite query keys

sequenceDiagram
    actor User
    participant MixStudioDialog
    participant MixStudioController
    participant MixEditorData
    participant HubHandler
    participant Database
    User->>MixStudioDialog: Clicks Save
    MixStudioDialog->>MixStudioController: getData()
    MixStudioController->>MixEditorData: Returns editor data
    MixStudioDialog->>MixStudioDialog: mixEditorDataToQuery(editorData)
    MixStudioDialog->>HubHandler: updateMix(..., queries)
    HubHandler->>Database: replace_mix_queries(..., queries)
    Database->>Database: Deduplicate by (operator, parameter)
    Database-->>HubHandler: Save result
    HubHandler-->>MixStudioDialog: Save result
    MixStudioDialog-->>User: Save complete
Loading

Class diagram for updated Mix Query deduplication logic

classDiagram
    class MixQuery {
        +String operator
        +String parameter
        +String group
        +String hlc_uuid
        +String updated_at_hlc_ts
        +int updated_at_hlc_ver
        +String updated_at_hlc_nid
    }
    class MixQueryEntity {
        +update(ActiveModel)
        +delete_by_id(id)
    }
    class replace_mix_queries {
        +replace_mix_queries(db, node_id, mix_id, mix_hlc_uuid, operator_parameters, group)
    }
    MixQueryEntity <|-- MixQuery
    replace_mix_queries --> MixQueryEntity
    replace_mix_queries --> MixQuery

    class HashMap_of_String_String_MixQuery {
        +insert((operator, parameter), MixQuery)
        +get((operator, parameter))
    }
Loading

File-Level Changes

Change Details Files
Use composite key (operator, parameter) for mix query deduplication in replace_mix_queries
  • Replaced existing_by_operator HashMap<String,Model> with existing_by_key HashMap<(String, String),Model>
  • Built new_query_keys as HashSet<(String, String)> from operator_parameters
  • Simplified existence check to contains_key and removed outdated update logic
  • Updated new record creation to include parameter in hlc_uuid generation
  • Revised deletion loop to enforce composite key lookup and updated error formatting
database/src/actions/mixes.rs
Refactor query mapping in Mix update handler
  • Extracted request.queries mapping into local Vec<(String, String)> queries_vec
  • Passed queries_vec to replace_mix_queries instead of inline mapping
native/hub/src/handlers/mix.rs
Extract editor data and queries in Mix Studio UI
  • Captured editor data via _controller.getData() into local variable
  • Computed queries from editorData using mixEditorDataToQuery
  • Replaced inline mixEditorDataToQuery calls in createMix/updateMix with queries variable
lib/utils/dialogs/mix/mix_studio_dialog.dart

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

Blocking issues:

  • Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)

General comments:

  • For changed queries, consider updating the existing record (incrementing its HLC version) instead of delete+insert to preserve history.
  • Use a named struct or type alias for the (operator, parameter) key rather than raw tuples to improve readability.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- For changed queries, consider updating the existing record (incrementing its HLC version) instead of delete+insert to preserve history.
- Use a named struct or type alias for the (operator, parameter) key rather than raw tuples to improve readability.

## Individual Comments

### Comment 1
<location> `database/src/actions/mixes.rs:324` </location>
<code_context>
-    for (operator, parameter) in &operator_parameters {
-        new_operator_params.insert(operator.clone(), parameter.clone());
-    }
+    // Create a set of new query keys for easy lookup
+    let new_query_keys: HashSet<(String, String)> = operator_parameters
+        .iter()
</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing manual contains checks and loop nesting with set-difference iterations for clearer intent and simpler logic.

```markdown
You can collapse the two “contains + loop” phases into a pair of set‐difference iterations—this makes the intent clearer (insert what’s new, delete what’s gone) and removes manual `contains` checks inside loops:

```rust
// 1) build a map of existing (op,param) → Model
let existing_by_key = existing_queries
    .into_iter()
    .map(|q| ((q.operator.clone(), q.parameter.clone()), q))
    .collect::<HashMap<_, _>>();

// 2) collect sets of keys
let new_keys = operator_parameters
    .iter()
    .cloned()
    .collect::<HashSet<(String, String)>>();
let existing_keys = existing_by_key.keys().cloned().collect::<HashSet<_>>();

// 3) insert missing entries
for (operator, parameter) in new_keys.difference(&existing_keys) {
    let new_query = mix_queries::ActiveModel {
        mix_id: ActiveValue::Set(mix_id),
        operator: ActiveValue::Set(operator.clone()),
        parameter: ActiveValue::Set(parameter.clone()),
        /* … */
    };
    MixQueryEntity::insert(new_query)
        .exec(&txn).await
        .with_context(|| format!("Failed to insert `{operator}({parameter})`"))?;
}

// 4) delete stale entries
for key in existing_keys.difference(&new_keys) {
    let existing = &existing_by_key[key];
    MixQueryEntity::delete_by_id(existing.id)
        .exec(&txn).await
        .with_context(|| format!("Failed to delete `{}`", format!("{}({})", key.0, key.1)))?;
}
```

This drops the extra `new_query_keys` map and the `if/else` nesting. You still build exactly the same inserts/deletes, and you preserve all functionality.
</issue_to_address>

### Comment 2
<location> `database/src/actions/mixes.rs:382` </location>
<code_context>
existing_key.1
</code_context>

<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

*Source: gitleaks*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

for (operator, parameter) in &operator_parameters {
new_operator_params.insert(operator.clone(), parameter.clone());
}
// Create a set of new query keys for easy lookup
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider replacing manual contains checks and loop nesting with set-difference iterations for clearer intent and simpler logic.

You can collapse the two “contains + loop” phases into a pair of set‐difference iterations—this makes the intent clearer (insert what’s new, delete what’s gone) and removes manual `contains` checks inside loops:

```rust
// 1) build a map of existing (op,param) → Model
let existing_by_key = existing_queries
    .into_iter()
    .map(|q| ((q.operator.clone(), q.parameter.clone()), q))
    .collect::<HashMap<_, _>>();

// 2) collect sets of keys
let new_keys = operator_parameters
    .iter()
    .cloned()
    .collect::<HashSet<(String, String)>>();
let existing_keys = existing_by_key.keys().cloned().collect::<HashSet<_>>();

// 3) insert missing entries
for (operator, parameter) in new_keys.difference(&existing_keys) {
    let new_query = mix_queries::ActiveModel {
        mix_id: ActiveValue::Set(mix_id),
        operator: ActiveValue::Set(operator.clone()),
        parameter: ActiveValue::Set(parameter.clone()),
        /* … */
    };
    MixQueryEntity::insert(new_query)
        .exec(&txn).await
        .with_context(|| format!("Failed to insert `{operator}({parameter})`"))?;
}

// 4) delete stale entries
for key in existing_keys.difference(&new_keys) {
    let existing = &existing_by_key[key];
    MixQueryEntity::delete_by_id(existing.id)
        .exec(&txn).await
        .with_context(|| format!("Failed to delete `{}`", format!("{}({})", key.0, key.1)))?;
}

This drops the extra new_query_keys map and the if/else nesting. You still build exactly the same inserts/deletes, and you preserve all functionality.

@dosubot dosubot bot added the C - bug Something isn't working label Oct 31, 2025
@Losses
Copy link
Owner

Losses commented Nov 1, 2025

Oh gosh! Thank you!

@Losses Losses merged commit d94bbc5 into Losses:master Nov 1, 2025
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C - bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants