/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.TreeMap;
import junit.framework.TestCase;
import org.voltcore.utils.Pair;
import org.voltdb.client.BatchTimeoutOverrideType;
import org.voltdb.client.ProcedureInvocation;
import org.voltdb.client.ProcedureInvocationExtensions;
import org.voltdb.client.ProcedureInvocationType;
import org.voltdb.utils.SerializationHelper;
/**
* Tests serialization and deserialization of a cross product of
* invocations across serialization versions and different code paths.
*
*/
public class TestStoredProcedureInvocation extends TestCase {
Pair<?,?>[] procedureNames = {
new Pair<String, Boolean>("Foo", true),
new Pair<String, Boolean>("this is spinal tap", true),
new Pair<String, Boolean>("@snapshot", true),
new Pair<String, Boolean>("你好", true),
new Pair<String, Boolean>("1", true),
new Pair<String, Boolean>("", false),
new Pair<String, Boolean>(null, false)
};
Pair<?,?>[] clientHandles = {
new Pair<Long, Boolean>(-1L, true),
new Pair<Long, Boolean>(0L, true),
new Pair<Long, Boolean>(-96L, true),
new Pair<Long, Boolean>(96L, true),
new Pair<Long, Boolean>(6000000000L, true),
new Pair<Long, Boolean>(Long.MAX_VALUE, true),
new Pair<Long, Boolean>(Long.MIN_VALUE, true)
};
Pair<?,?>[] timeouts = {
new Pair<Integer, Boolean>(0, true),
new Pair<Integer, Boolean>(-1, true),
new Pair<Integer, Boolean>(-2, false),
new Pair<Integer, Boolean>(1, true),
new Pair<Integer, Boolean>(Integer.MAX_VALUE, true),
new Pair<Integer, Boolean>(Integer.MIN_VALUE, false),
new Pair<Integer, Boolean>(2000000, true),
new Pair<Integer, Boolean>(BatchTimeoutOverrideType.NO_TIMEOUT, true)
};
Pair<?,?>[] allPartitions = {
new Pair<Boolean, Boolean>(true, true),
new Pair<Boolean, Boolean>(false, true)
};
Pair<?,?>[] params = {
new Pair<Object[], Boolean>(new Object[0], true),
new Pair<Object[], Boolean>(new Object[] { 1, 2, 3 }, true),
new Pair<Object[], Boolean>(new Object[] { null }, true),
new Pair<Object[], Boolean>(new Object[] { null, null }, true),
// a very silly parameter indeed
new Pair<Object[], Boolean>(new Object[] { new TreeMap<Integer,Integer>() }, false)
};
void roundTripBuffer(boolean expectSuccess, ByteBuffer buf, String procName, long handle, int timeout, boolean allPartition) throws IOException {
StoredProcedureInvocation spi = new StoredProcedureInvocation();
spi.initFromBuffer(buf);
if (expectSuccess) {
assertEquals(handle, spi.getClientHandle());
assertEquals(timeout, spi.getBatchTimeout());
assertEquals(allPartition, spi.getAllPartition());
assertTrue(procName.equals(spi.getProcName()));
}
else {
if (handle != spi.getClientHandle()) {
return;
}
if (timeout != spi.getBatchTimeout()) {
return;
}
// no all-partition handling for failure because it can't fail
if (procName != null) {
if (!procName.equals(spi.getProcName())) {
return;
}
}
else {
if (spi.getProcName() != null) {
return;
}
}
fail();
}
}
void roundTripProcedureInvocation(boolean expectSuccess, String procName, long handle, int timeout, boolean allPartition, Object[] params) throws IOException {
// try ProcedureInvocation version
try {
ProcedureInvocation pi = new ProcedureInvocation(handle, timeout, allPartition, procName, params);
ByteBuffer buf = ByteBuffer.allocate(pi.getSerializedSize());
pi.flattenToBuffer(buf);
buf.flip();
roundTripBuffer(expectSuccess, buf, procName, handle, timeout, allPartition);
}
catch (Exception e) {
if (expectSuccess) {
e.printStackTrace();
fail();
}
}
// try StoredProcedureInvocation
try {
StoredProcedureInvocation spi = new StoredProcedureInvocation();
spi.setProcName(procName);
spi.setClientHandle(handle);
spi.setBatchTimeout(timeout);
spi.setParams(params);
spi.setAllPartition(allPartition);
ByteBuffer buf = ByteBuffer.allocate(spi.getSerializedSize());
spi.flattenToBuffer(buf);
buf.flip();
roundTripBuffer(expectSuccess, buf, procName, handle, timeout, allPartition);
}
catch (Exception e) {
if (expectSuccess) {
e.printStackTrace();
fail();
}
}
// try extra extensions sometimes using latest binary serialization (version 2)
try {
ByteBuffer buf = ByteBuffer.allocate(10000); // rando buffer big enough
buf.put(ProcedureInvocationType.VERSION2.getValue()); //Version
SerializationHelper.writeString(procName, buf);
buf.putLong(handle);
if (timeout == BatchTimeoutOverrideType.NO_TIMEOUT) {
// write two extensions
buf.put((byte) 2);
buf.put((byte) 75); // nonsense type
buf.put((byte) 0); // zero length
buf.put((byte) 93); // nonsense type
buf.put((byte) 4); // eight bytes
buf.put(new byte[8]); // zero-fill 8 bytes
}
else {
buf.put((byte) 1);
ProcedureInvocationExtensions.writeBatchTimeoutWithTypeByte(buf, timeout);
}
ParameterSet paramSet = (params != null
? ParameterSet.fromArrayWithCopy(params)
: ParameterSet.emptyParameterSet());
paramSet.flattenToBuffer(buf);
buf.flip();
// don't bother testing allPartition in binary
roundTripBuffer(expectSuccess, buf, procName, handle, timeout, false);
}
catch (Exception e) {
if (expectSuccess) {
e.printStackTrace();
fail();
}
}
// try older serialization formats
// try original if no timeout
if (timeout == BatchTimeoutOverrideType.NO_TIMEOUT) {
try {
StoredProcedureInvocation spi = new StoredProcedureInvocation();
spi.setProcName(procName);
spi.setClientHandle(handle);
spi.setParams(params);
ByteBuffer buf = ByteBuffer.allocate(spi.getSerializedSizeForOriginalVersion());
spi.flattenToBufferForOriginalVersion(buf);
buf.flip();
roundTripBuffer(expectSuccess, buf, procName, handle, timeout, false);
}
catch (Exception e) {
if (expectSuccess) {
e.printStackTrace();
fail();
}
}
}
// try v1
try {
ByteBuffer buf = ByteBuffer.allocate(10000); // rando buffer big enough
buf.put(ProcedureInvocationType.VERSION1.getValue()); //Version
if (timeout == BatchTimeoutOverrideType.NO_TIMEOUT) {
buf.put(BatchTimeoutOverrideType.NO_OVERRIDE_FOR_BATCH_TIMEOUT.getValue());
}
else {
buf.put(BatchTimeoutOverrideType.HAS_OVERRIDE_FOR_BATCH_TIMEOUT.getValue());
buf.putInt(timeout);
}
SerializationHelper.writeString(procName, buf);
buf.putLong(handle);
ParameterSet paramSet = (params != null
? ParameterSet.fromArrayWithCopy(params)
: ParameterSet.emptyParameterSet());
paramSet.flattenToBuffer(buf);
buf.flip();
// don't bother testing allPartition in older versions
roundTripBuffer(expectSuccess, buf, procName, handle, timeout, false);
}
catch (Exception e) {
if (expectSuccess) {
e.printStackTrace();
fail();
}
}
}
public void testTimeoutExtension() throws IOException {
for (Pair<?,?> procsRaw : procedureNames) {
String procName = (String) procsRaw.getFirst();
boolean procShouldWork = (Boolean) procsRaw.getSecond();
for (Pair<?,?> handlesRaw : clientHandles) {
long handle = (Long) handlesRaw.getFirst();
boolean handleShouldWork = (Boolean) handlesRaw.getSecond();
for (Pair<?,?> timeoutsRaw : timeouts) {
int timeout = (Integer) timeoutsRaw.getFirst();
boolean timeoutShouldWork = (Boolean) timeoutsRaw.getSecond();
for (Pair<?,?> allPartitionsRaw : allPartitions) {
boolean allPartition = (Boolean) allPartitionsRaw.getFirst();
// all allPartition values are valid
for (Pair<?,?> paramsRaw : params) {
Object[] params = (Object[]) paramsRaw.getFirst();
boolean paramsShouldWork = (Boolean) paramsRaw.getSecond();
System.out.printf("Trying proc:\"%s\", handle:%d, timeout:%d, allPartition:%s, params:%s\n",
String.valueOf(procName), handle, timeout, String.valueOf(allPartition), String.valueOf(params));
// try without a timeout
boolean shouldWork = procShouldWork && handleShouldWork && paramsShouldWork && timeoutShouldWork;
roundTripProcedureInvocation(shouldWork, procName, handle, timeout, allPartition, params);
}
}
}
}
}
}
}