blob: 7ea34773d90736dbb992e06a34f01dad1ffc83c0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*/
package org.apache.phoenix.end2end;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.PropertiesUtil;
import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.ReadOnlyProps;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.google.common.collect.Lists;
/**
* Suite of integration tests that validate that Bulk Allocation of Sequence values
* using the NEXT <n> VALUES FOR <seq> syntax works as expected and interacts
* correctly with NEXT VALUE FOR <seq> and CURRENT VALUE FOR <seq>.
*
* All tests are run with both a generic connection and a multi-tenant connection.
*
*/
@RunWith(Parameterized.class)
public class SequenceBulkAllocationIT extends BaseClientManagedTimeIT {
private static final long BATCH_SIZE = 3;
private static final String SELECT_NEXT_VALUE_SQL =
"SELECT NEXT VALUE FOR %s FROM SYSTEM.\"SEQUENCE\" LIMIT 1";
private static final String SELECT_CURRENT_VALUE_SQL =
"SELECT CURRENT VALUE FOR %s FROM SYSTEM.\"SEQUENCE\" LIMIT 1";
private static final String CREATE_SEQUENCE_NO_MIN_MAX_TEMPLATE =
"CREATE SEQUENCE bulkalloc.alpha START WITH %s INCREMENT BY %s CACHE %s";
private static final String CREATE_SEQUENCE_WITH_MIN_MAX_TEMPLATE =
"CREATE SEQUENCE bulkalloc.alpha START WITH %s INCREMENT BY %s MINVALUE %s MAXVALUE %s CACHE %s";
private static final String CREATE_SEQUENCE_WITH_MIN_MAX_AND_CYCLE_TEMPLATE =
"CREATE SEQUENCE bulkalloc.alpha START WITH %s INCREMENT BY %s MINVALUE %s MAXVALUE %s CYCLE CACHE %s";
private Connection conn;
private String tenantId;
public SequenceBulkAllocationIT(String tenantId) {
this.tenantId = tenantId;
}
@BeforeClass
@Shadower(classBeingShadowed = BaseClientManagedTimeIT.class)
public static void doSetup() throws Exception {
Map<String, String> props = getDefaultProps();
// Must update config before starting server
props.put(QueryServices.SEQUENCE_CACHE_SIZE_ATTRIB, Long.toString(BATCH_SIZE));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
@After
public void tearDown() throws Exception {
// close any open connection between tests, so that connections are not leaked
if (conn != null) {
conn.close();
}
}
@Parameters(name="SequenceBulkAllocationIT_tenantId={0}") // name is used by failsafe as file name in reports
public static Object[] data() {
return new Object[] {null, "tenant1"};
}
@Test
public void testSequenceParseNextValuesWithNull() throws Exception {
nextConnection();
try {
conn.createStatement().executeQuery(
"SELECT NEXT NULL VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\" LIMIT 1");
fail("null is not allowed to be used for <n> in NEXT <n> VALUES FOR <seq>");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.NUM_SEQ_TO_ALLOCATE_MUST_BE_CONSTANT.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
}
@Test
public void testSequenceParseNextValuesWithNonNumber() throws Exception {
nextConnection();
try {
conn.createStatement().executeQuery(
"SELECT NEXT '89b' VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\" LIMIT 1");
fail("Only integers and longs are allowed to be used for <n> in NEXT <n> VALUES FOR <seq>");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.NUM_SEQ_TO_ALLOCATE_MUST_BE_CONSTANT.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
}
@Test
public void testSequenceParseNextValuesWithNegativeNumber() throws Exception {
nextConnection();
try {
conn.createStatement().executeQuery(
"SELECT NEXT '-1' VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\" LIMIT 1");
fail("null is not allowed to be used for <n> in NEXT <n> VALUES FOR <seq>");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.NUM_SEQ_TO_ALLOCATE_MUST_BE_CONSTANT.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
}
@Test
public void testParseNextValuesSequenceWithZeroAllocated() throws Exception {
nextConnection();
try {
conn.createStatement().executeQuery(
"SELECT NEXT 0 VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\" LIMIT 1");
fail("Only integers and longs are allowed to be used for <n> in NEXT <n> VALUES FOR <seq>");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.NUM_SEQ_TO_ALLOCATE_MUST_BE_CONSTANT.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
}
@Test
public void testNextValuesForSequenceWithNoAllocatedValues() throws Exception {
// Create Sequence
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(1)
.numAllocated(100).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
// Bulk Allocate Sequence Slots
final int currentValueAfterAllocation = 100;
reserveSlotsInBulkAndAssertValue(1, props.numAllocated);
assertExpectedStateInSystemSequence(props, 101);
assertExpectedNumberOfValuesAllocated(1, currentValueAfterAllocation, props.incrementBy, props.numAllocated);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(currentValueAfterAllocation);
assertExpectedNextValueForSequence(101);
}
@Test
/**
* Validates we can invoke NEXT <n> VALUES FOR using bind vars.
*/
public void testNextValuesForSequenceUsingBinds() throws Exception {
// Create Sequence
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(1)
.numAllocated(100).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
// Allocate 100 slots using SQL with Bind Params and a PreparedStatement
final int currentValueAfterAllocation = 100;
reserveSlotsInBulkUsingBindsAndAssertValue(1,props.numAllocated);
assertExpectedStateInSystemSequence(props, 101);
assertExpectedNumberOfValuesAllocated(1, currentValueAfterAllocation, props.incrementBy, props.numAllocated);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(currentValueAfterAllocation);
assertExpectedNextValueForSequence(101);
}
@Test
public void testNextValuesForSequenceWithPreviouslyAllocatedValues() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(2);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 1100;
int nextValueAfterAllocation = currentValueAfterAllocation + props.incrementBy;
int startValueAfterAllocation = 101;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(currentValueAfterAllocation);
assertExpectedNextValueForSequence(nextValueAfterAllocation);
}
@Test
/**
* Validates that if we close a connection after performing
* NEXT <n> VALUES FOR <seq> the values are correctly returned from
* the latest batch.
*/
public void testConnectionCloseReturnsSequenceValuesCorrectly() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(2).startsWith(1).cacheSize(100)
.numAllocated(100).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(3);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 399;
int nextValueAfterAllocation = currentValueAfterAllocation + props.incrementBy;
int startValueAfterAllocation = 201;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertExpectedCurrentValueForSequence(currentValueAfterAllocation);
// Close the Connection
conn.close();
// Test that sequence, doesn't have gaps after closing the connection
nextConnection();
assertExpectedNextValueForSequence(nextValueAfterAllocation);
assertExpectedCurrentValueForSequence(nextValueAfterAllocation);
}
@Test
/**
* Validates that calling NEXT <n> VALUES FOR <seq> works correctly with UPSERT.
*/
public void testNextValuesForSequenceWithUpsert() throws Exception {
// Create Sequence
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
// Create TABLE
nextGenericConnection();
conn.createStatement().execute("CREATE TABLE bulkalloc.test ( id INTEGER NOT NULL PRIMARY KEY)");
nextConnection();
// Grab batch from Sequence
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(2);
assertExpectedStateInSystemSequence(props, 101);
// Perform UPSERT and validate Sequence was incremented as expected
conn.createStatement().execute("UPSERT INTO bulkalloc.test (id) VALUES (NEXT " + props.numAllocated + " VALUES FOR bulkalloc.alpha)");
conn.commit();
assertExpectedStateInSystemSequence(props, 1101);
// SELECT values out and verify
nextConnection();
String query = "SELECT id, NEXT VALUE FOR bulkalloc.alpha FROM bulkalloc.test";
ResultSet rs = conn.prepareStatement(query).executeQuery();
assertTrue(rs.next());
assertEquals(101, rs.getInt(1)); // Threw out cache of 100, incremented by 1000
assertEquals(1101, rs.getInt(2));
assertFalse(rs.next());
}
@Test
public void testNextValuesForSequenceWithIncrementBy() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(3).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(4);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 3298;
int startValueAfterAllocation = 301;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(3298);
assertExpectedNextValueForSequence(3301);
}
@Test
public void testNextValuesForSequenceWithNegativeIncrementBy() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(-1).startsWith(2000).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(2000);
assertExpectedCurrentValueForSequence(2000);
assertExpectedNextValueForSequence(1999);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 901;
int startValueAfterAllocation = 1900;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(901);
assertExpectedNextValueForSequence(900);
}
@Test
public void testNextValuesForSequenceWithNegativeIncrementByGreaterThanOne() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(-5).startsWith(2000).cacheSize(100)
.numAllocated(100).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
// Pull first batch from Sequence
assertExpectedNextValueForSequence(2000);
assertExpectedCurrentValueForSequence(2000);
assertExpectedNextValueForSequence(1995);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 1005;
int startValueAfterAllocation = 1500;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(1005);
assertExpectedNextValueForSequence(1000);
}
@Test
/**
* Validates that for NEXT <n> VALUES FOR if you try an allocate more slots such that that
* we exceed the max value of the sequence we throw an exception. Allocating sequence values in bulk
* should be an all or nothing operation - if the operation succeeds clients are guaranteed that they
* have access to all slots requested.
*/
public void testNextValuesForSequenceExceedsMaxValue() throws Exception {
final SequenceProperties sequenceProps =
new SequenceProperties.Builder().incrementBy(1).startsWith(100).cacheSize(100)
.numAllocated(1000).minValue(100).maxValue(900).build();
nextConnection();
createSequenceWithMinMax(sequenceProps);
nextConnection();
// Pull first batch from the sequence
assertExpectedNextValueForSequence(100);
assertExpectedCurrentValueForSequence(100);
assertExpectedNextValueForSequence(101);
// Attempt to bulk Allocate more slots than available
try {
conn.createStatement().executeQuery(
"SELECT NEXT " + sequenceProps.numAllocated
+ " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\" LIMIT 1");
fail("Invoking SELECT NEXT VALUES should have thrown Reached Max Value Exception");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
// Assert sequence didn't advance
assertExpectedCurrentValueForSequence(101);
assertExpectedNextValueForSequence(102);
}
@Test
/**
* Validates that for NEXT <n> VALUES FOR if you try an allocate more slots such that that
* we exceed the min value of the sequence we throw an exception. Allocating sequence values in bulk
* should be an all or nothing operation - if the operation succeeds clients are guaranteed that they
* have access to all slots requested.
*/
public void testNextValuesForSequenceExceedsMinValue() throws Exception {
final SequenceProperties sequenceProps =
new SequenceProperties.Builder().incrementBy(-5).startsWith(900).cacheSize(100)
.numAllocated(160).minValue(100).maxValue(900).build();
nextConnection();
createSequenceWithMinMax(sequenceProps);
nextConnection();
// Pull first batch from the sequence
assertExpectedNextValueForSequence(900);
assertExpectedCurrentValueForSequence(900);
assertExpectedNextValueForSequence(895);
// Attempt to bulk Allocate more slots than available
try {
conn.createStatement().executeQuery(
"SELECT NEXT " + sequenceProps.numAllocated
+ " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\" LIMIT 1");
fail("Invoking SELECT NEXT VALUES should have thrown Reached Max Value Exception");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MIN_VALUE.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
// Assert sequence didn't advance (we still throw out the cached values)
assertExpectedCurrentValueForSequence(895);
assertExpectedNextValueForSequence(890);
}
@Test
/**
* Validates that if we don't exceed the limit bulk allocation works with sequences with a
* min and max defined.
*/
public void testNextValuesForSequenceWithMinMaxDefined() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(5).startsWith(100).cacheSize(100)
.numAllocated(1000).minValue(100).maxValue(6000).build();
nextConnection();
createSequenceWithMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(100);
assertExpectedCurrentValueForSequence(100);
assertExpectedNextValueForSequence(105);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 5595;
int startValueAfterAllocation = 600;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(5595);
assertExpectedNextValueForSequence(5600);
}
@Test
public void testNextValuesForSequenceWithDefaultMax() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(100).cacheSize(100)
.numAllocated(Long.MAX_VALUE - 100).build();
// Create Sequence
nextConnection();
createSequenceWithMinMax(props);
nextConnection();
// Bulk Allocate Sequence Slots
long currentValueAfterAllocation = 100;
long startValueAfterAllocation = Long.MAX_VALUE;
reserveSlotsInBulkAndAssertValue(currentValueAfterAllocation, props.numAllocated);
assertExpectedStateInSystemSequence(props, startValueAfterAllocation);
// Try and get next value
try {
conn.createStatement().executeQuery(String.format(SELECT_NEXT_VALUE_SQL, "bulkalloc.alpha"));
} catch (SQLException e) {
assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
}
@Test
/**
* Validates that if our current or start value is > 0 and we ask for Long.MAX
* and overflow to the next value, the correct Exception is thrown when
* the expression is evaluated.
*/
public void testNextValuesForSequenceOverflowAllocation() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(100).cacheSize(100)
.numAllocated(Long.MAX_VALUE).build();
// Create Sequence
nextConnection();
createSequenceWithMinMax(props);
nextConnection();
// Bulk Allocate Sequence Slots
try {
conn.createStatement().executeQuery(
"SELECT NEXT " + Long.MAX_VALUE
+ " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
}
@Test
/**
* Validates that specifying an bulk allocation less than the size of the cache defined on the sequence works
* as expected.
*/
public void testNextValuesForSequenceAllocationLessThanCacheSize() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(5).startsWith(100).cacheSize(100)
.numAllocated(50).minValue(100).maxValue(6000).build();
nextConnection();
createSequenceWithMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(100);
assertExpectedCurrentValueForSequence(100);
assertExpectedNextValueForSequence(105);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 355;
int startValueAfterAllocation = 110;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertExpectedStateInSystemSequence(props, 600);
assertExpectedNumberOfValuesAllocated(startValueAfterAllocation, currentValueAfterAllocation, props.incrementBy, props.numAllocated);
// Assert standard Sequence Operations return expected values
// 105 + (50 * 5) = 355
assertExpectedCurrentValueForSequence(355);
assertExpectedNextValueForSequence(360);
assertExpectedNextValueForSequence(365);
assertExpectedNextValueForSequence(370);
}
@Test
/**
* Validates that specifying an bulk allocation less than the size of the cache defined on the sequence works
* as expected if we don't have enough values in the cache to support the allocation.
*/
public void testNextValuesForInsufficentCacheValuesAllocationLessThanCacheSize() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(5).startsWith(100).cacheSize(100)
.numAllocated(50).minValue(100).maxValue(6000).build();
nextConnection();
createSequenceWithMinMax(props);
nextConnection();
// Allocate 51 slots, only 49 will be left
int currentValueAfter51Allocations = 355; // 100 + 51 * 5
for (int i = 100; i <= currentValueAfter51Allocations; i = i + 5) {
assertExpectedNextValueForSequence(i);
}
assertExpectedCurrentValueForSequence(currentValueAfter51Allocations);
// Bulk Allocate 50 Sequence Slots which greater than amount left in cache
// This should throw away rest of the cache, and allocate the request slot
// from the next start value
int currentValueAfterAllocation = 845;
int startValueAfterAllocation = 600;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(845);
assertExpectedNextValueForSequence(850);
assertExpectedNextValueForSequence(855);
assertExpectedNextValueForSequence(860);
}
@Test
/**
* Validates that for NEXT <n> VALUES FOR is not supported on Sequences that have the
* CYCLE flag set to true.
*/
public void testNextValuesForSequenceWithCycles() throws Exception {
final SequenceProperties sequenceProps =
new SequenceProperties.Builder().incrementBy(5).startsWith(100).cacheSize(100)
.numAllocated(1000).minValue(100).maxValue(900).build();
nextConnection();
createSequenceWithMinMaxAndCycle(sequenceProps);
nextConnection();
// Full first batch from the sequence
assertExpectedNextValueForSequence(100);
assertExpectedCurrentValueForSequence(100);
assertExpectedNextValueForSequence(105);
// Attempt to bulk Allocate more slots than available
try {
conn.createStatement().executeQuery(
"SELECT NEXT " + sequenceProps.numAllocated
+ " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\" LIMIT 1");
fail("Invoking SELECT NEXT VALUES should have failed as operation is not supported for sequences with Cycles.");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.NUM_SEQ_TO_ALLOCATE_NOT_SUPPORTED.getErrorCode(),
e.getErrorCode());
assertTrue(e.getNextException() == null);
}
// Assert sequence didn't advance
assertExpectedCurrentValueForSequence(105);
assertExpectedNextValueForSequence(110);
assertExpectedNextValueForSequence(115);
}
@Test
/**
* Validates that if we have multiple NEXT <n> VALUES FOR <seq> expression and the
* CURRENT VALUE FOR expression work correctly when used in the same statement.
*/
public void testCurrentValueForAndNextValuesForExpressionsForSameSequence() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(2);
// Bulk Allocate Sequence Slots
int currentValueAfterAllocation = 1100;
int nextValueAfterAllocation = currentValueAfterAllocation + props.incrementBy;
int startValueAfterAllocation = 101;
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT CURRENT VALUE FOR bulkalloc.alpha, NEXT " + props.numAllocated + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
assertTrue(rs.next());
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
int currentValueFor = rs.getInt(1);
int nextValuesFor = rs.getInt(2);
assertEquals("Expected the next value to be first value reserved", startValueAfterAllocation, nextValuesFor);
assertEquals("Expected current value to be the same as next value", startValueAfterAllocation, currentValueFor);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(currentValueAfterAllocation);
assertExpectedNextValueForSequence(nextValueAfterAllocation);
}
@Test
/**
* Validates that if we have multiple NEXT <n> VALUES FOR <seq> expressions for the *same* sequence
* in a statement we only process the one which has the highest value of <n> and return the start
* value for that for all expressions.
*/
public void testMultipleNextValuesForExpressionsForSameSequence() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(2);
// Bulk Allocate Sequence Slots - One for 5 and one for 1000, 1000 should have precedence
int currentValueAfterAllocation = 1100;
int nextValueAfterAllocation = currentValueAfterAllocation + props.incrementBy;
int startValueAfterAllocation = 101;
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT 5 VALUES FOR bulkalloc.alpha, NEXT " + props.numAllocated + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
assertTrue(rs.next());
int firstValue = rs.getInt(1);
int secondValue = rs.getInt(2);
assertEquals("Expected both expressions to return the same value", firstValue, secondValue);
assertEquals("Expected the value returned to be the highest allocation", startValueAfterAllocation, firstValue);
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(currentValueAfterAllocation);
assertExpectedNextValueForSequence(nextValueAfterAllocation);
}
@Test
/**
* Validates that if we have NEXT VALUE FOR <seq> and NEXT <n> VALUES FOR <seq> expressions for the *same* sequence
* in a statement we only process way and honor the value of the highest value of <n>, where for
* NEXT VALUE FOR <seq> is assumed to be 1.
*/
public void testMultipleDifferentExpressionsForSameSequence() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
// Pull First Batch from Sequence
assertExpectedNextValueForSequence(1);
// Bulk Allocate Sequence Slots and Get Next Value in Same Statement
int currentValueAfterAllocation = 1100;
int nextValueAfterAllocation = currentValueAfterAllocation + props.incrementBy;
int startValueAfterAllocation = 101;
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT VALUE FOR bulkalloc.alpha, "
+ "NEXT " + props.numAllocated + " VALUES FOR bulkalloc.alpha, "
+ "CURRENT VALUE FOR bulkalloc.alpha, "
+ "NEXT 999 VALUES FOR bulkalloc.alpha "
+ "FROM SYSTEM.\"SEQUENCE\"");
assertTrue(rs.next());
assertBulkAllocationSucceeded(props, currentValueAfterAllocation, startValueAfterAllocation);
// Assert all values returned are the same
// Expect them to be the highest value from NEXT VALUE or NEXT <n> VALUES FOR
int previousVal = 0;
for (int i = 1; i <= 4; i++) {
int currentVal = rs.getInt(i);
if (i != 1) {
assertEquals(
"Expected all NEXT VALUE FOR and NEXT <n> VALUES FOR expressions to return the same value",
previousVal, currentVal);
}
previousVal = currentVal;
}
// Assert standard Sequence Operations return expected values
assertExpectedCurrentValueForSequence(currentValueAfterAllocation);
assertExpectedNextValueForSequence(nextValueAfterAllocation);
}
@Test
/**
* Validates that using NEXT <n> VALUES FOR on different sequences in the
* same statement with *different* values of <n> works as expected. This
* test validates that we keep our numAllocated array and sequence keys in
* sync during the sequence management process.
*/
public void testMultipleNextValuesForExpressionsForDifferentSequences() throws Exception {
nextConnection();
conn.createStatement().execute("CREATE SEQUENCE bulkalloc.alpha START WITH 30 INCREMENT BY 3 CACHE 100");
conn.createStatement().execute("CREATE SEQUENCE bulkalloc.beta START WITH 100 INCREMENT BY 5 CACHE 50");
nextConnection();
// Bulk Allocate Sequence Slots for Two Sequences
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT 100 VALUES FOR bulkalloc.alpha, NEXT 1000 VALUES FOR bulkalloc.beta FROM SYSTEM.\"SEQUENCE\"");
assertTrue(rs.next());
assertEquals(30, rs.getInt(1));
assertEquals(100, rs.getInt(2));
// Assert standard Sequence Operations return expected values
for (int i = 330; i < 330 + (2 * 100); i += 3) {
assertExpectedCurrentValueForSequence(i - 3, "bulkalloc.alpha");
assertExpectedNextValueForSequence(i, "bulkalloc.alpha");
}
for (int i = 5100; i < 5100 + (2 * 1000); i += 5) {
assertExpectedCurrentValueForSequence(i - 5, "bulkalloc.beta");
assertExpectedNextValueForSequence(i, "bulkalloc.beta");
}
}
@Test
/**
* Validates that calling NEXT <n> VALUES FOR with EXPLAIN PLAN doesn't use
* allocate any slots.
*/
public void testExplainPlanValidatesSequences() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(3).startsWith(30).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextGenericConnection();
conn.createStatement().execute("CREATE TABLE bulkalloc.simpletbl (k BIGINT NOT NULL PRIMARY KEY)");
nextConnection();
// Bulk Allocate Sequence Slots
int startValueAfterAllocation = 30;
reserveSlotsInBulkAndAssertValue(startValueAfterAllocation, props.numAllocated);
// Execute EXPLAIN PLAN multiple times, which should not change Sequence values
for (int i = 0; i < 3; i++) {
conn.createStatement().executeQuery("EXPLAIN SELECT NEXT 1000 VALUES FOR bulkalloc.alpha FROM bulkalloc.simpletbl");
}
// Validate the current value was not advanced and was the starting value
assertExpectedStateInSystemSequence(props, 3030);
// Assert standard Sequence Operations return expected values
int startValue = 3030;
for (int i = startValue; i < startValue + (2 * props.cacheSize); i += props.incrementBy) {
assertExpectedCurrentValueForSequence(i - props.incrementBy);
assertExpectedNextValueForSequence(i);
}
}
@Test
public void testExplainPlanForNextValuesFor() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(3).startsWith(30).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextGenericConnection();
conn.createStatement().execute("CREATE TABLE bulkalloc.simpletbl (k BIGINT NOT NULL PRIMARY KEY)");
nextConnection();
// Execute EXPLAIN PLAN which should not change Sequence values
ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT NEXT 1000 VALUES FOR bulkalloc.alpha FROM bulkalloc.simpletbl");
// Assert output for Explain Plain result is as expected
assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER BULKALLOC.SIMPLETBL\n" +
" SERVER FILTER BY FIRST KEY ONLY\n" +
"CLIENT RESERVE VALUES FROM 1 SEQUENCE", QueryUtil.getExplainPlan(rs));
}
/**
* Performs a multithreaded test whereby we interleave reads from the result set of
* NEXT VALUE FOR and NEXT <n> VALUES FOR to make sure we get expected values with the
* following order of execution:
*
* 1) Execute expression NEXT <n> VALUES FOR <seq>
* 2) Execute expression NEXT VALUE FOR <seq>
* 3) Read back value from expression NEXT VALUE FOR <seq> via rs.next()
* 4) Read back value from expression NEXT <n> VALUES FOR <seq> via rs.next()
*/
public void testNextValuesForMixedWithNextValueForMultiThreaded() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(2);
// Bulk Allocate Sequence Slots
final long startValueAfterAllocation1 = 101;
final long startValueAfterAllocation2 = 1101;
final long numSlotToAllocate = props.numAllocated;
// Setup and run tasks in independent Threads
ExecutorService executorService = Executors.newCachedThreadPool();
try {
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(1);
Callable<Long> task1 = new Callable<Long>() {
@Override
public Long call() throws Exception {
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT " + numSlotToAllocate + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
latch1.countDown(); // Allows NEXT VALUE FOR thread to proceed
latch2.await(); // Waits until NEXT VALUE FOR thread reads and increments currentValue
rs.next();
return rs.getLong(1);
}
};
Callable<Long> task2 = new Callable<Long>() {
@Override
public Long call() throws Exception {
latch1.await(); // Wait for execution of NEXT <n> VALUES FOR expression
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT VALUE FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
rs.next();
long retVal = rs.getLong(1);
latch2.countDown(); // Allow NEXT <n> VALUES for thread to completed
return retVal;
}
};
@SuppressWarnings("unchecked")
List<Future<Long>> futures = executorService.invokeAll(Lists.newArrayList(task1, task2), 20, TimeUnit.SECONDS);
assertEquals(startValueAfterAllocation1, futures.get(0).get(10, TimeUnit.SECONDS).longValue());
assertEquals(startValueAfterAllocation2, futures.get(1).get(10, TimeUnit.SECONDS).longValue());
} finally {
executorService.shutdown();
}
}
@Test
public void testMultipleNextValuesWithDiffAllocsForMultiThreaded() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
assertExpectedNextValueForSequence(1);
assertExpectedCurrentValueForSequence(1);
assertExpectedNextValueForSequence(2);
// Bulk Allocate Sequence Slots
final long startValueAfterAllocation1 = 101;
final long startValueAfterAllocation2 = 1101;
final long numSlotToAllocate1 = 1000;
final long numSlotToAllocate2 = 100;
// Setup and run tasks in independent Threads
ExecutorService executorService = Executors.newCachedThreadPool();
try {
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(1);
Callable<Long> task1 = new Callable<Long>() {
@Override
public Long call() throws Exception {
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT " + numSlotToAllocate1 + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
rs.next();
latch1.countDown(); // Allows other thread to proceed
latch2.await();
return rs.getLong(1);
}
};
Callable<Long> task2 = new Callable<Long>() {
@Override
public Long call() throws Exception {
latch1.await(); // Wait for other thread to execut of NEXT <n> VALUES FOR expression
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT " + numSlotToAllocate2 + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
rs.next();
long retVal = rs.getLong(1);
latch2.countDown(); // Allow thread to completed
return retVal;
}
};
@SuppressWarnings("unchecked")
List<Future<Long>> futures = executorService.invokeAll(Lists.newArrayList(task1, task2), 5, TimeUnit.SECONDS);
// Retrieve value from Thread running NEXT <n> VALUES FOR
Long retValue1 = futures.get(0).get(5, TimeUnit.SECONDS);
assertEquals(startValueAfterAllocation1, retValue1.longValue());
// Retrieve value from Thread running NEXT VALUE FOR
Long retValue2 = futures.get(1).get(5, TimeUnit.SECONDS);
assertEquals(startValueAfterAllocation2, retValue2.longValue());
} finally {
executorService.shutdown();
}
}
@Test
public void testMultipleNextValuesWithSameAllocsForMultiThreaded() throws Exception {
final SequenceProperties props =
new SequenceProperties.Builder().incrementBy(1).startsWith(1).cacheSize(100)
.numAllocated(1000).build();
nextConnection();
createSequenceWithNoMinMax(props);
nextConnection();
// Bulk Allocate Sequence Slots
final long startValueAfterAllocation1 = 1;
final long startValueAfterAllocation2 = 1001;
final long numSlotToAllocate1 = 1000;
final long numSlotToAllocate2 = 1000;
// Setup and run tasks in independent Threads
ExecutorService executorService = Executors.newCachedThreadPool();
try {
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(1);
Callable<Long> task1 = new Callable<Long>() {
@Override
public Long call() throws Exception {
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT " + numSlotToAllocate1 + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
latch1.countDown(); // Allows other thread to proceed
latch2.await();
rs.next();
return rs.getLong(1);
}
};
Callable<Long> task2 = new Callable<Long>() {
@Override
public Long call() throws Exception {
latch1.await(); // Wait for other thread to execut of NEXT <n> VALUES FOR expression
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT " + numSlotToAllocate2 + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
rs.next();
long retVal = rs.getLong(1);
latch2.countDown(); // Allow thread to completed
return retVal;
}
};
// Because of the way the threads are interleaved the ranges used by each thread will the reserve
// of the order to statement execution
@SuppressWarnings("unchecked")
List<Future<Long>> futures = executorService.invokeAll(Lists.newArrayList(task1, task2), 5, TimeUnit.SECONDS);
assertEquals(startValueAfterAllocation2, futures.get(0).get(5, TimeUnit.SECONDS).longValue());
assertEquals(startValueAfterAllocation1, futures.get(1).get(5, TimeUnit.SECONDS).longValue());
} finally {
executorService.shutdown();
}
}
// -----------------------------------------------------------------
// Private Helper Methods
// -----------------------------------------------------------------
private void assertBulkAllocationSucceeded(SequenceProperties props,
int currentValueAfterAllocation, int startValueAfterAllocation) throws SQLException {
int nextValueAfterAllocation = currentValueAfterAllocation + props.incrementBy;
assertExpectedStateInSystemSequence(props, nextValueAfterAllocation);
assertExpectedNumberOfValuesAllocated(startValueAfterAllocation, currentValueAfterAllocation, props.incrementBy, props.numAllocated);
}
private void createSequenceWithNoMinMax(final SequenceProperties props) throws SQLException {
conn.createStatement().execute(
String.format(CREATE_SEQUENCE_NO_MIN_MAX_TEMPLATE, props.startsWith,
props.incrementBy, props.cacheSize));
}
private void createSequenceWithMinMax(final SequenceProperties props) throws SQLException {
conn.createStatement().execute(
String.format(CREATE_SEQUENCE_WITH_MIN_MAX_TEMPLATE, props.startsWith,
props.incrementBy, props.minValue, props.maxValue, props.cacheSize));
}
private void createSequenceWithMinMaxAndCycle(final SequenceProperties props) throws SQLException {
conn.createStatement().execute(
String.format(CREATE_SEQUENCE_WITH_MIN_MAX_AND_CYCLE_TEMPLATE, props.startsWith,
props.incrementBy, props.minValue, props.maxValue, props.cacheSize));
}
private void reserveSlotsInBulkAndAssertValue(long expectedValue, long numSlotToAllocate)
throws SQLException {
ResultSet rs =
conn.createStatement().executeQuery(
"SELECT NEXT " + numSlotToAllocate + " VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
assertTrue(rs.next());
assertEquals(expectedValue, rs.getInt(1));
}
private void reserveSlotsInBulkUsingBindsAndAssertValue(int expectedValue, long numSlotToAllocate)
throws SQLException {
PreparedStatement ps = conn.prepareStatement("SELECT NEXT ? VALUES FOR bulkalloc.alpha FROM SYSTEM.\"SEQUENCE\"");
ps.setLong(1, numSlotToAllocate);
ResultSet rs = ps.executeQuery();
assertTrue(rs.next());
int retValue = rs.getInt(1);
assertEquals(expectedValue, retValue);
}
private void assertExpectedCurrentValueForSequence(int expectedValue) throws SQLException {
assertExpectedCurrentValueForSequence(expectedValue, "bulkalloc.alpha");
}
private void assertExpectedCurrentValueForSequence(int expectedValue, String sequenceName) throws SQLException {
ResultSet rs;
rs = conn.createStatement().executeQuery(String.format(SELECT_CURRENT_VALUE_SQL, sequenceName));
assertTrue(rs.next());
assertEquals(expectedValue, rs.getInt(1));
}
private void assertExpectedNextValueForSequence(int expectedValue) throws SQLException {
assertExpectedNextValueForSequence(expectedValue, "bulkalloc.alpha");
}
private void assertExpectedNextValueForSequence(int expectedValue, String sequenceName) throws SQLException {
ResultSet rs;
rs = conn.createStatement().executeQuery(String.format(SELECT_NEXT_VALUE_SQL, sequenceName));
assertTrue(rs.next());
assertEquals(expectedValue, rs.getInt(1));
}
/**
* Returns a non-tenant specific connection.
*/
private void nextGenericConnection() throws Exception {
if (conn != null) conn.close();
long ts = nextTimestamp();
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts));
conn = DriverManager.getConnection(getUrl(), props);
}
private void nextConnection() throws Exception {
if (conn != null) conn.close();
long ts = nextTimestamp();
if (tenantId != null) {
// Create tenant specific connection
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(nextTimestamp()));
this.conn = DriverManager.getConnection(getUrl() + ';' + TENANT_ID_ATTRIB + '=' + "tenant1", props);
} else {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts));
conn = DriverManager.getConnection(getUrl(), props);
}
}
private void assertExpectedStateInSystemSequence(SequenceProperties props, long currentValue)
throws SQLException {
// Validate state in System.Sequence
ResultSet rs =
conn.createStatement()
.executeQuery(
"SELECT start_with, current_value, increment_by, cache_size, min_value, max_value, cycle_flag, sequence_schema, sequence_name FROM SYSTEM.\"SEQUENCE\"");
assertTrue(rs.next());
assertEquals(props.startsWith, rs.getLong("start_with"));
assertEquals(props.incrementBy, rs.getLong("increment_by"));
assertEquals(props.cacheSize, rs.getLong("cache_size"));
assertEquals(false, rs.getBoolean("cycle_flag"));
assertEquals("BULKALLOC", rs.getString("sequence_schema"));
assertEquals("ALPHA", rs.getString("sequence_name"));
assertEquals(currentValue, rs.getLong("current_value"));
assertEquals(props.minValue, rs.getLong("min_value"));
assertEquals(props.maxValue, rs.getLong("max_value"));
assertFalse(rs.next());
}
private void assertExpectedNumberOfValuesAllocated(long firstValue, long lastValue,
int incrementBy, long numAllocated) {
int cnt = 0;
for (long i = firstValue; (incrementBy > 0 ? i <= lastValue : i >= lastValue); i += incrementBy) {
cnt++;
}
assertEquals("Incorrect number of values allocated: " + cnt, numAllocated, cnt);
}
private static class SequenceProperties {
private final long numAllocated;
private final int incrementBy;
private final int startsWith;
private final int cacheSize;
private final long minValue;
private final long maxValue;
public SequenceProperties(Builder builder) {
this.numAllocated = builder.numAllocated;
this.incrementBy = builder.incrementBy;
this.startsWith = builder.startsWith;
this.cacheSize = builder.cacheSize;
this.minValue = builder.minValue;
this.maxValue = builder.maxValue;
}
private static class Builder {
long maxValue = Long.MAX_VALUE;
long minValue = Long.MIN_VALUE;
long numAllocated = 100;
int incrementBy = 1;
int startsWith = 1;
int cacheSize = 100;
public Builder numAllocated(long numAllocated) {
this.numAllocated = numAllocated;
return this;
}
public Builder startsWith(int startsWith) {
this.startsWith = startsWith;
return this;
}
public Builder cacheSize(int cacheSize) {
this.cacheSize = cacheSize;
return this;
}
public Builder incrementBy(int incrementBy) {
this.incrementBy = incrementBy;
return this;
}
public Builder minValue(long minValue) {
this.minValue = minValue;
return this;
}
public Builder maxValue(long maxValue) {
this.maxValue = maxValue;
return this;
}
public SequenceProperties build() {
return new SequenceProperties(this);
}
}
}
}