Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/support many to many relations #947

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

prakha
Copy link
Contributor

@prakha prakha commented Mar 20, 2025

Issue

Why is this change needed?

There was no support for many-to-many relationships when parsing through Prisma. This branch adds support for many-to-many relationships.

What would you like reviewers to focus on?

I want the reviewer to review the logic of the code when there exist a "many to many" relationship and the generation of the join table to define the relationship.

Testing Verification

Screenshot 2025-03-21 at 4 31 29 AM

What was done

🤖 Generated by PR Agent at 5396a3b

  • Added support for "many-to-many" relationships in schema parsing.
  • Updated logic to handle cardinality and join table creation.
  • Modified rendering logic to exclude "many-to-many" edges in UI.
  • Enhanced schema validation to include "MANY_TO_MANY" cardinality.

Detailed Changes

Relevant files
Enhancement
parser.ts
Implement "many-to-many" relationship parsing and join table creation

frontend/packages/db-structure/src/parser/prisma/parser.ts

  • Added logic to identify and handle "many-to-many" relationships.
  • Created join tables for "many-to-many" relationships dynamically.
  • Updated cardinality determination logic for relationships.
  • Enhanced table and index creation for join tables.
  • +140/-23
    dbStructure.ts
    Extend cardinality schema to include "MANY_TO_MANY"           

    frontend/packages/db-structure/src/schema/dbStructure.ts

    • Added "MANY_TO_MANY" to cardinality schema validation.
    +1/-1     
    RelationshipEdge.tsx
    Prevent rendering of "many-to-many" relationship edges     

    frontend/packages/erd-core/src/features/erd/components/ERDContent/components/RelationshipEdge/RelationshipEdge.tsx

  • Added condition to skip rendering edges for "many-to-many"
    relationships.
  • +5/-0     

    Additional Notes


    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • prakha added 4 commits March 21, 2025 02:38

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.
    …snhip
    @prakha prakha requested a review from a team as a code owner March 20, 2025 23:02
    @prakha prakha requested review from hoshinotsuyoshi, FunamaYukina, junkisai, MH4GF, NoritakaIkeda and sasamuku and removed request for a team March 20, 2025 23:02
    Copy link

    changeset-bot bot commented Mar 20, 2025

    ⚠️ No Changeset found

    Latest commit: 5ef0bef

    Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

    This PR includes no changesets

    When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

    Click here to learn what changesets are, and how to add one.

    Click here if you're a maintainer who wants to add a changeset to this PR

    Copy link

    vercel bot commented Mar 20, 2025

    The latest updates on your projects. Learn more about Vercel for Git ↗︎

    Name Status Preview Comments Updated (UTC)
    liam-app ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 25, 2025 1:22pm
    2 Skipped Deployments
    Name Status Preview Comments Updated (UTC)
    test-liam-docs ⬜️ Ignored (Inspect) Mar 25, 2025 1:22pm
    test-liam-erd-sample ⬜️ Ignored (Inspect) Mar 25, 2025 1:22pm

    Copy link

    vercel bot commented Mar 20, 2025

    @prakha is attempting to deploy a commit to the ROUTE06 Core Team on Vercel.

    A member of the Team first needs to authorize it.

    Copy link
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Duplicate Code

    The code for creating columns in the join table has duplicate logic for primary and foreign tables. This could be refactored into a helper function to reduce redundancy and improve maintainability.

    for (const primaryTable of primaryTableIndices) {
      for (const field of primaryTable.fields) {
        const existingColumns =
          primaryTableName && tables[primaryTableName]?.columns['id']
        const columnName = primaryTable.model + field.name
        if (existingColumns && typeof existingColumns === 'object') {
          columns[columnName] = {
            name: columnName,
            type: existingColumns.type ?? 'id',
            default: existingColumns.default ?? '',
            notNull: existingColumns.notNull,
            unique: existingColumns.unique,
            primary: existingColumns.primary,
            comment: existingColumns.comment,
            check: existingColumns.check,
          }
        } else {
          columns[columnName] = {
            name: columnName,
            type: 'id',
            default: '',
            notNull: false,
            unique: false,
            primary: false,
            comment: '',
            check: '',
          }
        }
      }
    }
    
    for (const foreignTable of foreignTableIndices) {
      for (const field of foreignTable.fields) {
        const existingColumns =
          primaryTableName && tables[primaryTableName]?.columns['id']
        const columnName = foreignTable.model + field.name
        if (existingColumns && typeof existingColumns === 'object') {
          columns[columnName] = {
            name: columnName,
            type: existingColumns.type ?? 'id',
            default: existingColumns.default ?? '',
            notNull: existingColumns.notNull,
            unique: existingColumns.unique,
            primary: existingColumns.primary,
            comment: existingColumns.comment,
            check: existingColumns.check,
          }
        } else {
          columns[columnName] = {
            name: columnName,
            type: 'id',
            default: '',
            notNull: false,
            unique: false,
            primary: false,
            comment: '',
            check: '',
          }
        }
      }
    }
    Error Handling

    The code doesn't handle potential errors when accessing properties like tables[primaryTableName]?.columns['id']. If these values are undefined, it could lead to runtime errors.

        const existingColumns =
          primaryTableName && tables[primaryTableName]?.columns['id']
        const columnName = primaryTable.model + field.name
        if (existingColumns && typeof existingColumns === 'object') {
          columns[columnName] = {
            name: columnName,
            type: existingColumns.type ?? 'id',
            default: existingColumns.default ?? '',
            notNull: existingColumns.notNull,
            unique: existingColumns.unique,
            primary: existingColumns.primary,
            comment: existingColumns.comment,
            check: existingColumns.check,
          }
        } else {
          columns[columnName] = {
            name: columnName,
            type: 'id',
            default: '',
            notNull: false,
            unique: false,
            primary: false,
            comment: '',
            check: '',
          }
        }
      }
    }
    
    for (const foreignTable of foreignTableIndices) {
      for (const field of foreignTable.fields) {
        const existingColumns =
          primaryTableName && tables[primaryTableName]?.columns['id']
        const columnName = foreignTable.model + field.name
    Hardcoded Values

    The code uses hardcoded values like 'id' for column types and other properties. These should be constants or derived from the schema to make the code more maintainable.

        type: existingColumns.type ?? 'id',
        default: existingColumns.default ?? '',
        notNull: existingColumns.notNull,
        unique: existingColumns.unique,
        primary: existingColumns.primary,
        comment: existingColumns.comment,
        check: existingColumns.check,
      }
    } else {
      columns[columnName] = {
        name: columnName,
        type: 'id',
        default: '',
        notNull: false,
        unique: false,
        primary: false,
        comment: '',
        check: '',
      }

    This comment was marked as off-topic.

    Copy link
    Contributor

    qodo-merge-pro-for-open-source bot commented Mar 20, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    General
    Add separator for concatenated identifiers

    When concatenating strings to create identifiers, consider adding a separator
    between the parts to avoid potential naming conflicts and improve readability.
    Without a separator, "userprofile" could be from "user" + "profile" or "userp" +
    "rofile".

    frontend/packages/db-structure/src/parser/prisma/parser.ts [189-191]

     const existingColumns =
       primaryTableName && tables[primaryTableName]?.columns['id']
    -const columnName = primaryTable.model + field.name
    +const columnName = primaryTable.model + '_' + field.name
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    __

    Why: Adding a separator between concatenated identifiers is important for preventing naming conflicts and improving code readability. Without a separator, combined names can be ambiguous and lead to unexpected behavior in database column naming.

    Medium
    Avoid reserved identifier usage

    The destructuring pattern [, rel] uses a reserved identifier which can cause
    issues in strict TypeScript configurations. Use a more descriptive variable name
    or a different pattern to avoid potential problems.

    frontend/packages/db-structure/src/parser/prisma/parser.ts [168-172]

     const manyToManyRelationships = Object.fromEntries(
       Object.entries(relationships).filter(
    -    ([_, rel]) => rel.cardinality === 'MANY_TO_MANY',
    +    ([key, rel]) => rel.cardinality === 'MANY_TO_MANY',
       ),
     )
    • Apply this suggestion
    Suggestion importance[1-10]: 5

    __

    Why: Using the underscore as a variable name is a common convention for unused variables, but it can cause issues in strict TypeScript configurations. Replacing it with a more descriptive name improves code maintainability and avoids potential linting errors.

    Low
    Use empty fragment instead
    Suggestion Impact:The commit implemented the suggestion by replacing 'return null' with 'return <>' in the RelationshipEdge component when handling MANY_TO_MANY cardinality

    code diff:

    -    return null // Don't render anything if it's MANY_TO_MANY
    +    return <></> // Don't render anything if it's MANY_TO_MANY

    Returning null from a React component can lead to unexpected behavior in some
    cases. Consider returning an empty fragment (<></>) instead, which is the
    React-recommended way to render nothing.

    frontend/packages/erd-core/src/features/erd/components/ERDContent/components/RelationshipEdge/RelationshipEdge.tsx [24-26]

     if (data?.cardinality === 'MANY_TO_MANY') {
    -  return null // Don't render anything if it's MANY_TO_MANY
    +  return <></> // Don't render anything if it's MANY_TO_MANY
     }

    [Suggestion has been applied]

    Suggestion importance[1-10]: 4

    __

    Why: Using an empty fragment (<></>) instead of null is a React best practice for rendering nothing. While both approaches work in most cases, empty fragments are more consistent with React's component model and can prevent subtle issues in certain rendering scenarios.

    Low
    Learned
    best practice
    Validate that all required values exist before using them in string concatenation operations to prevent potential undefined behavior
    Suggestion Impact:The commit completely refactored the code, extracting the functionality into a separate function called processTableIndices. While it doesn't explicitly check for primaryTable.model or field.name being defined, it addresses the underlying issue by restructuring the code and adding validation for other potential undefined values (like relationshipValue and indexes).

    code diff:

    +      if (!relationshipValue) continue // Skip if undefined
    +
    +      const { primaryTableName, foreignTableName } = relationshipValue
           const indexes = dmmf?.datamodel?.indexes
    -      const primaryTableIndices = indexes.filter(
    -        (index) => index.model === primaryTableName && index.type === 'id',
    +      if (!indexes) continue

    The code doesn't validate that primaryTable.model and field.name are defined
    before concatenating them to create columnName. This could lead to unexpected
    behavior if either value is undefined. Add validation to ensure both values
    exist before using them.

    frontend/packages/db-structure/src/parser/prisma/parser.ts [189-202]

     const existingColumns =
       primaryTableName && tables[primaryTableName]?.columns['id']
    +// Validate that model and field name exist before concatenation
    +if (!primaryTable.model || !field.name) {
    +  continue; // Skip this iteration or handle the error appropriately
    +}
     const columnName = primaryTable.model + field.name
     if (existingColumns && typeof existingColumns === 'object') {
       columns[columnName] = {
         name: columnName,
         type: existingColumns.type ?? 'id',
         default: existingColumns.default ?? '',
         notNull: existingColumns.notNull,
         unique: existingColumns.unique,
         primary: existingColumns.primary,
         comment: existingColumns.comment,
         check: existingColumns.check,
       }
     }

    [Suggestion has been applied]

    Suggestion importance[1-10]: 6
    Low
    • Update

    Copy link
    Member

    @MH4GF MH4GF left a comment

    Choose a reason for hiding this comment

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

    @prakha
    Copy link
    Contributor Author

    prakha commented Mar 21, 2025

    prakha added 3 commits March 22, 2025 00:05

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.
    @prakha prakha requested a review from MH4GF March 21, 2025 19:17
    prakha added 3 commits March 24, 2025 03:00

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.
    Copy link
    Member

    @MH4GF MH4GF left a comment

    Choose a reason for hiding this comment

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

    @prakha I'm sorry, I was mistaken. What we wanted to do here was not to add "MANY_TO_MANY" to the cardinality, but to render the table that Prisma creates in a implicit way.
    Please read this comment? #609 (comment)

    @prakha
    Copy link
    Contributor Author

    prakha commented Mar 25, 2025

    @MH4GF Okay, should I remove the relationship between the two tables when hovering over the table?
    the table is getting rendered.

    @MH4GF
    Copy link
    Member

    MH4GF commented Mar 25, 2025

    @prakha Thanks!

    Okay, should I remove the relationship between the two tables when hovering over the table?
    the table is getting rendered.

    Yeah, this is a little bad as UX. However, if you can display an implicit table and use “one to many” to display it, there is no problem.

    prakha added 2 commits March 25, 2025 18:48

    Unverified

    This user has not yet uploaded their public signing key.

    Unverified

    This user has not yet uploaded their public signing key.
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    Prisma Parser - Support Many-to-many relations
    2 participants