jerryshao commented on code in PR #13689:
URL: https://github.com/apache/gravitino/pull/13689#discussion_r4215337024


##########
core/src/test/java/org/apache/gravitino/storage/TestSQLScripts.java:
##########
@@ -255,6 +259,219 @@ public void testUpgradeToTwoZeroBackfillsOccVersions() 
throws SQLException, IOEx
     }
   }
 
+  /** Verifies a completed upgrade can be repeated without resetting migrated 
data. */
+  @TestTemplate
+  public void testUpgradeToTwoZeroIsIdempotent() throws SQLException, 
IOException {
+    Path scriptDir = upgradeScriptDirectory();
+    String suffix = "-" + backendType.toLowerCase() + ".sql";
+    File upgrade = scriptDir.resolve("upgrade-1.3.0-to-2.0.0" + 
suffix).toFile();
+    dropAllTables();
+    executeScript(scriptDir.resolve("schema-1.3.0" + suffix).toFile());
+    seedUpgradeData();
+    executeScript(upgrade);
+    executeStatements(
+        List.of(
+            "UPDATE idp_user_meta SET enabled = FALSE, audit_info = 'preserved 
audit'",
+            "UPDATE idp_group_meta SET group_comment = 'preserved comment'",
+            "UPDATE tag_meta SET allowed_values = '[\"blue\"]'",
+            "UPDATE tag_relation_meta SET tag_value = 'blue'",
+            "UPDATE fileset_meta SET occ_version = 42",
+            "UPDATE policy_meta SET occ_version = 43"),
+        "customized migrated data");
+    Map<String, Set<String>> expectedColumns = readSchemaColumns();
+    List<String> expectedIndexes = readSchemaIndexes();
+    Map<String, List<List<String>>> expectedData = readUpgradeData();
+    executeScript(upgrade);
+    executeScript(upgrade);
+    Assertions.assertEquals(expectedColumns, readSchemaColumns());
+    Assertions.assertEquals(expectedIndexes, readSchemaIndexes());
+    Assertions.assertEquals(expectedData, readUpgradeData(), "Retry must 
preserve migrated values");

Review Comment:
   [Important] Both new tests snapshot `expectedColumns`/`expectedIndexes` from 
an already-successful upgrade and then compare the retried database against 
that same snapshot. That catches a retry which *diverges*, but never a guard 
that is wrong on the first run: an inverted guard condition, or an `IF EXISTS` 
drop that silently no-ops because the object has a different name, yields 
identical expected and actual values and passes. For H2 it is weaker still, 
because `readSchemaIndexes` (line 445) blanks index names, so only structure is 
compared.
   
   The helpers you just added make the missing assertion cheap: drop the 
database, load `schema-2.0.0` + suffix, and compare 
`readSchemaColumns()`/`readSchemaIndexes()` with the values captured from the 
upgraded database. I ran exactly that comparison for H2 2.2.224 in `MODE=MYSQL` 
with the upgrade applied three times - it passes, with `policy_relation_meta` 
the only difference, which the script header at 
`scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql:24-25` documents as 
intentional. One assertion with that single documented exception would pin the 
property the script's own comment claims at 
`scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql:132-136` ("A database upgraded 
from 1.3.0 ends up with the same index names as a fresh 2.0.0 install"), which 
nothing in the suite checks today.
   
   Verified by: ran `schema-1.3.0-h2.sql` plus three passes of 
`upgrade-1.3.0-to-2.0.0-h2.sql` against a fresh `schema-2.0.0-h2.sql` on H2 
2.2.224 over JDBC, diffing `getColumns`/`getIndexInfo`; read both new tests and 
all helpers in full.



##########
core/src/test/java/org/apache/gravitino/storage/TestSQLScripts.java:
##########
@@ -255,6 +259,219 @@ public void testUpgradeToTwoZeroBackfillsOccVersions() 
throws SQLException, IOEx
     }
   }
 
+  /** Verifies a completed upgrade can be repeated without resetting migrated 
data. */
+  @TestTemplate
+  public void testUpgradeToTwoZeroIsIdempotent() throws SQLException, 
IOException {
+    Path scriptDir = upgradeScriptDirectory();
+    String suffix = "-" + backendType.toLowerCase() + ".sql";
+    File upgrade = scriptDir.resolve("upgrade-1.3.0-to-2.0.0" + 
suffix).toFile();
+    dropAllTables();
+    executeScript(scriptDir.resolve("schema-1.3.0" + suffix).toFile());
+    seedUpgradeData();
+    executeScript(upgrade);
+    executeStatements(
+        List.of(
+            "UPDATE idp_user_meta SET enabled = FALSE, audit_info = 'preserved 
audit'",
+            "UPDATE idp_group_meta SET group_comment = 'preserved comment'",
+            "UPDATE tag_meta SET allowed_values = '[\"blue\"]'",
+            "UPDATE tag_relation_meta SET tag_value = 'blue'",
+            "UPDATE fileset_meta SET occ_version = 42",
+            "UPDATE policy_meta SET occ_version = 43"),
+        "customized migrated data");
+    Map<String, Set<String>> expectedColumns = readSchemaColumns();
+    List<String> expectedIndexes = readSchemaIndexes();
+    Map<String, List<List<String>>> expectedData = readUpgradeData();
+    executeScript(upgrade);
+    executeScript(upgrade);
+    Assertions.assertEquals(expectedColumns, readSchemaColumns());
+    Assertions.assertEquals(expectedIndexes, readSchemaIndexes());
+    Assertions.assertEquals(expectedData, readUpgradeData(), "Retry must 
preserve migrated values");
+    if ("mysql".equals(backendType)) {
+      // Neither rename source nor target exists: this is damage, not an 
already-applied DDL.
+      executeStatements(
+          List.of("ALTER TABLE schema_meta DROP INDEX schema_meta_idx_mid"),
+          "missing target index");
+      AssertionFailedError failure =
+          Assertions.assertThrows(AssertionFailedError.class, () -> 
executeScript(upgrade));
+      Assertions.assertInstanceOf(SQLException.class, failure.getCause());
+      Assertions.assertEquals(1176, ((SQLException) 
failure.getCause()).getErrorCode());
+    }
+  }
+
+  /** Verifies retries after structural DDL and data updates, including 
prepared DDL execution. */
+  @TestTemplate
+  public void testUpgradeToTwoZeroRetriesPartialExecution() throws 
SQLException, IOException {
+    Path scriptDir = upgradeScriptDirectory();
+    String suffix = "-" + backendType.toLowerCase() + ".sql";
+    File schema = scriptDir.resolve("schema-1.3.0" + suffix).toFile();
+    File upgrade = scriptDir.resolve("upgrade-1.3.0-to-2.0.0" + 
suffix).toFile();
+    List<String> statements = extractStatements(upgrade.toPath());
+    dropAllTables();
+    executeScript(schema);
+    seedUpgradeData();
+    executeScript(upgrade);
+    Map<String, Set<String>> expectedColumns = readSchemaColumns();
+    List<String> expectedIndexes = readSchemaIndexes();
+    Map<String, List<List<String>>> expectedData = readUpgradeData();
+
+    for (int completed = 1; completed <= statements.size(); completed++) {

Review Comment:
   [Nit] This loop rebuilds the whole fixture once per structural statement: 59 
iterations for MySQL, 27 for PostgreSQL, 21 for H2. Each iteration drops 
roughly 60 tables, replays `schema-1.3.0`, replays all 203 statements of the 
MySQL upgrade, and then reads metadata three times - and `readSchemaIndexes()` 
calls `readSchemaColumns()` again (line 428) before issuing one `getIndexInfo` 
per table, so every checkpoint pays about 60 extra metadata round trips on top. 
Against dockerised MySQL that is a lot of wall clock for one test.
   
   Two cheap reductions: pass the already-computed table set into 
`readSchemaIndexes` instead of re-reading all columns, and sample the 31 
near-identical `RENAME INDEX` checkpoints rather than running every one of them 
- they all exercise the same guard shape.
   
   Verified by: re-derived the per-engine checkpoint counts (59/27/21, 107 
total, matching the PR description) by applying this method's prefix filter to 
each upgrade script; read `readSchemaIndexes` at line 428.



##########
scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql:
##########
@@ -17,30 +17,64 @@
 -- under the License.
 --
 
+-- MySQL has no ADD COLUMN / CREATE INDEX IF NOT EXISTS. Guard completed DDL
+-- so a failed upgrade can be restarted from the beginning without --force.
+-- Keep each operation in this session; no stored routines or extra privileges 
are required.

Review Comment:
   [Nit] This comment is the only place the new contract is written down, and 
`docs/how-to-upgrade.md` still tells operators the opposite. Step 5 there (line 
159) says just "These scripts should run to completion without any errors. If 
you do encounter errors you need to analyze the cause and attempt to trace it 
back to one of the preceding steps." Nothing tells an operator that re-running 
the script is now the supported recovery from a partial failure, that this 
holds for 1.3.0 -> 2.0.0 only (earlier upgrade scripts are unchanged), or that 
a schema-drift error such as MySQL 1176 on a rename means manual repair rather 
than another retry. Given the PR lists this as a user-facing change, a two-line 
note in Step 5 would make it discoverable.
   
   Verified by: read `docs/how-to-upgrade.md` Steps 4-6; `git diff --stat 
origin/main...HEAD` touches only the three scripts and `TestSQLScripts.java`, 
no docs.



##########
core/src/test/java/org/apache/gravitino/storage/TestSQLScripts.java:
##########
@@ -255,6 +259,219 @@ public void testUpgradeToTwoZeroBackfillsOccVersions() 
throws SQLException, IOEx
     }
   }
 
+  /** Verifies a completed upgrade can be repeated without resetting migrated 
data. */
+  @TestTemplate
+  public void testUpgradeToTwoZeroIsIdempotent() throws SQLException, 
IOException {
+    Path scriptDir = upgradeScriptDirectory();
+    String suffix = "-" + backendType.toLowerCase() + ".sql";
+    File upgrade = scriptDir.resolve("upgrade-1.3.0-to-2.0.0" + 
suffix).toFile();
+    dropAllTables();
+    executeScript(scriptDir.resolve("schema-1.3.0" + suffix).toFile());
+    seedUpgradeData();
+    executeScript(upgrade);
+    executeStatements(
+        List.of(
+            "UPDATE idp_user_meta SET enabled = FALSE, audit_info = 'preserved 
audit'",
+            "UPDATE idp_group_meta SET group_comment = 'preserved comment'",
+            "UPDATE tag_meta SET allowed_values = '[\"blue\"]'",
+            "UPDATE tag_relation_meta SET tag_value = 'blue'",
+            "UPDATE fileset_meta SET occ_version = 42",
+            "UPDATE policy_meta SET occ_version = 43"),
+        "customized migrated data");
+    Map<String, Set<String>> expectedColumns = readSchemaColumns();
+    List<String> expectedIndexes = readSchemaIndexes();
+    Map<String, List<List<String>>> expectedData = readUpgradeData();
+    executeScript(upgrade);
+    executeScript(upgrade);
+    Assertions.assertEquals(expectedColumns, readSchemaColumns());
+    Assertions.assertEquals(expectedIndexes, readSchemaIndexes());
+    Assertions.assertEquals(expectedData, readUpgradeData(), "Retry must 
preserve migrated values");
+    if ("mysql".equals(backendType)) {
+      // Neither rename source nor target exists: this is damage, not an 
already-applied DDL.
+      executeStatements(
+          List.of("ALTER TABLE schema_meta DROP INDEX schema_meta_idx_mid"),
+          "missing target index");
+      AssertionFailedError failure =
+          Assertions.assertThrows(AssertionFailedError.class, () -> 
executeScript(upgrade));
+      Assertions.assertInstanceOf(SQLException.class, failure.getCause());
+      Assertions.assertEquals(1176, ((SQLException) 
failure.getCause()).getErrorCode());

Review Comment:
   [Nit] Pinning the numeric code 1176 (`ER_KEY_DOES_NOT_EXITS`) ties the test 
to one server's error numbering; MariaDB or a future MySQL release may report 
the rename failure differently, and the assertion would then fail with nothing 
more informative than `expected: 1176`. Asserting that the exception message 
names the missing index (`idx_mid`) expresses the same intent and survives a 
backend change.
   
   Verified by: read the block above; the guard being exercised is the 
`schema_meta` rename at 
`scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql:137-141`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to