This is an automated email from the ASF dual-hosted git repository.

tkhurana pushed a commit to branch PHOENIX-7562-feature-new
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/PHOENIX-7562-feature-new by 
this push:
     new 26951424fe PHOENIX-8014 Add support for writes to SYSTEM.SEQUENCE 
table (#2628)
26951424fe is described below

commit 26951424fe97e3781ef691627720734e7e36c03e
Author: tkhurana <[email protected]>
AuthorDate: Fri Oct 2 10:23:28 2026 -0700

    PHOENIX-8014 Add support for writes to SYSTEM.SEQUENCE table (#2628)
---
 .../apache/phoenix/compile/SequenceManager.java    |   8 +-
 .../phoenix/query/ConnectionQueryServices.java     |  25 ++++
 .../phoenix/query/ConnectionQueryServicesImpl.java |  38 +++++-
 .../query/DelegateConnectionQueryServices.java     |  15 +++
 .../org/apache/phoenix/schema/MetaDataClient.java  |   5 +-
 .../java/org/apache/phoenix/util/SchemaUtil.java   |   5 +-
 .../coprocessor/SequenceRegionObserver.java        |  20 +++
 .../phoenix/replication/ReplicationLogGroupIT.java | 146 +++++++++++++++++++++
 .../org/apache/phoenix/schema/SchemaUtilTest.java  |  18 +++
 9 files changed, 273 insertions(+), 7 deletions(-)

diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/SequenceManager.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/SequenceManager.java
index 4c9e205599..ada0a708eb 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/SequenceManager.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/SequenceManager.java
@@ -107,7 +107,13 @@ public class SequenceManager {
       Long scn = statement.getConnection().getSCN();
       long timestamp = scn == null ? HConstants.LATEST_TIMESTAMP : scn;
       ConnectionQueryServices services = 
statement.getConnection().getQueryServices();
-      services.incrementSequences(nextSequences, timestamp, srcSequenceValues, 
sqlExceptions);
+      // Resolve the HA group name (null for a non-HA connection) and pass it 
down so the INCREMENT
+      // mutations can be tagged for replication when the connection is part 
of an HA group.
+      String haGroupName = statement.getConnection().getHAGroup() != null
+        ? statement.getConnection().getHAGroup().getName()
+        : null;
+      services.incrementSequences(nextSequences, timestamp, srcSequenceValues, 
sqlExceptions,
+        haGroupName);
       setSequenceValues(srcSequenceValues, dstSequenceValues, sqlExceptions);
       int offset = nextSequences.size();
       for (int i = 0; i < currentSequences.size(); i++) {
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
index 37e0f1d229..8c8cdd5e86 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
@@ -212,6 +212,20 @@ public interface ConnectionQueryServices extends 
QueryServices, MetaDataMutated
     long incrementBy, long cacheSize, long minValue, long maxValue, boolean 
cycle, long timestamp)
     throws SQLException;
 
+  /**
+   * Overload of
+   * {@link #createSequence(String, String, String, long, long, long, long, 
long, boolean, long)}
+   * that carries the HA group name so the CREATE mutation can be replicated 
when the connection is
+   * part of an HA group. The default ignores the name and preserves the 
non-replicating behavior;
+   * implementations that support HA replication override it.
+   */
+  default long createSequence(String tenantId, String schemaName, String 
sequenceName,
+    long startWith, long incrementBy, long cacheSize, long minValue, long 
maxValue, boolean cycle,
+    long timestamp, String haGroupName) throws SQLException {
+    return createSequence(tenantId, schemaName, sequenceName, startWith, 
incrementBy, cacheSize,
+      minValue, maxValue, cycle, timestamp);
+  }
+
   long dropSequence(String tenantId, String schemaName, String sequenceName, 
long timestamp)
     throws SQLException;
 
@@ -221,6 +235,17 @@ public interface ConnectionQueryServices extends 
QueryServices, MetaDataMutated
   void incrementSequences(List<SequenceAllocation> sequenceAllocation, long 
timestamp,
     long[] values, SQLException[] exceptions) throws SQLException;
 
+  /**
+   * Overload of {@link #incrementSequences(List, long, long[], 
SQLException[])} that carries the HA
+   * group name so the INCREMENT mutations can be replicated when the 
connection is part of an HA
+   * group. The default ignores the name and preserves the non-replicating 
behavior; implementations
+   * that support HA replication override it.
+   */
+  default void incrementSequences(List<SequenceAllocation> sequenceAllocation, 
long timestamp,
+    long[] values, SQLException[] exceptions, String haGroupName) throws 
SQLException {
+    incrementSequences(sequenceAllocation, timestamp, values, exceptions);
+  }
+
   long currentSequenceValue(SequenceKey sequenceKey, long timestamp) throws 
SQLException;
 
   void returnSequences(List<SequenceKey> sequenceKeys, long timestamp, 
SQLException[] exceptions)
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
index 13276b8163..ef7ab66434 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
@@ -227,6 +227,7 @@ import 
org.apache.phoenix.coprocessor.generated.MetaDataProtos.MetaDataResponse;
 import org.apache.phoenix.coprocessor.generated.MetaDataProtos.MetaDataService;
 import 
org.apache.phoenix.coprocessor.generated.MetaDataProtos.UpdateIndexStateRequest;
 import org.apache.phoenix.coprocessor.generated.RegionServerEndpointProtos;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
 import 
org.apache.phoenix.coprocessorclient.InvalidateServerMetadataCacheRequest;
 import org.apache.phoenix.coprocessorclient.MetaDataProtocol;
 import 
org.apache.phoenix.coprocessorclient.MetaDataProtocol.MetaDataMutationResult;
@@ -6026,6 +6027,14 @@ public class ConnectionQueryServicesImpl extends 
DelegateQueryServices
   public long createSequence(String tenantId, String schemaName, String 
sequenceName,
     long startWith, long incrementBy, long cacheSize, long minValue, long 
maxValue, boolean cycle,
     long timestamp) throws SQLException {
+    return createSequence(tenantId, schemaName, sequenceName, startWith, 
incrementBy, cacheSize,
+      minValue, maxValue, cycle, timestamp, null);
+  }
+
+  @Override
+  public long createSequence(String tenantId, String schemaName, String 
sequenceName,
+    long startWith, long incrementBy, long cacheSize, long minValue, long 
maxValue, boolean cycle,
+    long timestamp, String haGroupName) throws SQLException {
     SequenceKey sequenceKey =
       new SequenceKey(tenantId, schemaName, sequenceName, 
nSequenceSaltBuckets);
     Sequence newSequences = new Sequence(sequenceKey);
@@ -6038,6 +6047,7 @@ public class ConnectionQueryServicesImpl extends 
DelegateQueryServices
       // Now that we have the lock we need, create the sequence
       Append append = sequence.createSequence(startWith, incrementBy, 
cacheSize, timestamp,
         minValue, maxValue, cycle);
+      annotateSequenceMutationWithHAGroup(append, haGroupName);
       Table htable = this.getTable(SchemaUtil
         .getPhysicalName(PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_NAME_BYTES, 
this.getProps())
         .getName());
@@ -6114,7 +6124,8 @@ public class ConnectionQueryServicesImpl extends 
DelegateQueryServices
   @Override
   public void validateSequences(List<SequenceAllocation> sequenceAllocations, 
long timestamp,
     long[] values, SQLException[] exceptions, Sequence.ValueOp action) throws 
SQLException {
-    incrementSequenceValues(sequenceAllocations, timestamp, values, 
exceptions, action);
+    // VALIDATE_SEQUENCE only reads (never persists a value), so it has 
nothing to replicate.
+    incrementSequenceValues(sequenceAllocations, timestamp, values, 
exceptions, action, null);
   }
 
   /**
@@ -6127,12 +6138,32 @@ public class ConnectionQueryServicesImpl extends 
DelegateQueryServices
   @Override
   public void incrementSequences(List<SequenceAllocation> sequenceAllocations, 
long timestamp,
     long[] values, SQLException[] exceptions) throws SQLException {
+    incrementSequences(sequenceAllocations, timestamp, values, exceptions, 
null);
+  }
+
+  @Override
+  public void incrementSequences(List<SequenceAllocation> sequenceAllocations, 
long timestamp,
+    long[] values, SQLException[] exceptions, String haGroupName) throws 
SQLException {
     incrementSequenceValues(sequenceAllocations, timestamp, values, exceptions,
-      Sequence.ValueOp.INCREMENT_SEQUENCE);
+      Sequence.ValueOp.INCREMENT_SEQUENCE, haGroupName);
+  }
+
+  /**
+   * Tag a sequence CREATE/INCREMENT mutation with the HA group name so that, 
server-side,
+   * {@code SequenceRegionObserver} can copy it onto the Put it applies and 
{@code
+   * IndexRegionObserver} ships the write to the HA replication log. A null 
name (non-HA connection)
+   * leaves the mutation untagged, so it is not replicated.
+   */
+  private void annotateSequenceMutationWithHAGroup(Mutation mutation, String 
haGroupName) {
+    if (haGroupName != null) {
+      
mutation.setAttribute(BaseScannerRegionObserverConstants.HA_GROUP_NAME_ATTRIB,
+        Bytes.toBytes(haGroupName));
+    }
   }
 
   private void incrementSequenceValues(List<SequenceAllocation> 
sequenceAllocations, long timestamp,
-    long[] values, SQLException[] exceptions, Sequence.ValueOp op) throws 
SQLException {
+    long[] values, SQLException[] exceptions, Sequence.ValueOp op, String 
haGroupName)
+    throws SQLException {
     List<Sequence> sequences = 
Lists.newArrayListWithExpectedSize(sequenceAllocations.size());
     for (SequenceAllocation sequenceAllocation : sequenceAllocations) {
       SequenceKey key = sequenceAllocation.getSequenceKey();
@@ -6161,6 +6192,7 @@ public class ConnectionQueryServicesImpl extends 
DelegateQueryServices
           toIncrementList.add(sequence);
           Increment inc =
             sequence.newIncrement(timestamp, op, 
sequenceAllocations.get(i).getNumAllocations());
+          annotateSequenceMutationWithHAGroup(inc, haGroupName);
           incrementBatch.add(inc);
         } catch (SQLException e) {
           exceptions[i] = e;
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
index 24506ee9a6..2a79cfeb42 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
@@ -264,6 +264,14 @@ public class DelegateConnectionQueryServices extends 
DelegateQueryServices
       cacheSize, minValue, maxValue, cycle, timestamp);
   }
 
+  @Override
+  public long createSequence(String tenantId, String schemaName, String 
sequenceName,
+    long startWith, long incrementBy, long cacheSize, long minValue, long 
maxValue, boolean cycle,
+    long timestamp, String haGroupName) throws SQLException {
+    return getDelegate().createSequence(tenantId, schemaName, sequenceName, 
startWith, incrementBy,
+      cacheSize, minValue, maxValue, cycle, timestamp, haGroupName);
+  }
+
   @Override
   public long dropSequence(String tenantId, String schemaName, String 
sequenceName, long timestamp)
     throws SQLException {
@@ -282,6 +290,13 @@ public class DelegateConnectionQueryServices extends 
DelegateQueryServices
     getDelegate().incrementSequences(sequenceAllocations, timestamp, values, 
exceptions);
   }
 
+  @Override
+  public void incrementSequences(List<SequenceAllocation> sequenceAllocations, 
long timestamp,
+    long[] values, SQLException[] exceptions, String haGroupName) throws 
SQLException {
+    getDelegate().incrementSequences(sequenceAllocations, timestamp, values, 
exceptions,
+      haGroupName);
+  }
+
   @Override
   public long currentSequenceValue(SequenceKey sequenceKey, long timestamp) 
throws SQLException {
     return getDelegate().currentSequenceValue(sequenceKey, timestamp);
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index 22833e2494..b9d917cd3b 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -2193,9 +2193,12 @@ public class MetaDataClient {
   private MutationState createSequence(String tenantId, String schemaName, 
String sequenceName,
     boolean ifNotExists, long startWith, long incrementBy, long cacheSize, 
boolean cycle,
     long minValue, long maxValue, long timestamp) throws SQLException {
+    // Resolve the HA group name (null for a non-HA connection) and pass it 
down so the CREATE
+    // SEQUENCE mutation can be tagged for replication when the connection is 
part of an HA group.
+    String haGroupName = connection.getHAGroup() != null ? 
connection.getHAGroup().getName() : null;
     try {
       connection.getQueryServices().createSequence(tenantId, schemaName, 
sequenceName, startWith,
-        incrementBy, cacheSize, minValue, maxValue, cycle, timestamp);
+        incrementBy, cacheSize, minValue, maxValue, cycle, timestamp, 
haGroupName);
     } catch (SequenceAlreadyExistsException e) {
       if (ifNotExists) {
         return new MutationState(0, 0, connection);
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java 
b/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java
index 33f8370b48..5190d5381b 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java
@@ -1523,7 +1523,8 @@ public class SchemaUtil {
    * because this data is very specific to the cluster except for 
SYSTEM.CATALOG and
    * SYSTEM.CHILD_LINK where we only replicate rows with tenant information. 
Non tenant (Global)
    * rows are assumed to be executed by an admin or an admin process in each 
cluster separately and
-   * thus not replicated.
+   * thus not replicated. SYSTEM.SEQUENCE replicates all rows (not just tenant 
rows) so that
+   * sequence values consumed on the active are known on the standby after 
failover.
    * @param tableName full name of the table
    * @return true if the table data should be replicated, else false
    */
@@ -1531,7 +1532,7 @@ public class SchemaUtil {
     if (!isSystemTable(tableName)) {
       return true;
     }
-    if (isMetaTable(tableName) || isChildLinkTable(tableName)) {
+    if (isMetaTable(tableName) || isChildLinkTable(tableName) || 
isSequenceTable(tableName)) {
       return true;
     }
     return false;
diff --git 
a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/SequenceRegionObserver.java
 
b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/SequenceRegionObserver.java
index 0bbe171486..87d606580d 100644
--- 
a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/SequenceRegionObserver.java
+++ 
b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/SequenceRegionObserver.java
@@ -48,6 +48,7 @@ import org.apache.hadoop.hbase.regionserver.Region;
 import org.apache.hadoop.hbase.regionserver.Region.RowLock;
 import org.apache.hadoop.hbase.regionserver.RegionScanner;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
 import org.apache.phoenix.coprocessorclient.MetaDataProtocol;
 import org.apache.phoenix.coprocessorclient.SequenceRegionObserverConstants;
 import org.apache.phoenix.exception.SQLExceptionCode;
@@ -292,6 +293,7 @@ public class SequenceRegionObserver implements 
RegionObserver, RegionCoprocessor
             PhoenixDatabaseMetaData.LIMIT_REACHED_FLAG_BYTES, limitReached, 
timestamp);
           put.add(newLimitReachedKV);
         }
+        copyHAGroupNameAttribute(increment, put);
         // update the KeyValues on the server
         Mutation[] mutations = new Mutation[] { put };
         region.batchMutate(mutations);
@@ -308,6 +310,19 @@ public class SequenceRegionObserver implements 
RegionObserver, RegionCoprocessor
     }
   }
 
+  /**
+   * Copies the {@code _HAGroupName} attribute (when present) from the client 
sequence operation
+   * onto the Put applied server-side. The client only sets it for HA 
connections, so a non-HA write
+   * leaves the Put untagged and IndexRegionObserver does not ship it.
+   */
+  private static void copyHAGroupNameAttribute(Mutation source, Mutation 
target) {
+    byte[] haGroupName =
+      
source.getAttribute(BaseScannerRegionObserverConstants.HA_GROUP_NAME_ATTRIB);
+    if (haGroupName != null) {
+      
target.setAttribute(BaseScannerRegionObserverConstants.HA_GROUP_NAME_ATTRIB, 
haGroupName);
+    }
+  }
+
   /**
    * Creates a new KeyValue for a long value
    * @param key     key used while creating KeyValue
@@ -437,6 +452,11 @@ public class SequenceRegionObserver implements 
RegionObserver, RegionCoprocessor
           case CREATE_SEQUENCE:
             m = new Put(row, clientTimestamp);
             m.getFamilyCellMap().putAll(append.getFamilyCellMap());
+            // Propagate the HA group name from the client Append onto the Put 
so that
+            // IndexRegionObserver ships the sequence creation to the HA 
replication log. Only
+            // CREATE is replicated; RETURN_SEQUENCE and DROP_SEQUENCE are 
intentionally left
+            // untagged so they are not shipped.
+            copyHAGroupNameAttribute(append, m);
             break;
         }
         if (!hadClientTimestamp) {
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/replication/ReplicationLogGroupIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/replication/ReplicationLogGroupIT.java
index ebc5a1573f..b72634458d 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/replication/ReplicationLogGroupIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/replication/ReplicationLogGroupIT.java
@@ -20,6 +20,7 @@ package org.apache.phoenix.replication;
 import static 
org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
 import static 
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME;
 import static 
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CHILD_LINK_NAME;
+import static 
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_NAME;
 import static org.apache.phoenix.query.BaseTest.generateUniqueName;
 import static 
org.apache.phoenix.replication.CrossClusterReplicationTestUtil.findLogFiles;
 import static org.junit.Assert.assertEquals;
@@ -1234,6 +1235,151 @@ public class ReplicationLogGroupIT extends 
ReplicationLogGroupBaseIT {
     }
   }
 
+  /**
+   * End-to-end replication of SYSTEM.SEQUENCE. A sequence created and 
advanced on the active over
+   * an HA connection replicates its CREATE and INCREMENT writes; the standby 
never runs CREATE
+   * SEQUENCE, so its SYSTEM.SEQUENCE row must materialize purely from replay. 
After replaying the
+   * log onto cluster 2, NEXT VALUE FOR on the standby must resume from the 
replicated value rather
+   * than restart from START -- i.e. no duplicate sequence values after 
failover.
+   * <p>
+   * This is the bare-minimum verbatim path (see {@code 
SchemaUtil.shouldReplicateTable}), which
+   * assumes ordered replay: because every sequence cell shares one constant 
timestamp, the
+   * last-applied CURRENT_VALUE Put wins, and sequential replay applies them 
in log order.
+   */
+  @Test
+  public void testSystemSequence() throws Exception {
+    final String sequenceName = "SEQ_" + generateUniqueName();
+    final int nextCount = 5;
+    final String createSeqDdl =
+      "CREATE SEQUENCE " + sequenceName + " START WITH 1 INCREMENT BY 1 CACHE 
1";
+
+    long lastActiveValue = -1;
+    // Create + advance the sequence on the active over an HA connection so 
the CREATE Append and
+    // the INCREMENT mutations carry _HAGroupName and replicate. The sequence 
is NOT created on
+    // cluster 2; its SYSTEM.SEQUENCE row must materialize purely from replay.
+    try (FailoverPhoenixConnection conn = (FailoverPhoenixConnection) 
DriverManager
+      .getConnection(CLUSTERS.getJdbcHAUrl(), clientProps)) {
+      conn.createStatement().execute(createSeqDdl);
+      conn.commit();
+      for (int i = 0; i < nextCount; i++) {
+        try (ResultSet rs =
+          conn.createStatement().executeQuery("SELECT NEXT VALUE FOR " + 
sequenceName)) {
+          assertTrue(rs.next());
+          lastActiveValue = rs.getLong(1);
+        }
+      }
+    }
+
+    // The CREATE and INCREMENT writes to SYSTEM.SEQUENCE must have been 
shipped.
+    logGroup.close();
+    Map<String, List<Mutation>> logsByTable = groupLogsByTable();
+    dumpTableLogCount(logsByTable);
+    assertTrue("SYSTEM.SEQUENCE writes should replicate",
+      getCountForTable(logsByTable, SYSTEM_SEQUENCE_NAME) > 0);
+
+    // Replay onto cluster 2 without creating the sequence there; the row is 
built from replay.
+    replayAndVerifyAcrossClusters(Collections.emptyList());
+
+    // On the standby, NEXT VALUE FOR resumes from the replicated value: the 
next value is one past
+    // the last value handed out on the active, not START (which would collide 
with 1..lastActive).
+    try (Connection conn2 = CLUSTERS.getCluster2Connection(haGroup); ResultSet 
rs =
+      conn2.createStatement().executeQuery("SELECT NEXT VALUE FOR " + 
sequenceName)) {
+      assertTrue(rs.next());
+      assertEquals("Standby must resume from the replicated sequence value, 
not restart from START",
+        lastActiveValue + 1, rs.getLong(1));
+    }
+  }
+
+  /**
+   * Same as {@link #testSystemSequence} but with CACHE &gt; 1, the realistic 
production path. The
+   * server reserves a whole block per round trip, so the replicated 
CURRENT_VALUE jumps ahead of
+   * the last value handed out on the active. The standby therefore resumes 
strictly past
+   * lastActiveValue (a benign gap), never duplicating a value -- so the 
invariant is {@code
+   * standbyNext > lastActiveValue}, not the exact {@code lastActiveValue + 1} 
that only holds for
+   * CACHE 1.
+   */
+  @Test
+  public void testSystemSequenceWithCache() throws Exception {
+    final String sequenceName = "SEQ_" + generateUniqueName();
+    final int nextCount = 5;
+    final String createSeqDdl =
+      "CREATE SEQUENCE " + sequenceName + " START WITH 1 INCREMENT BY 1 CACHE 
100";
+
+    long lastActiveValue = -1;
+    try (FailoverPhoenixConnection conn = (FailoverPhoenixConnection) 
DriverManager
+      .getConnection(CLUSTERS.getJdbcHAUrl(), clientProps)) {
+      conn.createStatement().execute(createSeqDdl);
+      conn.commit();
+      for (int i = 0; i < nextCount; i++) {
+        try (ResultSet rs =
+          conn.createStatement().executeQuery("SELECT NEXT VALUE FOR " + 
sequenceName)) {
+          assertTrue(rs.next());
+          lastActiveValue = rs.getLong(1);
+        }
+      }
+    }
+
+    logGroup.close();
+    Map<String, List<Mutation>> logsByTable = groupLogsByTable();
+    dumpTableLogCount(logsByTable);
+    assertTrue("SYSTEM.SEQUENCE writes should replicate",
+      getCountForTable(logsByTable, SYSTEM_SEQUENCE_NAME) > 0);
+
+    replayAndVerifyAcrossClusters(Collections.emptyList());
+
+    try (Connection conn2 = CLUSTERS.getCluster2Connection(haGroup); ResultSet 
rs =
+      conn2.createStatement().executeQuery("SELECT NEXT VALUE FOR " + 
sequenceName)) {
+      assertTrue(rs.next());
+      assertTrue("Standby must resume past the last active value (gap allowed, 
never a duplicate)",
+        rs.getLong(1) > lastActiveValue);
+    }
+  }
+
+  /**
+   * DROP SEQUENCE must NOT replicate: the drop's Delete is left untagged (see
+   * SequenceRegionObserver.preAppend), so only the CREATE and INCREMENT 
writes ship. After dropping
+   * on the active and replaying onto cluster 2, the standby's SYSTEM.SEQUENCE 
row must survive --
+   * NEXT VALUE FOR there still resumes from the replicated value. A 
regression that shipped the
+   * drop would wipe the standby row and this NEXT VALUE FOR would fail.
+   */
+  @Test
+  public void testSystemSequenceDropNotReplicated() throws Exception {
+    final String sequenceName = "SEQ_" + generateUniqueName();
+    final int nextCount = 5;
+    final String createSeqDdl =
+      "CREATE SEQUENCE " + sequenceName + " START WITH 1 INCREMENT BY 1 CACHE 
1";
+
+    long lastActiveValue = -1;
+    try (FailoverPhoenixConnection conn = (FailoverPhoenixConnection) 
DriverManager
+      .getConnection(CLUSTERS.getJdbcHAUrl(), clientProps)) {
+      conn.createStatement().execute(createSeqDdl);
+      conn.commit();
+      for (int i = 0; i < nextCount; i++) {
+        try (ResultSet rs =
+          conn.createStatement().executeQuery("SELECT NEXT VALUE FOR " + 
sequenceName)) {
+          assertTrue(rs.next());
+          lastActiveValue = rs.getLong(1);
+        }
+      }
+      // Drop on the active. The Delete is untagged, so it must not ship.
+      conn.createStatement().execute("DROP SEQUENCE " + sequenceName);
+      conn.commit();
+    }
+
+    logGroup.close();
+    replayAndVerifyAcrossClusters(Collections.emptyList());
+
+    // The standby row survived replay: NEXT VALUE FOR resumes from the 
replicated value rather than
+    // failing (which it would if the drop's Delete had been shipped and wiped 
the row).
+    try (Connection conn2 = CLUSTERS.getCluster2Connection(haGroup); ResultSet 
rs =
+      conn2.createStatement().executeQuery("SELECT NEXT VALUE FOR " + 
sequenceName)) {
+      assertTrue(rs.next());
+      assertEquals(
+        "Standby row must survive a non-replicated DROP and resume from 
replicated value",
+        lastActiveValue + 1, rs.getLong(1));
+    }
+  }
+
   /**
    * Verifies that when data mutations are replayed on the standby via 
ReplicationLogProcessor,
    * IndexRegionObserver on the standby generates index mutations from the 
data mutations. The
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/schema/SchemaUtilTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/schema/SchemaUtilTest.java
index d03a588b70..c8bc3e5093 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/schema/SchemaUtilTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/SchemaUtilTest.java
@@ -18,9 +18,12 @@
 package org.apache.phoenix.schema;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
 import org.apache.phoenix.util.SchemaUtil;
 import org.junit.Test;
 
@@ -78,4 +81,19 @@ public class SchemaUtilTest {
     String tableDisplayName = 
SchemaUtil.getTableNameFromFullName("schemaName.tableName");
     assertEquals(tableDisplayName, "tableName");
   }
+
+  @Test
+  public void testShouldReplicateTable() {
+    // Non-system tables always replicate.
+    
assertTrue(SchemaUtil.shouldReplicateTable(Bytes.toBytes("schemaName.tableName")));
+    // SYSTEM.CATALOG, SYSTEM.CHILD_LINK and SYSTEM.SEQUENCE replicate.
+    
assertTrue(SchemaUtil.shouldReplicateTable(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME_BYTES));
+    assertTrue(
+      
SchemaUtil.shouldReplicateTable(PhoenixDatabaseMetaData.SYSTEM_CHILD_LINK_NAME_BYTES));
+    
assertTrue(SchemaUtil.shouldReplicateTable(PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_NAME_BYTES));
+    // Other system tables do not replicate.
+    
assertFalse(SchemaUtil.shouldReplicateTable(PhoenixDatabaseMetaData.SYSTEM_STATS_NAME_BYTES));
+    assertFalse(
+      
SchemaUtil.shouldReplicateTable(PhoenixDatabaseMetaData.SYSTEM_FUNCTION_NAME_BYTES));
+  }
 }

Reply via email to