Skip to content

fix: database compatibility and plugin-not-found graceful degradation - #65

Open
windy664 wants to merge 1 commit into
ikafly144:masterfrom
windy664:fix/database-compat-and-plugin-fallback
Open

fix: database compatibility and plugin-not-found graceful degradation#65
windy664 wants to merge 1 commit into
ikafly144:masterfrom
windy664:fix/database-compat-and-plugin-fallback

Conversation

@windy664

Copy link
Copy Markdown

Summary

Fix database compatibility issues with H2 and add graceful degradation when plugins are not found.

Changes

Database Compatibility (H2)

  • Replace ALTER TABLE IF NOT EXISTS with JDBC metadata column checks
  • Add addColumnIfNotExists() and dropColumnIfExists() helper methods
  • Change address column from TEXT to VARCHAR(255) for UNIQUE constraint compatibility
  • Add null check for database connection in setup()
  • Throw RuntimeException on database setup failure instead of silent e.printStackTrace()

Plugin-Not-Found Graceful Degradation

When a plugin that registered a PluginMailUser is not loaded:

UserConverter.readUser:

  • Catches IllegalStateException from PluginUser.toUser()
  • Returns null instead of crashing

Base.getMails/getMail:

  • Handles null sender/receiver
  • Creates fallback DummyMailUser with "Unknown Sender/Receiver"

Base.getMailTemplate(s):

  • Handles null sender
  • Creates fallback DummyMailUser

Use Cases

  • Using H2 database (default) instead of MySQL
  • Plugin disabled or uninstalled but data remains in database
  • Server operators switching between plugins
  • Database schema migration

Testing

Tested with:

  • H2 database (default)
  • MySQL database
  • TransferStation plugin disabled
  • Missing plugin data in database

Database compatibility improvements:
- Replace ALTER TABLE IF NOT EXISTS with JDBC metadata checks for H2 compatibility
- Add addColumnIfNotExists() and dropColumnIfExists() helper methods
- Change address column from TEXT to VARCHAR(255) for UNIQUE constraint compatibility
- Add null check for database connection in setup()
- Throw RuntimeException on database setup failure instead of silent e.printStackTrace()

Plugin-not-found graceful degradation:
- UserConverter.readUser catches IllegalStateException from PluginUser.toUser
- Base.getMails/getMail handle null sender/receiver with DummyMailUser fallback
- Base.getMailTemplate(s) handle null sender with DummyMailUser fallback

This prevents crashes when:
- Using H2 database (default) instead of MySQL
- Plugins that registered PluginMailUser are disabled/uninstalled
- Database schema needs migration
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@windy664, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84f89a12-4eb9-493c-9cc5-8af2b5cee1c0

📥 Commits

Reviewing files that changed from the base of the PR and between de9adac and 2bb8a32.

📒 Files selected for processing (2)
  • paper/src/main/java/net/sabafly/mailBox/database/UserConverter.java
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request improves database robustness and compatibility by wrapping user conversion in a try-catch block, changing the address column type to VARCHAR(255), and refactoring table schema updates to use metadata-based helper methods (addColumnIfNotExists and dropColumnIfExists). It also adds fallback dummy users for null senders or receivers during mail loading. The review feedback suggests adding a null check for the database connection in reload() to prevent a potential NullPointerException, and utilizing try-with-resources in the new helper methods to avoid resource leaks of ResultSet objects.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 144 to 146
public void reload() {
try (Connection conn = getConnection()) {
var fallbackPreview = ItemStack.of(Material.STONE).serializeAsBytes();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In reload(), if getConnection() returns null (for example, if the database connection fails), a NullPointerException will be thrown when addColumnIfNotExists attempts to call conn.getMetaData(). Adding a null check for conn in reload(), similar to the one in setup(), will handle connection failures gracefully.

Suggested change
public void reload() {
try (Connection conn = getConnection()) {
var fallbackPreview = ItemStack.of(Material.STONE).serializeAsBytes();
public void reload() {
try (Connection conn = getConnection()) {
if (conn == null) {
throw new SQLException("Failed to get database connection");
}
var fallbackPreview = ItemStack.of(Material.STONE).serializeAsBytes();

Comment on lines +170 to +194
private void addColumnIfNotExists(Connection conn, String table, String column, String definition) throws SQLException {
try {
// 检查列是否存在(H2 默认大写存储标识符)
var rs = conn.getMetaData().getColumns(null, null, table.toUpperCase(), column.toUpperCase());
if (!rs.next()) {
runner.execute(conn, "ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
}
rs.close();
} catch (SQLException e) {
// 如果表不存在或其他错误,忽略
e.printStackTrace();
}
}

private void dropColumnIfExists(Connection conn, String table, String column) {
try {
var rs = conn.getMetaData().getColumns(null, null, table.toUpperCase(), column.toUpperCase());
if (rs.next()) {
runner.execute(conn, "ALTER TABLE " + table + " DROP COLUMN " + column);
}
rs.close();
} catch (SQLException e) {
// 忽略错误
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In both addColumnIfNotExists and dropColumnIfExists, the ResultSet rs is opened but not closed using a try-with-resources block. If an exception is thrown during runner.execute, the ResultSet will leak. Additionally, addColumnIfNotExists declares throws SQLException in its signature, but it already catches SQLException internally and does not rethrow it, making the throws clause redundant. Refactoring both methods to use try-with-resources ensures resources are always closed properly and cleans up the redundant throws clause.

    private void addColumnIfNotExists(Connection conn, String table, String column, String definition) {
        try (var rs = conn.getMetaData().getColumns(null, null, table.toUpperCase(), column.toUpperCase())) {
            if (!rs.next()) {
                runner.execute(conn, "ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
            }
        } catch (SQLException e) {
            // 如果表不存在或其他错误,忽略
            e.printStackTrace();
        }
    }

    private void dropColumnIfExists(Connection conn, String table, String column) {
        try (var rs = conn.getMetaData().getColumns(null, null, table.toUpperCase(), column.toUpperCase())) {
            if (rs.next()) {
                runner.execute(conn, "ALTER TABLE " + table + " DROP COLUMN " + column);
            }
        } catch (SQLException e) {
            // 忽略错误
        }
    }

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