/* 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.planner;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import org.voltdb.VoltType;
import org.voltdb.common.Constants;
import org.voltdb.compiler.AdHocPlannedStatement;
/**
* CorePlan is an immutable representation of a SQL execution plan.
* It refers to a parameterized statement, and so it could apply to
* multiple SQL literal strings before constants have been pulled
* out. It is usually embedded in an {@link AdHocPlannedStatement}
* together with extracted parameter values, a SQL literal and more.
*/
public class CorePlan {
/** The plan itself. Collector can be null. */
public final byte[] aggregatorFragment;
public final byte[] collectorFragment;
/** hashes */
public final byte[] aggregatorHash;
public final byte[] collectorHash;
/**
* If true, divide the number of tuples changed
* by the number of partitions, as the number will
* be the sum of tuples changed on all replicas.
*/
public final boolean isReplicatedTableDML;
/** Does the statement write? */
public final boolean readOnly;
/** What SHA-1 hash of the catalog is this plan good for? */
private final byte[] catalogHash;
/** What are the types of the parameters this plan accepts? */
public final VoltType[] parameterTypes;
/**
* If single partition, which of the parameters can be used
* to determine the correct partition to route the transaction?
* (Note, not serialized because it's not needed at the ExecutionSite.)
*/
private int partitioningParamIndex = -1;
private Object partitioningParamValue = null;
/**
* Constructor from QueryPlanner output.
*
* @param plan The output from the QueryPlanner.
* @param catalogHash The sha-1 hash of the catalog this plan was generated against.
*/
public CorePlan(CompiledPlan plan, byte[] catalogHash) {
aggregatorFragment = CompiledPlan.bytesForPlan(plan.rootPlanGraph);
collectorFragment = CompiledPlan.bytesForPlan(plan.subPlanGraph);
// compute the hashes
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
System.exit(-1); // JVM is broken
}
md.update(aggregatorFragment);
aggregatorHash = md.digest();
if (collectorFragment != null) {
md.reset();
md.update(collectorFragment);
collectorHash = md.digest();
}
else {
collectorHash = null;
}
isReplicatedTableDML = plan.replicatedTableDML;
this.catalogHash = catalogHash;
parameterTypes = plan.parameterTypes();
readOnly = plan.isReadOnly();
}
/***
* Constructor, mainly for deserialization (but also testing)
*
* @param aggregatorFragment planned aggregator fragment
* @param collectorFragment planned collector fragment
* @param isReplicatedTableDML replication flag
* @param isReadOnly does it write
* @param paramTypes parameter type array
* @param catalogHash SHA-1 hash of catalog
*/
public CorePlan(byte[] aggregatorFragment,
byte[] collectorFragment,
byte[] aggregatorHash,
byte[] collectorHash,
boolean isReplicatedTableDML,
boolean isReadOnly,
VoltType[] paramTypes,
byte[] catalogHash)
{
this.aggregatorFragment = aggregatorFragment;
this.collectorFragment = collectorFragment;
this.aggregatorHash = aggregatorHash;
this.collectorHash = collectorHash;
this.isReplicatedTableDML = isReplicatedTableDML;
this.readOnly = isReadOnly;
this.parameterTypes = paramTypes;
this.catalogHash = catalogHash;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("COMPILED PLAN {\n");
sb.append(" ONE: ").append(aggregatorFragment == null ?
"null" : new String(aggregatorFragment, Constants.UTF8ENCODING)).append("\n");
sb.append(" ALL: ").append(collectorFragment == null ?
"null" : new String(collectorFragment, Constants.UTF8ENCODING)).append("\n");
sb.append(" RTD: ").append(isReplicatedTableDML ? "true" : "false").append("\n");
sb.append("}");
return sb.toString();
}
public int getSerializedSize() {
// plan fragments first
int size = 4 + aggregatorFragment.length + 20; // hash is 20b
if (collectorFragment != null) {
size += 4 + collectorFragment.length + 20; // hash is 20b
}
else {
size += 4;
}
size += 2; // booleans
size += 20; // catalog hash SHA-1 is 20b
size += 2; // params count
size += parameterTypes.length;
return size;
}
public void flattenToBuffer(ByteBuffer buf) throws IOException {
// plan fragments first
buf.putInt(aggregatorFragment.length);
buf.put(aggregatorFragment);
buf.put(aggregatorHash);
if (collectorFragment == null) {
buf.putInt(-1);
}
else {
buf.putInt(collectorFragment.length);
buf.put(collectorFragment);
buf.put(collectorHash);
}
// booleans
buf.put((byte) (isReplicatedTableDML ? 1 : 0));
buf.put((byte) (readOnly ? 1 : 0));
// catalog hash
buf.put(catalogHash);
// param types
buf.putShort((short) parameterTypes.length);
for (VoltType type : parameterTypes) {
buf.put(type.getValue());
}
}
public static CorePlan fromBuffer(ByteBuffer buf) throws IOException {
// plan fragments first
byte[] aggregatorFragment = new byte[buf.getInt()];
buf.get(aggregatorFragment);
byte[] aggregatorHash = new byte[20]; // sha-1 hash is 20b
buf.get(aggregatorHash);
byte[] collectorFragment = null;
byte[] collectorHash = null;
int cflen = buf.getInt();
if (cflen >= 0) {
collectorFragment = new byte[cflen];
buf.get(collectorFragment);
collectorHash = new byte[20]; // sha-1 hash is 20b
buf.get(collectorHash);
}
// booleans
boolean isReplicatedTableDML = buf.get() == 1;
boolean isReadOnly = buf.get() == 1;
// catalog hash
byte[] catalogHash = new byte[20]; // Catalog sha-1 hash is 20b
buf.get(catalogHash);
// param types
short paramCount = buf.getShort();
VoltType[] paramTypes = new VoltType[paramCount];
for (int i = 0; i < paramCount; ++i) {
paramTypes[i] = VoltType.get(buf.get());
}
return new CorePlan(
aggregatorFragment,
collectorFragment,
aggregatorHash,
collectorHash,
isReplicatedTableDML,
isReadOnly,
paramTypes,
catalogHash);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*
* Used mainly for debugging and for assertions.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CorePlan)) {
return false;
}
CorePlan other = (CorePlan) obj;
if (!Arrays.equals(aggregatorHash, other.aggregatorHash)) {
return false;
}
if (!Arrays.equals(collectorHash, other.collectorHash)) {
return false;
}
if (!Arrays.equals(parameterTypes, other.parameterTypes)) {
return false;
}
if (isReplicatedTableDML != other.isReplicatedTableDML) {
return false;
}
if (readOnly != other.readOnly) {
return false;
}
if (!Arrays.equals(catalogHash, other.catalogHash)) {
return false;
}
if (partitioningParamIndex != other.partitioningParamIndex) {
return false;
}
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
public void setPartitioningParamIndex(int partitioningParamIndex) {
this.partitioningParamIndex = partitioningParamIndex;
}
public int getPartitioningParamIndex() {
return partitioningParamIndex;
}
public void setPartitioningParamValue(Object partitioningParamValue) {
this.partitioningParamValue = partitioningParamValue;
}
public Object getPartitioningParamValue() {
return partitioningParamValue;
}
public VoltType getPartitioningParamType() {
if (partitioningParamIndex < 0 || partitioningParamIndex >= parameterTypes.length) {
return VoltType.NULL;
}
return parameterTypes[partitioningParamIndex];
}
public boolean wasPlannedAgainstHash(byte[] catalogHash) {
return Arrays.equals(catalogHash, this.catalogHash);
}
}