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: [#280] Add Foreign methods #715

Merged
merged 3 commits into from
Nov 8, 2024
Merged

feat: [#280] Add Foreign methods #715

merged 3 commits into from
Nov 8, 2024

Conversation

hwbrzzl
Copy link
Contributor

@hwbrzzl hwbrzzl commented Nov 7, 2024

📑 Description

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced foreign key support in table schema management.
    • Added methods to define and compile foreign key constraints.
    • New interface for configuring foreign key behaviors.
  • Bug Fixes

    • Enhanced SQL generation for foreign key constraints.
  • Tests

    • Added tests for foreign key creation and validation in the schema.

✅ Checks

  • Added test cases for my code

Copy link
Contributor

coderabbitai bot commented Nov 7, 2024

Walkthrough

The changes in this pull request introduce several new methods and interfaces to enhance the functionality of the database schema management system. Key additions include new methods in the Blueprint and Grammar interfaces, a new ForeignKeyDefinition interface, and the implementation of foreign key handling in SQL generation. Additionally, new tests are added to validate the functionality of these features, ensuring that foreign key constraints can be created and managed effectively.

Changes

File Path Change Summary
contracts/database/schema/blueprint.go Added methods: Foreign(column ...string) ForeignKeyDefinition, GetPrefix() string.
contracts/database/schema/grammar.go Added method: CompileForeign(blueprint Blueprint, command *Command) string.
contracts/database/schema/index.go Added new interface: ForeignKeyDefinition with methods for configuring foreign key constraints.
database/schema/blueprint.go Added methods: Foreign(column ...string) schema.ForeignKeyDefinition, GetPrefix() string.
database/schema/constants/constants.go Added constant: CommandForeign.
database/schema/grammars/postgres.go Added method: CompileForeign(blueprint schema.Blueprint, command *schema.Command) string.
database/schema/grammars/postgres_test.go Added test method: TestCompileForeign() to validate CompileForeign functionality.
database/schema/index.go Added type: ForeignKeyDefinition with methods for configuring foreign key constraints.
database/schema/schema_test.go Added test method: TestForeign() to validate foreign key creation in schema.

Possibly related PRs

Suggested reviewers

  • devhaozi

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hwbrzzl hwbrzzl marked this pull request as ready for review November 7, 2024 08:54
@hwbrzzl hwbrzzl requested a review from a team as a code owner November 7, 2024 08:54
Copy link

codecov bot commented Nov 7, 2024

Codecov Report

Attention: Patch coverage is 51.51515% with 32 lines in your changes missing coverage. Please review.

Project coverage is 69.77%. Comparing base (0a28acb) to head (8cf910f).
Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
database/schema/index.go 27.27% 32 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #715      +/-   ##
==========================================
- Coverage   69.84%   69.77%   -0.08%     
==========================================
  Files         198      198              
  Lines       15562    15628      +66     
==========================================
+ Hits        10869    10904      +35     
- Misses       4101     4132      +31     
  Partials      592      592              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@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: 8

🧹 Outside diff range and nitpick comments (12)
contracts/database/schema/index.go (1)

3-14: Add documentation and consider additional foreign key options.

The interface design is well-structured with a fluent API pattern and comprehensive foreign key actions. However, consider the following improvements:

  1. Add GoDoc comments for the interface and each method to document their purpose and behavior.
  2. Consider adding methods for additional foreign key options:
    • SetDefaultOnDelete() and SetDefaultOnUpdate() for SET DEFAULT action
    • Deferrable() and InitiallyDeferred() for deferrable constraints

Example documentation:

// ForeignKeyDefinition provides a fluent interface for configuring
// foreign key constraints in database schemas.
type ForeignKeyDefinition interface {
    // CascadeOnDelete configures the foreign key to cascade deletes
    // from the referenced table to the referencing table.
    CascadeOnDelete() ForeignKeyDefinition
    // ... (document other methods)
}
contracts/database/schema/blueprint.go (2)

14-15: Enhance method documentation with usage examples.

While the comment describes the purpose, it would be more helpful to include:

  • Parameter description
  • Return value explanation
  • Usage example

Consider expanding the documentation like this:

-// Foreign Specify a foreign key for the table.
+// Foreign Specify a foreign key for the table.
+// Parameters:
+//   - column: One or more column names to create the foreign key on
+// Returns:
+//   - ForeignKeyDefinition: A builder to define the foreign key constraints
+// Example:
+//   table.Foreign("user_id").References("id").On("users")

14-21: Consider additional foreign key management methods.

The interface additions look good, but consider adding complementary methods for comprehensive foreign key management:

  1. DropForeign - To remove foreign key constraints
  2. HasForeign - To check if a foreign key exists
  3. GetForeignKeys - To retrieve all foreign keys defined on the table

These methods would provide a complete API for foreign key lifecycle management. Would you like me to help draft the interface definitions for these methods?

database/schema/index.go (2)

7-15: LGTM with a suggestion for defensive programming.

The type definition and constructor are well-implemented. Consider adding a nil check in the constructor for defensive programming.

 func NewForeignKeyDefinition(command *schema.Command) schema.ForeignKeyDefinition {
+	if command == nil {
+		panic("command cannot be nil")
+	}
 	return &ForeignKeyDefinition{
 		command: command,
 	}
 }

17-27: Consider using constants for action values.

The cascade methods are well-implemented, but using string literals for action values could lead to maintenance issues and inconsistencies.

+const (
+	ActionCascade   = "cascade"
+	ActionNoAction  = "no action"
+	ActionSetNull   = "set null"
+	ActionRestrict  = "restrict"
+)

 func (f *ForeignKeyDefinition) CascadeOnDelete() schema.ForeignKeyDefinition {
-	f.command.OnDelete = "cascade"
+	f.command.OnDelete = ActionCascade
 	return f
 }

 func (f *ForeignKeyDefinition) CascadeOnUpdate() schema.ForeignKeyDefinition {
-	f.command.OnUpdate = "cascade"
+	f.command.OnUpdate = ActionCascade
 	return f
 }
database/schema/blueprint.go (2)

63-67: Add documentation and input validation.

While the implementation follows the existing patterns, consider these improvements:

  1. Add godoc comments explaining the method's purpose and usage
  2. Add validation to ensure at least one column is provided
+// Foreign creates a new foreign key constraint on the table.
+// It accepts one or more column names that will form the foreign key.
 func (r *Blueprint) Foreign(column ...string) schema.ForeignKeyDefinition {
+	if len(column) == 0 {
+		panic("at least one column is required for foreign key")
+	}
 	command := r.indexCommand(constants.CommandForeign, column)
 
 	return NewForeignKeyDefinition(command)
 }

Line range hint 63-163: Consider adding integration tests for foreign key functionality.

Since this is part of a larger feature adding foreign key support, ensure that:

  1. Integration tests cover the full lifecycle of foreign key operations
  2. Documentation is added explaining the foreign key API usage
  3. Error cases are handled consistently across different database drivers

Would you like help creating integration tests or documentation for this feature?

database/schema/grammars/postgres.go (1)

60-75: Add input validation for required fields.

The method should validate that required fields are not empty to prevent invalid SQL generation.

Add validation checks at the start of the method:

 func (r *Postgres) CompileForeign(blueprint schema.Blueprint, command *schema.Command) string {
+	if command.Index == "" {
+		panic("Foreign key constraint name is required")
+	}
+	if len(command.Columns) == 0 {
+		panic("Foreign key columns are required")
+	}
+	if command.On == "" {
+		panic("Referenced table is required")
+	}
+	if len(command.References) == 0 {
+		panic("Referenced columns are required")
+	}
+	if len(command.Columns) != len(command.References) {
+		panic("Number of foreign key columns must match number of referenced columns")
+	}

 	sql := fmt.Sprintf("alter table %s add constraint %s foreign key (%s) references %s (%s)",
database/schema/schema_test.go (1)

91-93: Consider improving test isolation.

While the test correctly uses the existing test infrastructure, it should ensure proper isolation between different database drivers. Consider wrapping the test in a transaction that can be rolled back.

 for driver, testQuery := range s.driverToTestQuery {
     s.Run(driver.String(), func() {
+        // Start transaction
+        tx := testQuery.Query().Begin()
+        defer tx.Rollback()
+
         schema := GetTestSchema(testQuery, s.driverToTestQuery)
database/schema/grammars/postgres_test.go (3)

104-131: Consider adding more test cases for better coverage.

While the current test cases cover basic scenarios, consider adding:

  • Edge cases with multiple columns in the foreign key
  • Negative test cases (e.g., invalid references)
  • Error cases validation

Example additional test cases:

{
    name: "with multiple columns",
    command: &contractsschema.Command{
        Index:      "fk_users_company_department",
        Columns:    []string{"company_id", "department_id"},
        On:         "departments",
        References: []string{"company_id", "id"},
        OnDelete:   "cascade",
    },
    expectSql: "alter table users add constraint fk_users_company_department foreign key (company_id, department_id) references goravel_departments (company_id, id) on delete cascade",
},
{
    name: "with invalid reference columns count",
    command: &contractsschema.Command{
        Index:      "fk_invalid",
        Columns:    []string{"role_id"},
        On:         "roles",
        References: []string{"id", "extra_id"},
    },
    expectError: "number of referencing columns must match referenced columns",
}

98-102: Enhance mock verification and cleanup.

Consider adding mock verification to ensure all expected calls were made and cleaning up between test cases:

 func (s *PostgresSuite) TestCompileForeign() {
     var mockBlueprint *mocksschema.Blueprint

     beforeEach := func() {
         mockBlueprint = mocksschema.NewBlueprint(s.T())
         mockBlueprint.EXPECT().GetPrefix().Return("goravel_").Once()
         mockBlueprint.EXPECT().GetTableName().Return("users").Once()
     }

+    afterEach := func() {
+        // Verify that all expected mock calls were made
+        s.True(mockBlueprint.AssertExpectations(s.T()))
+    }

     // ... test cases ...

     for _, test := range tests {
         s.Run(test.name, func() {
             beforeEach()
+            defer afterEach()

             sql := s.grammar.CompileForeign(mockBlueprint, test.command)
             s.Equal(test.expectSql, sql)
         })
     }
 }

Also applies to: 133-141


137-139: Consider enhancing SQL validation.

The current SQL validation using string equality is basic. Consider:

  1. Adding component-wise validation to check individual parts of the SQL statement
  2. Using case-insensitive comparison for SQL keywords
  3. Validating SQL syntax structure

Example enhanced validation:

func (s *PostgresSuite) validateForeignKeySQL(sql string, expected string) {
    // Case-insensitive comparison for SQL keywords
    normalizedSQL := strings.ToUpper(sql)
    normalizedExpected := strings.ToUpper(expected)
    s.Equal(normalizedExpected, normalizedSQL)

    // Component-wise validation
    s.Contains(sql, "ALTER TABLE")
    s.Contains(sql, "ADD CONSTRAINT")
    s.Contains(sql, "FOREIGN KEY")
    s.Contains(sql, "REFERENCES")

    // Validate basic SQL syntax structure
    parts := strings.Split(sql, " ")
    s.Greater(len(parts), 8, "SQL should have minimum required parts")
}

Then use it in the test:

-s.Equal(test.expectSql, sql)
+s.validateForeignKeySQL(sql, test.expectSql)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6defb9f and 81d4e73.

⛔ Files ignored due to path filters (3)
  • mocks/database/schema/Blueprint.go is excluded by !mocks/**
  • mocks/database/schema/ForeignKeyDefinition.go is excluded by !mocks/**
  • mocks/database/schema/Grammar.go is excluded by !mocks/**
📒 Files selected for processing (9)
  • contracts/database/schema/blueprint.go (1 hunks)
  • contracts/database/schema/grammar.go (1 hunks)
  • contracts/database/schema/index.go (1 hunks)
  • database/schema/blueprint.go (3 hunks)
  • database/schema/constants/constants.go (1 hunks)
  • database/schema/grammars/postgres.go (1 hunks)
  • database/schema/grammars/postgres_test.go (1 hunks)
  • database/schema/index.go (1 hunks)
  • database/schema/schema_test.go (1 hunks)
🔇 Additional comments (9)
database/schema/constants/constants.go (1)

8-8: LGTM! The constant addition follows the established pattern.

The new CommandForeign constant maintains consistency with existing command naming conventions and is properly positioned in alphabetical order.

Let's verify its usage in the codebase:

✅ Verification successful

The constant is properly integrated and used consistently

The verification confirms that CommandForeign is:

  • Correctly defined in constants.go
  • Used appropriately in Blueprint's foreign key implementation
  • Referenced in the SQL compilation logic
  • No instances of hardcoded "foreign" string that should be using the constant instead
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of CommandForeign constant
# Expected: Should find references in Blueprint's Foreign method and SQL generation

# Search for direct usage of the constant
rg -A 3 "CommandForeign" 

# Search for string literal "foreign" to ensure we're not missing any places 
# where we should be using the constant
rg -A 3 '"foreign"'

Length of output: 1153

contracts/database/schema/blueprint.go (1)

20-21: Enhance GetPrefix documentation and verify its usage.

The current documentation lacks context about the purpose and importance of table prefixes.

Consider expanding the documentation:

-// GetPrefix Get the table prefix.
+// GetPrefix Get the table prefix.
+// Returns the configured prefix for database tables, which is used for:
+// - Multi-tenant database organization
+// - Namespace isolation
+// - Database schema organization
+// Returns an empty string if no prefix is configured.

Let's verify how this prefix is being used:

contracts/database/schema/grammar.go (2)

18-19: LGTM! Well-designed interface addition.

The new CompileForeign method follows the established interface patterns and maintains consistency with existing compilation methods in terms of naming, parameter types, and return values.


18-19: Consider enhancing method documentation and ensure test coverage.

While the method signature is clear, consider expanding the documentation to include:

  • Parameter descriptions (what the Blueprint and Command should contain)
  • Return value description (expected SQL format)
  • Any specific constraints or requirements for foreign key compilation

Also, noting from the PR objectives that test cases are pending. Please ensure comprehensive test coverage for this new interface method across all implementations.

Let's check for existing test patterns:

database/schema/index.go (2)

41-51: LGTM!

The NoAction methods are well-implemented and follow the established pattern.


65-75: LGTM!

The Restrict methods are well-implemented and follow the established pattern.

database/schema/blueprint.go (1)

82-84: LGTM!

Simple and correct implementation of a getter method.

database/schema/grammars/postgres.go (1)

60-75: Consider adding unit tests.

Based on the PR objectives mentioning unchecked test cases, ensure comprehensive test coverage for the new CompileForeign method.

Let's verify if tests exist:

Would you like me to help generate comprehensive test cases for this method? The tests should cover:

  1. Basic foreign key creation
  2. Multiple column foreign keys
  3. ON DELETE/UPDATE actions
  4. Invalid input handling
  5. Identifier escaping
database/schema/grammars/postgres_test.go (1)

95-103: LGTM! Well-structured test setup.

The test follows good practices with a clear setup using beforeEach and proper mock initialization.

database/schema/index.go Show resolved Hide resolved
database/schema/index.go Show resolved Hide resolved
database/schema/index.go Show resolved Hide resolved
database/schema/blueprint.go Show resolved Hide resolved
database/schema/grammars/postgres.go Show resolved Hide resolved
database/schema/grammars/postgres.go Show resolved Hide resolved
database/schema/schema_test.go Show resolved Hide resolved
database/schema/schema_test.go Show resolved Hide resolved
@hwbrzzl hwbrzzl enabled auto-merge (squash) November 8, 2024 02:20
@hwbrzzl hwbrzzl merged commit 7674cb5 into master Nov 8, 2024
10 of 12 checks passed
@hwbrzzl hwbrzzl deleted the bowen/#280-18 branch November 8, 2024 02:27
@coderabbitai coderabbitai bot mentioned this pull request Dec 5, 2024
1 task
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.

2 participants