/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.iv2;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.voltcore.TransactionIdManager;
/**
* Encapsulates an Iv2 transaction id, timestamp and random number seed.
*/
final public class TxnEgo {
// The transaction id layout.
static final long UNUSED_SIGN_BITS = 1;
static final long SEQUENCE_BITS = 49;
static final long PARTITIONID_BITS = 14;
// maximum values for the fields
static final long SEQUENCE_MAX_VALUE = (1L << SEQUENCE_BITS) - 1L;
static final int PARTITIONID_MAX_VALUE = (1 << PARTITIONID_BITS) - 1;
// The legacy transaction id included 40-bits of timestamp starting
// from VOLT_EPOCH: time in millis since 1/1/2008 at 12am. Iv2 ids
// are seeded from 12/31/2014 to guarantee uniqueness with previously
// generated legacy ids.
static final long VOLT_EPOCH = TransactionIdManager.getEpoch();;
// Create the starting SEQUENCE value. Must shift this left
// to put the legacy transaction id seed in the most significant
// 40-bits of the 49 bit sequence number (where it existed bit-wise
// in the legacy id).
static public final long SEQUENCE_ZERO = (getSequenceZero() - VOLT_EPOCH);
private final static long getSequenceZero() {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(0);
c.set(2015, 0, 1, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
c.set(Calendar.ZONE_OFFSET, 0);
c.set(Calendar.DST_OFFSET, 0);
long retval = c.getTimeInMillis();
return retval;
}
// per TxnEgo data.
private final long m_txnId;
/**
* Make the zero-valued (initial) TxnEgo for a partition
*/
public static TxnEgo makeZero(final long partitionId)
{
return new TxnEgo(SEQUENCE_ZERO, partitionId);
}
/**
* Make the next sequence-valued TxnEgo
*/
public TxnEgo makeNext()
{
return new TxnEgo(getSequence() + 1, getPartitionId());
}
public TxnEgo(long txnId) {
this(getSequence(txnId), getPartitionId(txnId));
}
TxnEgo(long sequence, long partitionId)
{
if (sequence < SEQUENCE_ZERO) {
throw new IllegalArgumentException("Invalid sequence value "
+ sequence + " is less than minimum allowed value "
+ SEQUENCE_ZERO);
}
else if (sequence > SEQUENCE_MAX_VALUE) {
throw new IllegalArgumentException("Invalid sequence value "
+ sequence + " is greater than maximum allowed value "
+ SEQUENCE_MAX_VALUE);
}
else if (partitionId < 0) {
throw new IllegalArgumentException("Invalid partitionId value "
+ partitionId + ". Must be greater than or equal to 0.");
}
else if (partitionId > PARTITIONID_MAX_VALUE) {
throw new IllegalArgumentException("Invalid partitionId value "
+ partitionId + " is greater than maximum allowed value "
+ PARTITIONID_MAX_VALUE);
}
m_txnId = (sequence << PARTITIONID_BITS) | partitionId;
}
final public long getTxnId()
{
return m_txnId;
}
public static long getSequence(long txnId) {
return txnId >> PARTITIONID_BITS;
}
public static int getPartitionId(long txnId) {
return (int) txnId & PARTITIONID_MAX_VALUE;
}
final public int getPartitionId()
{
return (int) m_txnId & PARTITIONID_MAX_VALUE;
}
long getSequence() {
long seq = m_txnId >> PARTITIONID_BITS;
return seq;
}
/**
* Get a string representation of the TxnId
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(128);
sb.append("TxnId: ").append(getTxnId());
sb.append(" Sequence: ").append(getSequence());
sb.append(" PartitionId: ").append(getPartitionId());
return sb.toString();
}
public String toBitString() {
StringBuffer retval = new StringBuffer();
long mask = 0x8000000000000000L;
for(int i = 0; i < 64; i++) {
if ((getTxnId() & mask) == 0) retval.append("0");
else retval.append("1");
mask >>>= 1;
}
return retval.toString();
}
public static String txnIdSeqToString(long txnId) {
return Long.toString(TxnEgo.getSequence(txnId) - TxnEgo.SEQUENCE_ZERO);
}
public static String txnIdToString(long txnId)
{
return "(" + (TxnEgo.getSequence(txnId) - TxnEgo.SEQUENCE_ZERO) + ":" +
TxnEgo.getPartitionId(txnId) + ")";
}
public static void txnIdToString(long txnId, StringBuilder sb)
{
sb.append("(").append(TxnEgo.getSequence(txnId) - TxnEgo.SEQUENCE_ZERO).append(":").append(TxnEgo.getPartitionId(txnId)).append(")");
}
public static String txnIdCollectionToString(Collection<Long> ids) {
List<String> idstrings = new ArrayList<String>();
for (Long id : ids) {
idstrings.add(txnIdToString(id));
}
// Easy hack, sort txn IDs lexically.
Collections.sort(idstrings);
StringBuilder sb = new StringBuilder();
sb.append("[");
boolean first = false;
for (String id : idstrings) {
if (!first) {
first = true;
} else {
sb.append(", ");
}
sb.append(id);
}
sb.append("]");
return sb.toString();
}
}