/* 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.nio.ByteBuffer;
import org.voltcore.logging.VoltLogger;
import org.voltdb.HybridCrc32;
/**
* This class expands the determinism hash with an array
* of hashes. For speed and memory reasons, this class is a simple int
* array, even though it's really three ints and then an array of int pairs.
*
* The first int is an overall hash, equivalent to the old hash value, but
* also including the catalog version of the procedure.
*
* Then catalog version (not hashed), then the number of DML statements run
* by the proc (not hashed).
*
* Then a list of pairs, up to MAX_STATEMENTS_WITH_DETAIL long.
* a) The hash of the SQL text
* b) The hash of the parameter values for that SQL statement.
*
* This array is passed around in ClientResponseImpl internally, where we
* used to pass a single long.
*
* It is not yet used for replay or command logging.
*
* Use the static helper function in this class to check two arrays and print
* helpful output.
*/
public class DeterminismHash {
protected static final VoltLogger m_tmLog = new VoltLogger("TM");
// HEADER IS:
// 1) total hash
// 2) catalog version
// 3) hash count (each statement has two hashes: SQL hash and parameter hash)
public final static int HEADER_OFFSET = 3;
public final static int MAX_HASHES_COUNT = Integer.getInteger("MAX_STATEMENTS_WITH_DETAIL", 32) * 2;
int m_catalogVersion = 0;
int m_hashCount = 0;
final int[] m_hashes = new int[MAX_HASHES_COUNT + HEADER_OFFSET];
protected final HybridCrc32 m_inputCRC = new HybridCrc32();
public void reset(int catalogVersion) {
m_catalogVersion = catalogVersion;
m_inputCRC.reset();
m_hashCount = 0;
}
/**
* Serialize the running hashes to an array and complete the overall
* hash for the first int value in the array.
*/
public int[] get() {
int includedHashes = Math.min(m_hashCount, MAX_HASHES_COUNT);
int[] retval = new int[includedHashes + HEADER_OFFSET];
System.arraycopy(m_hashes, 0, retval, HEADER_OFFSET, includedHashes);
m_inputCRC.update(m_hashCount);
m_inputCRC.update(m_catalogVersion);
retval[0] = (int) m_inputCRC.getValue();
retval[1] = m_catalogVersion;
retval[2] = m_hashCount;
return retval;
}
/**
* Update the overall hash. Add a pair of ints to the array
* if the size isn't too large.
*/
public void offerStatement(int stmtHash, int offset, ByteBuffer psetBuffer) {
m_inputCRC.update(stmtHash);
m_inputCRC.updateFromPosition(offset, psetBuffer);
if (m_hashCount < MAX_HASHES_COUNT) {
m_hashes[m_hashCount] = stmtHash;
m_hashes[m_hashCount + 1] = (int) m_inputCRC.getValue();
}
m_hashCount += 2;
}
/**
* Compare two hash arrays return true if the same.
*
* For now, just compares first integer value in array.
*/
public static boolean compareHashes(int[] leftHashes, int[] rightHashes) {
assert(leftHashes != null);
assert(rightHashes != null);
assert(leftHashes.length >= 3);
assert(rightHashes.length >= 3);
return leftHashes[0] == rightHashes[0];
}
/**
* Log the contents of the hash array
*/
public static String description(int[] hashes) {
assert(hashes != null);
assert(hashes.length >= 3);
StringBuilder sb = new StringBuilder();
sb.append("Full Hash ").append(hashes[0]);
sb.append(", Catalog Version ").append(hashes[1]);
sb.append(", Statement Count ").append(hashes[2] / 2);
int includedHashes = Math.min(hashes[2], MAX_HASHES_COUNT);
for (int i = HEADER_OFFSET; i < HEADER_OFFSET + includedHashes; i += 2) {
sb.append("\n Ran Statement ").append(hashes[i]);
sb.append(" with Parameters ").append(hashes[i + 1]);
}
if (hashes[2] > MAX_HASHES_COUNT) {
sb.append("\n Additional SQL statements truncated");
}
return sb.toString();
}
}