fix(webauthn): use Length.LONG32 for byte[] columns to fix MariaDB row size limit#287
Conversation
…w size limit (#286) VARBINARY(65535) columns exceed MariaDB's InnoDB 65,535-byte row size limit, causing silent table creation failure with ddl-auto: update. Length.LONG32 causes Hibernate to auto-upgrade to LONGBLOB on MariaDB (stored off-page) and stays as bytea on PostgreSQL (no OID issues). Avoids @lob which maps to OID on PostgreSQL per Hibernate docs.
PR Review: fix(webauthn): use Length.LONG32 for byte[] columnsSummaryThe fix is correct and well-reasoned. Using Correctness ✅
Import Ordering
|
There was a problem hiding this comment.
Pull request overview
Updates the WebAuthn credential persistence mapping to avoid MariaDB/InnoDB row-size failures during Hibernate auto-DDL by forcing large byte[] columns to map as off-page BLOB types, while keeping PostgreSQL compatibility.
Changes:
- Updated
WebAuthnCredentialbyte[]columns to use@Column(length = Length.LONG32)instead of fixed byte lengths. - Added a unit test that asserts the
@Column(length)values via reflection for the affected fields.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/main/java/com/digitalsanctuary/spring/user/persistence/model/WebAuthnCredential.java | Switches byte[] column length metadata to Length.LONG32 to influence Hibernate’s binary type selection (esp. MariaDB/MySQL). |
| src/test/java/com/digitalsanctuary/spring/user/persistence/model/WebAuthnCredentialColumnMappingTest.java | Adds a reflection-based regression test to ensure the annotation lengths remain Length.LONG32. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -58,11 +59,11 @@ public class WebAuthnCredential { | |||
| private boolean backupState; | |||
|
|
|||
| /** Attestation data from registration (can be several KB). */ | |||
| @Column(name = "attestation_object", length = 65535) | |||
| @Column(name = "attestation_object", length = Length.LONG32) | |||
| private byte[] attestationObject; | |||
|
|
|||
| /** Client data JSON from registration (can be several KB). */ | |||
| @Column(name = "attestation_client_data_json", length = 65535) | |||
| @Column(name = "attestation_client_data_json", length = Length.LONG32) | |||
| private byte[] attestationClientDataJson; | |||
There was a problem hiding this comment.
Consider adding a brief rationale to the field Javadocs (or a class-level comment) explaining that length = Length.LONG32 is intentionally used to force Hibernate to map these byte[] columns to off-page BLOB types on MariaDB/MySQL (avoiding InnoDB row-size limits) while remaining compatible with PostgreSQL. Without this context, a future “cleanup” could revert to a smaller length (e.g., 65535) and reintroduce the MariaDB DDL failure.
| @ParameterizedTest | ||
| @ValueSource(strings = {"attestationObject", "attestationClientDataJson", "publicKey"}) | ||
| @DisplayName("should use Length.LONG32 on byte[] fields for cross-database BLOB compatibility") | ||
| void shouldUseLengthLong32OnBlobFields(String fieldName) throws NoSuchFieldException { | ||
| Field field = WebAuthnCredential.class.getDeclaredField(fieldName); | ||
| Column column = field.getAnnotation(Column.class); | ||
| assertThat(column) | ||
| .as("Field '%s' must have @Column annotation", fieldName) | ||
| .isNotNull(); | ||
| assertThat(column.length()) | ||
| .as("Field '%s' @Column length must be Length.LONG32 (%d) to auto-upgrade " | ||
| + "to LONGBLOB on MariaDB/MySQL and remain bytea on PostgreSQL", | ||
| fieldName, Length.LONG32) | ||
| .isEqualTo(Length.LONG32); |
There was a problem hiding this comment.
The reflection-based assertion verifies the annotation value but not the actual DDL/type Hibernate generates for MariaDB/MySQL (the regression reported in #286). If feasible, add a test that runs Hibernate schema generation with the MariaDB dialect (or a lightweight Testcontainers MariaDB) and asserts these fields map to a BLOB/LONGBLOB type rather than VARBINARY, so the table-creation failure can’t silently return.
- Fix import ordering (alphabetical: jakarta < java < lombok < org) in WebAuthnCredential.java and WebAuthnCredentialColumnMappingTest.java - Add Javadoc rationale to byte[] fields explaining Length.LONG32 is intentional to prevent future reversion to smaller lengths - Add MIGRATION.md note for existing deployments with VARBINARY columns
Follow-up to prior reviewCorrection to the earlier Claude review: the Migration Concern I raised about missing Updated VerdictThe PR is in good shape. The only remaining actionable items are:
Everything else — the fix itself, the javadoc explaining the |
…stgreSQL (#286) Validate that Hibernate can create the full schema without errors on real MariaDB and PostgreSQL instances via Testcontainers. Tests verify: - All 10 expected tables (entities + join tables) are created - WebAuthn byte[] columns map to BLOB-compatible types (longblob on MariaDB, bytea on PostgreSQL) rather than inline VARBINARY Adds testcontainers-postgresql and testcontainers-junit-jupiter deps.
|
Correction to prior Claude reviews: both previous Claude comments incorrectly flagged import ordering as an issue. The actual diff moves java.time.Instant to after jakarta.* and places org.hibernate.Length after lombok.Data, giving the final order jakarta -> java -> lombok -> org, which is alphabetically correct per CLAUDE.md. No import ordering issues remain. The MIGRATION.md section was also already present in the PR — both points in the second Claude review were already resolved in the diff. New tests: The PR adds AbstractSchemaValidationTest, MariaDBSchemaValidationTest, and PostgreSQLSchemaValidationTest, which directly address the schema-level coverage gap raised earlier. One concern: in AbstractSchemaValidationTest, shouldCreateWebAuthnBlobColumns queries INFORMATION_SCHEMA.COLUMNS for the user_credentials columns. If the table was never created (the exact regression being guarded against), JdbcTemplate.queryForObject throws EmptyResultDataAccessException rather than a clear assertion failure. Since JUnit 5 does not guarantee test method order, shouldCreateAllExpectedTables may not run first to give the clearer table-missing message. Suggested fix: add @TestMethodOrder(MethodOrderer.OrderAnnotation.class) on the abstract class and @order(1)/@order(2) on the two test methods, so the table-existence check always precedes the column-type check. Alternatively, catch EmptyResultDataAccessException in the column loop and fail with an explicit message. Non-blocking. Overall: the fix is correct, the imports are ordered correctly, MIGRATION.md is present, and the layered test coverage is solid. The only actionable item is improving test-ordering clarity in AbstractSchemaValidationTest before merge. |
Summary
Fixes #286
@Column(length = 65535)/@Column(length = 2048)with@Column(length = Length.LONG32)on the threebyte[]fields inWebAuthnCredential(attestationObject,attestationClientDataJson,publicKey)LONGBLOBon MariaDB/MySQL (stored off-page, avoids 65,535-byte InnoDB row size limit) and stays asbyteaon PostgreSQL@Lobwhich maps toOIDon PostgreSQL per Hibernate docs, causing cross-database incompatibilityTest Plan
WebAuthnCredentialColumnMappingTestverifiesLength.LONG32on all threebyte[]fields via reflectionWebAuthnFeatureEnabledIntegrationTestpasses (persists credentials with H2)ddl-auto: update(demo app)