@@ -1565,4 +1565,164 @@ private Schema generateSuperSetSchemaNewField() {
15651565 return AvroSchemaParseUtils .parseSchemaFromJSONStrictValidation (schemaStr );
15661566 }
15671567
1568+ /**
1569+ * Test that verifies a bug where the superset schema ID does not update when read/write compute
1570+ * is disabled on a store, even if a superset schema was previously set.
1571+ *
1572+ * Bug scenario:
1573+ * 1. Store is created with read computation enabled
1574+ * 2. Multiple schemas are added, causing a superset schema to be generated (e.g., ID 4)
1575+ * 3. Read/write computation is disabled on the store
1576+ * 4. New schemas are added that would normally require a new superset schema
1577+ * 5. Bug: The superset schema ID remains unchanged (stays at 4 instead of updating)
1578+ *
1579+ * The root cause is in VeniceParentHelixAdmin.addValueSchema() which checks:
1580+ * if (store.isReadComputationEnabled() || store.isWriteComputationEnabled())
1581+ * before updating the superset schema. When both are disabled, the superset schema
1582+ * is not updated even if one was previously set.
1583+ */
1584+ @ Test (timeOut = DEFAULT_TEST_TIMEOUT_MS )
1585+ public void testSupersetSchemaNotUpdatedWhenComputeDisabled () throws IOException {
1586+ String storeName = Utils .getUniqueString ("test_superset_compute_disabled" );
1587+ String owner = "test_owner" ;
1588+ String keySchemaStr = "\" long\" " ;
1589+
1590+ // Load schemas from test resources
1591+ // ValueV1: f0, f1
1592+ // ValueV2: f0, f1, f2
1593+ // ValueV3: f1, f2, f3, f4
1594+ Schema valueSchemaV1 =
1595+ AvroCompatibilityHelper .parse (TestWriteUtils .loadFileAsString ("valueSchema/supersetschemas/ValueV1.avsc" ));
1596+ Schema valueSchemaV2 =
1597+ AvroCompatibilityHelper .parse (TestWriteUtils .loadFileAsString ("valueSchema/supersetschemas/ValueV2.avsc" ));
1598+ Schema valueSchemaV3 =
1599+ AvroCompatibilityHelper .parse (TestWriteUtils .loadFileAsString ("valueSchema/supersetschemas/ValueV3.avsc" ));
1600+
1601+ // Schema with a NEW field f5 that would normally trigger superset schema update
1602+ // Contains f2, f3, f5 - f5 is not in the current superset (f0, f1, f2, f3, f4)
1603+ String schemaWithNewFieldStr = "{\n " + " \" type\" : \" record\" ,\n " + " \" namespace\" : \" example.avro\" ,\n "
1604+ + " \" name\" : \" ValueRecordName\" ,\n " + " \" fields\" : [\n "
1605+ + " { \" name\" : \" f2\" , \" type\" : \" int\" , \" default\" : -1 },\n "
1606+ + " { \" name\" : \" f3\" , \" type\" : \" int\" , \" default\" : -1 },\n "
1607+ + " { \" name\" : \" f5\" , \" type\" : \" int\" , \" default\" : -1 }\n " + " ]\n " + "}" ;
1608+ Schema schemaWithNewField = AvroCompatibilityHelper .parse (schemaWithNewFieldStr );
1609+
1610+ try (ControllerClient parentControllerClient =
1611+ new ControllerClient (clusterName , multiRegionMultiClusterWrapper .getControllerConnectString ())) {
1612+
1613+ // Step 1: Create store with first schema
1614+ NewStoreResponse newStoreResponse =
1615+ parentControllerClient .createNewStore (storeName , owner , keySchemaStr , valueSchemaV1 .toString ());
1616+ Assert .assertNotNull (newStoreResponse );
1617+ Assert .assertFalse (newStoreResponse .isError (), "error in newStoreResponse: " + newStoreResponse .getError ());
1618+
1619+ // Step 2: Enable read computation to trigger superset schema generation
1620+ UpdateStoreQueryParams params = new UpdateStoreQueryParams ();
1621+ params .setReadComputationEnabled (true );
1622+ ControllerResponse updateStoreResponse = parentControllerClient .updateStore (storeName , params );
1623+ Assert .assertNotNull (updateStoreResponse );
1624+ Assert .assertFalse (
1625+ updateStoreResponse .isError (),
1626+ "error in updateStoreResponse: " + updateStoreResponse .getError ());
1627+
1628+ // Verify initial superset schema ID is 1 (the first and only schema)
1629+ StoreResponse storeResponse = parentControllerClient .getStore (storeName );
1630+ Assert .assertFalse (storeResponse .isError (), "error in storeResponse: " + storeResponse .getError ());
1631+ int initialSupersetSchemaId = storeResponse .getStore ().getLatestSuperSetValueSchemaId ();
1632+ Assert
1633+ .assertEquals (initialSupersetSchemaId , 1 , "Initial superset schema ID should be 1 (the first value schema)" );
1634+
1635+ // Step 3: Add schema V2 (f0, f1, f2) - this is a superset of V1 (f0, f1)
1636+ SchemaResponse addSchemaResponse = parentControllerClient .addValueSchema (storeName , valueSchemaV2 .toString ());
1637+ Assert .assertNotNull (addSchemaResponse );
1638+ Assert .assertFalse (addSchemaResponse .isError (), "error in addSchemaResponse: " + addSchemaResponse .getError ());
1639+
1640+ // Verify superset schema is now V2 (since V2 is superset of V1)
1641+ storeResponse = parentControllerClient .getStore (storeName );
1642+ Assert .assertFalse (storeResponse .isError (), "error in storeResponse: " + storeResponse .getError ());
1643+ int supersetSchemaIdAfterV2 = storeResponse .getStore ().getLatestSuperSetValueSchemaId ();
1644+ Assert .assertEquals (supersetSchemaIdAfterV2 , 2 , "Superset schema ID should be 2 after adding V2" );
1645+
1646+ // Step 4: Add schema V3 (f1, f2, f3, f4) - this requires generating a new superset schema
1647+ // combining fields from V2 (f0, f1, f2) and V3 (f1, f2, f3, f4) = superset (f0, f1, f2, f3, f4)
1648+ addSchemaResponse = parentControllerClient .addValueSchema (storeName , valueSchemaV3 .toString ());
1649+ Assert .assertNotNull (addSchemaResponse );
1650+ Assert .assertFalse (addSchemaResponse .isError (), "error in addSchemaResponse: " + addSchemaResponse .getError ());
1651+
1652+ // Verify a new superset schema was generated (schema ID 4)
1653+ storeResponse = parentControllerClient .getStore (storeName );
1654+ Assert .assertFalse (storeResponse .isError (), "error in storeResponse: " + storeResponse .getError ());
1655+ int supersetSchemaIdBeforeDisable = storeResponse .getStore ().getLatestSuperSetValueSchemaId ();
1656+ Assert .assertEquals (
1657+ supersetSchemaIdBeforeDisable ,
1658+ 4 ,
1659+ "Superset schema ID should be 4 after adding V3 (V1=1, V2=2, V3=3, superset=4)" );
1660+
1661+ // Verify the superset schema contains all fields (f0, f1, f2, f3, f4)
1662+ SchemaResponse supersetSchemaResponse =
1663+ parentControllerClient .getValueSchema (storeName , supersetSchemaIdBeforeDisable );
1664+ Assert .assertFalse (
1665+ supersetSchemaResponse .isError (),
1666+ "error in schemaResponse: " + supersetSchemaResponse .getError ());
1667+ Schema supersetSchema = AvroCompatibilityHelper .parse (supersetSchemaResponse .getSchemaStr ());
1668+ Assert .assertNotNull (supersetSchema .getField ("f0" ), "Superset schema should contain f0" );
1669+ Assert .assertNotNull (supersetSchema .getField ("f1" ), "Superset schema should contain f1" );
1670+ Assert .assertNotNull (supersetSchema .getField ("f2" ), "Superset schema should contain f2" );
1671+ Assert .assertNotNull (supersetSchema .getField ("f3" ), "Superset schema should contain f3" );
1672+ Assert .assertNotNull (supersetSchema .getField ("f4" ), "Superset schema should contain f4" );
1673+
1674+ // Step 5: Disable read computation
1675+ params = new UpdateStoreQueryParams ();
1676+ params .setReadComputationEnabled (false );
1677+ updateStoreResponse = parentControllerClient .updateStore (storeName , params );
1678+ Assert .assertNotNull (updateStoreResponse );
1679+ Assert .assertFalse (
1680+ updateStoreResponse .isError (),
1681+ "error in updateStoreResponse: " + updateStoreResponse .getError ());
1682+
1683+ // Verify read computation is disabled
1684+ storeResponse = parentControllerClient .getStore (storeName );
1685+ Assert .assertFalse (storeResponse .isError (), "error in storeResponse: " + storeResponse .getError ());
1686+ Assert .assertFalse (storeResponse .getStore ().isReadComputationEnabled (), "Read computation should be disabled" );
1687+
1688+ // Step 6: Add schema with NEW field f5 (f2, f3, f5)
1689+ // This schema introduces f5 which is NOT in the current superset (f0, f1, f2, f3, f4)
1690+ // With compute enabled, this would trigger a new superset schema containing f0-f5
1691+ addSchemaResponse = parentControllerClient .addValueSchema (storeName , schemaWithNewField .toString ());
1692+ Assert .assertNotNull (addSchemaResponse );
1693+ Assert .assertFalse (addSchemaResponse .isError (), "error in addSchemaResponse: " + addSchemaResponse .getError ());
1694+
1695+ // BUG VERIFICATION: When compute is disabled, the superset schema logic is completely bypassed.
1696+ // Even though we added a schema with a NEW field (f5), the superset schema is NOT updated.
1697+ storeResponse = parentControllerClient .getStore (storeName );
1698+ Assert .assertFalse (storeResponse .isError (), "error in storeResponse: " + storeResponse .getError ());
1699+ int supersetSchemaIdAfterDisable = storeResponse .getStore ().getLatestSuperSetValueSchemaId ();
1700+
1701+ // The superset schema ID remains unchanged at 4 because compute is disabled.
1702+ // This documents the current (buggy) behavior where the superset schema is not updated
1703+ // even when a new field is introduced that SHOULD be added to the superset.
1704+ Assert .assertEquals (
1705+ supersetSchemaIdAfterDisable ,
1706+ supersetSchemaIdBeforeDisable ,
1707+ "BUG: Superset schema ID should not change when compute is disabled, "
1708+ + "even though a schema with NEW field f5 was added." );
1709+
1710+ // Verify the superset schema still does NOT contain f5 (proving the bug)
1711+ supersetSchemaResponse = parentControllerClient .getValueSchema (storeName , supersetSchemaIdAfterDisable );
1712+ Assert .assertFalse (
1713+ supersetSchemaResponse .isError (),
1714+ "error in schemaResponse: " + supersetSchemaResponse .getError ());
1715+ Schema supersetSchemaAfterDisable = AvroCompatibilityHelper .parse (supersetSchemaResponse .getSchemaStr ());
1716+ Assert .assertNull (
1717+ supersetSchemaAfterDisable .getField ("f5" ),
1718+ "BUG: Superset schema should NOT contain f5 because superset update was bypassed when compute is disabled" );
1719+
1720+ // Verify total schema count: V1=1, V2=2, V3=3, superset=4, schemaWithNewField=5
1721+ MultiSchemaResponse schemaResponse = parentControllerClient .getAllValueSchema (storeName );
1722+ Assert .assertNotNull (schemaResponse );
1723+ Assert .assertFalse (schemaResponse .isError (), "error in schemaResponse: " + schemaResponse .getError ());
1724+ Assert .assertEquals (schemaResponse .getSchemas ().length , 5 , "There should be 5 value schemas total" );
1725+ }
1726+ }
1727+
15681728}
0 commit comments