Skip to content

fix(mysql): JSON path filters, Label unique key and chat label updates on MySQL - #2772

Open
omaralalwi wants to merge 1 commit into
evolution-foundation:developfrom
omaralalwi:fix/mysql-postgres-only-queries
Open

omaralalwi wants to merge 1 commit into
evolution-foundation:developfrom
omaralalwi:fix/mysql-postgres-only-queries

Conversation

@omaralalwi

@omaralalwi omaralalwi commented Oct 29, 2026 •

Copy link
Copy Markdown

📋 Description

develop already has MySQL variants for the raw SQL queries, but three MySQL paths are still broken. This PR fixes them and leaves PostgreSQL behaviour unchanged.

1. Prisma JSON path filters use the PostgreSQL form

Every key: { path: ['id'], … } filter passes the path as an array. Prisma only accepts that on PostgreSQL; on MySQL the path must be a JSONPath string ('$.id'). On MySQL every lookup by message key (findMessages, edit/delete/update by message id, Chatwoot sync, the Meta channel) fails Prisma validation with Argument `path`: Invalid value provided. Expected String.

A small jsonPath(field) helper (src/utils/jsonPath.ts) returns '$.field' on MySQL and ['field'] otherwise, and the 28 filters use it.

2. Label has no (labelId, instanceId) unique key on MySQL

The Baileys label sync upserts by labelId_instanceId. The PostgreSQL schema has had that key since 20240813003116_make_label_unique_for_instance, but the MySQL schema and migrations never got it. The MySQL Prisma client therefore rejects the upsert at runtime with Unknown argument `labelId_instanceId` , and tsc --noEmit fails with two TS2353 errors in whatsapp.baileys.service.ts when the client is generated from the MySQL schema. The PR adds @@unique([labelId, instanceId]) and a migration that creates the index.

3. MySQL chat label updates lose or duplicate labels

  • removeLabel: when the label is not on the chat, JSON_SEARCH returns NULL, so JSON_REMOVE returns NULL, and the COALESCE then replaces the chat's labels with []. Every other label on the chat is wiped.
  • addLabel: JSON_ARRAY_APPEND appends even when the label is already present, so repeated label events duplicate it. The PostgreSQL branch deduplicates.

Both now check JSON_CONTAINS first. JSON_SEARCH uses an escape character, so a label id containing % or _ is matched literally.

🔗 Related Issue

Related to #2344 (MySQL errors on v2.3.6). PR #2769 touches the same area against main; this one is scoped to what is still missing on develop and targets develop as described in CONTRIBUTING.

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (if applicable)

On MySQL 8.4, against a fresh database migrated with prisma/mysql-migrations (including the new migration):

  • Ran the generated MySQL Prisma 7 client (@prisma/adapter-mariadb), with the develop schema and then with this PR's schema:
    • key: { path: ['id'] } fails with Expected String on both; path: '$.id' (what jsonPath returns) matches the row.
    • label.upsert({ where: { labelId_instanceId } }) fails with Unknown argument on develop and succeeds with this PR.
  • Ran the two label statements as prepared statements, with the argument order used in the code: add to a new chat, add a second label, add a duplicate (kept once), remove an absent label (other labels kept), remove a present label, remove a label containing % (matched literally), remove from NULL labels.
  • tsc --noEmit: 2 errors on develop with the MySQL client, 0 with this PR for both the MySQL and the PostgreSQL client. eslint passes on the changed files.

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

The new migration fails if an existing MySQL install already has duplicate (labelId, instanceId) rows, the same way the PostgreSQL migration would. Labels are re-synced from WhatsApp, so removing duplicates before upgrading is safe.

Summary by Sourcery

Fix MySQL message JSON filtering, label synchronization, and chat label updates without changing PostgreSQL behavior.

Bug Fixes:

  • Fix MySQL JSON field filtering across message lookup, synchronization, editing, deletion, and channel operations.
  • Prevent MySQL chat label updates from removing existing labels or adding duplicates, including correct handling of special characters in label IDs.
  • Enable Baileys label synchronization on MySQL by adding the missing per-instance label uniqueness constraint.

Enhancements:

  • Centralize database-specific Prisma JSON path handling while preserving PostgreSQL behavior.

- Prisma JSON field filters take the path as an array on PostgreSQL but
  as a '$.field' string on MySQL; add a jsonPath() helper and use it for
  every key filter, so message lookups by key no longer fail validation.
- Add the (labelId, instanceId) unique key to the MySQL schema with a
  migration; the Baileys label sync upserts on it.
- addLabel/removeLabel (MySQL): skip a label that is already present, and
  stop wiping all chat labels when removing one that is not there
  (JSON_SEARCH returned NULL).
@sourcery-ai

sourcery-ai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Fixes the remaining MySQL-specific failures by selecting the correct Prisma JSON path format, adding the composite Label uniqueness key and migration, and making chat label add/remove SQL operations safe and idempotent, while preserving PostgreSQL behavior.

Sequence diagram for database-aware JSON path filtering

sequenceDiagram
    participant Service
    participant jsonPath
    participant Prisma
    participant Database
    Service->>jsonPath: jsonPath('id')
    alt MySQL provider
        jsonPath-->>Service: '$.id'
    else PostgreSQL provider
        jsonPath-->>Service: ['id']
    end
    Service->>Prisma: findFirst or findMany with key.path
    Prisma->>Database: Execute JSON path filter
    Database-->>Prisma: Matching messages
Loading

Entity relationship diagram for the MySQL Label uniqueness key

erDiagram
    Label {
        string labelId
        string instanceId
        string labelId_instanceId UK
    }
    Label }o--|| Instance : belongs_to
Loading

Flow diagram for idempotent MySQL chat label updates

flowchart TD
    Event[Chat label event] --> Contains{JSON_CONTAINS label?}
    Contains -->|Add and present| Keep[Keep labels unchanged]
    Contains -->|Add and absent| Append[JSON_ARRAY_APPEND label]
    Contains -->|Remove and present| Search[JSON_SEARCH with escaped label]
    Search --> Remove[JSON_REMOVE matched label]
    Contains -->|Remove and absent| Preserve[Preserve existing labels]
    Append --> Updated[Update chat labels]
    Remove --> Updated
    Keep --> Updated
    Preserve --> Updated
Loading

File-Level Changes

Change Details Files
Abstract Prisma JSON filter paths across database providers so MySQL uses JSONPath strings while PostgreSQL retains array paths.
  • Added a provider-aware jsonPath helper.
  • Replaced message-key JSON filters across Baileys, channel, Meta, and Chatwoot flows.
  • Covered message lookup, update/delete, synchronization, and query filtering paths.
src/utils/jsonPath.ts
src/api/integrations/channel/meta/whatsapp.business.service.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
src/api/services/channel.service.ts
Add the missing MySQL uniqueness constraint required by label synchronization upserts.
  • Added the composite Label unique key on labelId and instanceId to the MySQL Prisma schema.
  • Added a MySQL migration creating the corresponding unique index.
prisma/mysql-schema.prisma
prisma/mysql-migrations/20261029040337_add_label_instance_unique_key/migration.sql
Make MySQL chat label mutations idempotent and preserve labels when removing missing values.
  • Guarded label insertion with JSON_CONTAINS to prevent duplicates.
  • Guarded removal with JSON_CONTAINS so absent labels do not null out the label array.
  • Escaped JSON_SEARCH wildcard characters and supplied the MySQL escape character for literal label matching.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. The MySQL upsert changes stored chat-label arrays, so a faulty duplicate check or removal could leave labels missing or duplicated; reverting the code would not undo those changes, though the affected labels can be corrected. The new unique index can also make writes fail if the assumed per-instance uniqueness is wrong, but removing the index reverses that constraint.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

This branch has not been deployed

No deployments
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