org.voltdb.ClientResponseImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of voltdbclient Show documentation
Show all versions of voltdbclient Show documentation
VoltDB client interface libraries
/* 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 .
*/
package org.voltdb;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONString;
import org.json_voltpatches.JSONStringer;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ClientUtils;
import org.voltdb.common.Constants;
import org.voltdb.utils.SerializationHelper;
/**
* Packages up the data to be sent back to the client as a stored
* procedure response in one FastSerialziable object.
*
*/
public class ClientResponseImpl implements ClientResponse, JSONString {
private boolean setProperly = false;
private byte status = 0;
private String statusString = null;
private byte encodedStatusString[];
private byte appStatus = Byte.MIN_VALUE;
private String appStatusString = null;
private byte encodedAppStatusString[];
private VoltTable[] results = new VoltTable[0];
private Integer m_hash = null;
private int clusterRoundTripTime = 0;
private int clientRoundTripTime = 0;
private long clientRoundTripTimeNanos = 0;
// JSON KEYS FOR SERIALIZATION
static final String JSON_STATUS_KEY = "status";
static final String JSON_STATUSSTRING_KEY = "statusstring";
static final String JSON_APPSTATUS_KEY = "appstatus";
static final String JSON_APPSTATUSSTRING_KEY = "appstatusstring";
static final String JSON_RESULTS_KEY = "results";
static final String JSON_TYPE_KEY = "type";
static final String JSON_EXCEPTION_KEY = "exception";
// Error string returned when a replayed clog transaction is ignored or a replayed DR
// transaction is a duplicate
public static final String IGNORED_TRANSACTION = "Ignored replayed transaction";
/** opaque data optionally provided by and returned to the client */
private long clientHandle = -1;
public ClientResponseImpl() {}
/**
* Used in the successful procedure invocation case.
*/
public ClientResponseImpl(byte status, byte appStatus, String appStatusString, VoltTable[] results, String statusString) {
this(status, appStatus, appStatusString, results, statusString, -1);
}
/**
* Constructor used for tests and error responses.
*/
public ClientResponseImpl(byte status, VoltTable[] results, String statusString) {
this(status, ClientResponse.UNINITIALIZED_APP_STATUS_CODE, null, results, statusString, -1);
}
/**
* Another constructor for test and error responses
*/
public ClientResponseImpl(byte status, VoltTable[] results, String statusString, long handle) {
this(status, ClientResponse.UNINITIALIZED_APP_STATUS_CODE, null, results, statusString, handle);
}
public ClientResponseImpl(byte status, byte appStatus, String appStatusString, VoltTable[] results, String statusString, long handle) {
this.appStatus = appStatus;
this.appStatusString = appStatusString;
setResults(status, results, statusString);
clientHandle = handle;
}
private void setResults(byte status, VoltTable[] results, String statusString) {
assert results != null;
for (VoltTable result : results) {
// null values are not permitted in results. If there is one, it will cause an
// exception in writeExternal. This throws the exception sooner.
assert result != null;
}
this.status = status;
this.results = results;
this.statusString = statusString;
this.setProperly = true;
}
public void setHash(Integer hash) {
m_hash = hash;
}
@Override
public byte getStatus() {
return status;
}
@Override
public VoltTable[] getResults() {
return results;
}
@Override
public String getStatusString() {
return statusString;
}
public void setClientHandle(long aHandle) {
clientHandle = aHandle;
}
public long getClientHandle() {
return clientHandle;
}
public Integer getHash() {
return m_hash;
}
public void initFromBuffer(ByteBuffer buf) throws IOException {
buf.get();//Skip version byte
clientHandle = buf.getLong();
byte presentFields = buf.get();
status = buf.get();
if ((presentFields & (1 << 5)) != 0) {
statusString = SerializationHelper.getString(buf);
} else {
statusString = null;
}
appStatus = buf.get();
if ((presentFields & (1 << 7)) != 0) {
appStatusString = SerializationHelper.getString(buf);
} else {
appStatusString = null;
}
clusterRoundTripTime = buf.getInt();
if ((presentFields & (1 << 6)) != 0) {
throw new RuntimeException("Use of deprecated exception in Client Response serialization.");
}
if ((presentFields & (1 << 4)) != 0) {
m_hash = buf.getInt();
} else {
m_hash = null;
}
int tableCount = buf.getShort();
if (tableCount < 0) {
throw new IOException("Table count is negative: " + tableCount);
}
results = new VoltTable[tableCount];
for (int i = 0; i < tableCount; i++) {
int tableSize = buf.getInt();
final int originalLimit = buf.limit();
buf.limit(buf.position() + tableSize);
final ByteBuffer slice = buf.slice();
buf.position(buf.position() + tableSize);
buf.limit(originalLimit);
results[i] = new VoltTable(slice, false);
}
setProperly = true;
}
public int getSerializedSize() {
int msgsize = 1 // version
+ 8 // clientHandle
+ 1 // present fields
+ 1 // status
+ 1 // app status
+ 4 // cluster roundtrip time
+ 2; // number of result tables
if (appStatusString != null) {
encodedAppStatusString = appStatusString.getBytes(Constants.UTF8ENCODING);
msgsize += encodedAppStatusString.length + 4;
}
if (statusString != null) {
encodedStatusString = statusString.getBytes(Constants.UTF8ENCODING);
msgsize += encodedStatusString.length + 4;
}
if (m_hash != null) {
msgsize += 4;
}
for (VoltTable vt : results) {
msgsize += vt.getSerializedSize();
}
return msgsize;
}
/**
* @return buf to allow call chaining.
*/
public ByteBuffer flattenToBuffer(ByteBuffer buf) {
assert setProperly;
buf.put((byte)0); //version
buf.putLong(clientHandle);
byte presentFields = 0;
if (appStatusString != null) {
presentFields |= 1 << 7;
}
if (statusString != null) {
presentFields |= 1 << 5;
}
if (m_hash != null) {
presentFields |= 1 << 4;
}
buf.put(presentFields);
buf.put(status);
if (statusString != null) {
buf.putInt(encodedStatusString.length);
buf.put(encodedStatusString);
}
buf.put(appStatus);
if (appStatusString != null) {
buf.putInt(encodedAppStatusString.length);
buf.put(encodedAppStatusString);
}
buf.putInt(clusterRoundTripTime);
if (m_hash != null) {
buf.putInt(m_hash.intValue());
}
buf.putShort((short) results.length);
for (VoltTable vt : results)
{
vt.flattenToBuffer(buf);
}
return buf;
}
@Override
public int getClusterRoundtrip() {
return clusterRoundTripTime;
}
public void setClusterRoundtrip(int time) {
clusterRoundTripTime = time;
}
@Override
public int getClientRoundtrip() {
return clientRoundTripTime;
}
@Override
public long getClientRoundtripNanos() {
return clientRoundTripTimeNanos;
}
public void setClientRoundtrip(long timeNanos) {
clientRoundTripTimeNanos = timeNanos;
clientRoundTripTime = (int)TimeUnit.NANOSECONDS.toMillis(timeNanos);
}
@Override
public byte getAppStatus() {
return appStatus;
}
@Override
public String getAppStatusString() {
return appStatusString;
}
public boolean isTransactionallySuccessful() {
return isTransactionallySuccessful(status);
}
public static boolean isTransactionallySuccessful(byte status) {
return (status == SUCCESS) || (status == OPERATIONAL_FAILURE);
}
@Override
public String toJSONString() {
JSONStringer js = new JSONStringer();
try {
js.object();
js.keySymbolValuePair(JSON_STATUS_KEY, status);
js.keySymbolValuePair(JSON_APPSTATUS_KEY, appStatus);
js.keySymbolValuePair(JSON_STATUSSTRING_KEY, statusString);
js.keySymbolValuePair(JSON_APPSTATUSSTRING_KEY, appStatusString);
js.key(JSON_RESULTS_KEY);
js.array();
for (VoltTable o : results) {
js.value(o);
}
js.endArray();
js.endObject();
}
catch (JSONException e) {
e.printStackTrace();
throw new RuntimeException("Failed to serialize a parameter set to JSON.", e);
}
return js.toString();
}
/**
* @return MD5 hash as int of the tables in the result. Only hashes first bits of big results.
*/
public int getHashOfTableResults() {
try {
long cheesyChecksum = 0;
for (int i = 0; i < results.length; ++i) {
cheesyChecksum += ClientUtils.cheesyBufferCheckSum(results[i].m_buffer);
}
return (int)cheesyChecksum;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* Take the perfectly good results and convert them to a single long value
* that stores the hash for determinism.
*
* This presumes the DR agent has no need for results. This probably saves
* some small amount of bandwidth. The other proposed idea was using the status
* string to hold the sql hash. Tossup... this seemed slightly more performant,
* but also a bit icky.
*/
public void convertResultsToHashForDeterminism() {
int hash = m_hash == null ? 0 : m_hash;
VoltTable t = new VoltTable(new VoltTable.ColumnInfo("", VoltType.INTEGER));
t.addRow(hash);
results = new VoltTable[] { t };
}
public void dropResultTable() {
results = new VoltTable[] {};
}
}