/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.transport.messages;
import java.util.*;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ResultSet;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.cql3.statements.ParsedStatement;
import org.apache.cassandra.transport.*;
import org.apache.cassandra.thrift.CqlPreparedResult;
import org.apache.cassandra.thrift.CqlResult;
import org.apache.cassandra.thrift.CqlResultType;
import org.apache.cassandra.utils.MD5Digest;
public abstract class ResultMessage extends Message.Response
{
public static final Message.Codec<ResultMessage> codec = new Message.Codec<ResultMessage>()
{
public ResultMessage decode(ChannelBuffer body, int version)
{
Kind kind = Kind.fromId(body.readInt());
return kind.subcodec.decode(body, version);
}
public void encode(ResultMessage msg, ChannelBuffer dest, int version)
{
dest.writeInt(msg.kind.id);
msg.kind.subcodec.encode(msg, dest, version);
}
public int encodedSize(ResultMessage msg, int version)
{
return 4 + msg.kind.subcodec.encodedSize(msg, version);
}
};
public enum Kind
{
VOID (1, Void.subcodec),
ROWS (2, Rows.subcodec),
SET_KEYSPACE (3, SetKeyspace.subcodec),
PREPARED (4, Prepared.subcodec),
SCHEMA_CHANGE(5, SchemaChange.subcodec);
public final int id;
public final Message.Codec<ResultMessage> subcodec;
private static final Kind[] ids;
static
{
int maxId = -1;
for (Kind k : Kind.values())
maxId = Math.max(maxId, k.id);
ids = new Kind[maxId + 1];
for (Kind k : Kind.values())
{
if (ids[k.id] != null)
throw new IllegalStateException("Duplicate kind id");
ids[k.id] = k;
}
}
private Kind(int id, Message.Codec<ResultMessage> subcodec)
{
this.id = id;
this.subcodec = subcodec;
}
public static Kind fromId(int id)
{
Kind k = ids[id];
if (k == null)
throw new ProtocolException(String.format("Unknown kind id %d in RESULT message", id));
return k;
}
}
public final Kind kind;
protected ResultMessage(Kind kind)
{
super(Message.Type.RESULT);
this.kind = kind;
}
public abstract CqlResult toThriftResult();
public static class Void extends ResultMessage
{
// Even though we have no specific information here, don't make a
// singleton since as each message it has in fact a streamid and connection.
public Void()
{
super(Kind.VOID);
}
public static final Message.Codec<ResultMessage> subcodec = new Message.Codec<ResultMessage>()
{
public ResultMessage decode(ChannelBuffer body, int version)
{
return new Void();
}
public void encode(ResultMessage msg, ChannelBuffer dest, int version)
{
assert msg instanceof Void;
}
public int encodedSize(ResultMessage msg, int version)
{
return 0;
}
};
public CqlResult toThriftResult()
{
return new CqlResult(CqlResultType.VOID);
}
@Override
public String toString()
{
return "EMPTY RESULT";
}
}
public static class SetKeyspace extends ResultMessage
{
public final String keyspace;
public SetKeyspace(String keyspace)
{
super(Kind.SET_KEYSPACE);
this.keyspace = keyspace;
}
public static final Message.Codec<ResultMessage> subcodec = new Message.Codec<ResultMessage>()
{
public ResultMessage decode(ChannelBuffer body, int version)
{
String keyspace = CBUtil.readString(body);
return new SetKeyspace(keyspace);
}
public void encode(ResultMessage msg, ChannelBuffer dest, int version)
{
assert msg instanceof SetKeyspace;
CBUtil.writeString(((SetKeyspace)msg).keyspace, dest);
}
public int encodedSize(ResultMessage msg, int version)
{
assert msg instanceof SetKeyspace;
return CBUtil.sizeOfString(((SetKeyspace)msg).keyspace);
}
};
public CqlResult toThriftResult()
{
return new CqlResult(CqlResultType.VOID);
}
@Override
public String toString()
{
return "RESULT set keyspace " + keyspace;
}
}
public static class Rows extends ResultMessage
{
public static final Message.Codec<ResultMessage> subcodec = new Message.Codec<ResultMessage>()
{
public ResultMessage decode(ChannelBuffer body, int version)
{
return new Rows(ResultSet.codec.decode(body, version));
}
public void encode(ResultMessage msg, ChannelBuffer dest, int version)
{
assert msg instanceof Rows;
Rows rowMsg = (Rows)msg;
ResultSet.codec.encode(rowMsg.result, dest, version);
}
public int encodedSize(ResultMessage msg, int version)
{
assert msg instanceof Rows;
Rows rowMsg = (Rows)msg;
return ResultSet.codec.encodedSize(rowMsg.result, version);
}
};
public final ResultSet result;
public Rows(ResultSet result)
{
super(Kind.ROWS);
this.result = result;
}
public CqlResult toThriftResult()
{
return result.toThriftResult();
}
@Override
public String toString()
{
return "ROWS " + result;
}
}
public static class Prepared extends ResultMessage
{
public static final Message.Codec<ResultMessage> subcodec = new Message.Codec<ResultMessage>()
{
public ResultMessage decode(ChannelBuffer body, int version)
{
MD5Digest id = MD5Digest.wrap(CBUtil.readBytes(body));
ResultSet.Metadata metadata = ResultSet.Metadata.codec.decode(body, version);
ResultSet.Metadata resultMetadata = ResultSet.Metadata.EMPTY;
if (version > 1)
resultMetadata = ResultSet.Metadata.codec.decode(body, version);
return new Prepared(id, -1, metadata, resultMetadata);
}
public void encode(ResultMessage msg, ChannelBuffer dest, int version)
{
assert msg instanceof Prepared;
Prepared prepared = (Prepared)msg;
assert prepared.statementId != null;
CBUtil.writeBytes(prepared.statementId.bytes, dest);
ResultSet.Metadata.codec.encode(prepared.metadata, dest, version);
if (version > 1)
ResultSet.Metadata.codec.encode(prepared.resultMetadata, dest, version);
}
public int encodedSize(ResultMessage msg, int version)
{
assert msg instanceof Prepared;
Prepared prepared = (Prepared)msg;
assert prepared.statementId != null;
int size = 0;
size += CBUtil.sizeOfBytes(prepared.statementId.bytes);
size += ResultSet.Metadata.codec.encodedSize(prepared.metadata, version);
if (version > 1)
size += ResultSet.Metadata.codec.encodedSize(prepared.resultMetadata, version);
return size;
}
};
public final MD5Digest statementId;
public final ResultSet.Metadata metadata;
public final ResultSet.Metadata resultMetadata;
// statement id for CQL-over-thrift compatibility. The binary protocol ignore that.
private final int thriftStatementId;
public Prepared(MD5Digest statementId, ParsedStatement.Prepared prepared)
{
this(statementId, -1, new ResultSet.Metadata(prepared.boundNames), extractResultMetadata(prepared.statement));
}
public static Prepared forThrift(int statementId, List<ColumnSpecification> names)
{
return new Prepared(null, statementId, new ResultSet.Metadata(names), ResultSet.Metadata.EMPTY);
}
private Prepared(MD5Digest statementId, int thriftStatementId, ResultSet.Metadata metadata, ResultSet.Metadata resultMetadata)
{
super(Kind.PREPARED);
this.statementId = statementId;
this.thriftStatementId = thriftStatementId;
this.metadata = metadata;
this.resultMetadata = resultMetadata;
}
private static ResultSet.Metadata extractResultMetadata(CQLStatement statement)
{
if (!(statement instanceof SelectStatement))
return ResultSet.Metadata.EMPTY;
return ((SelectStatement)statement).getResultMetadata();
}
public CqlResult toThriftResult()
{
throw new UnsupportedOperationException();
}
public CqlPreparedResult toThriftPreparedResult()
{
List<String> namesString = new ArrayList<String>(metadata.names.size());
List<String> typesString = new ArrayList<String>(metadata.names.size());
for (ColumnSpecification name : metadata.names)
{
namesString.add(name.toString());
typesString.add(name.type.toString());
}
return new CqlPreparedResult(thriftStatementId, metadata.names.size()).setVariable_types(typesString).setVariable_names(namesString);
}
@Override
public String toString()
{
return "RESULT PREPARED " + statementId + " " + metadata + " (resultMetadata=" + resultMetadata + ")";
}
}
public static class SchemaChange extends ResultMessage
{
public enum Change { CREATED, UPDATED, DROPPED }
public final Change change;
public final String keyspace;
public final String columnFamily;
public SchemaChange(Change change, String keyspace)
{
this(change, keyspace, "");
}
public SchemaChange(Change change, String keyspace, String columnFamily)
{
super(Kind.SCHEMA_CHANGE);
this.change = change;
this.keyspace = keyspace;
this.columnFamily = columnFamily;
}
public static final Message.Codec<ResultMessage> subcodec = new Message.Codec<ResultMessage>()
{
public ResultMessage decode(ChannelBuffer body, int version)
{
Change change = CBUtil.readEnumValue(Change.class, body);
String keyspace = CBUtil.readString(body);
String columnFamily = CBUtil.readString(body);
return new SchemaChange(change, keyspace, columnFamily);
}
public void encode(ResultMessage msg, ChannelBuffer dest, int version)
{
assert msg instanceof SchemaChange;
SchemaChange scm = (SchemaChange)msg;
CBUtil.writeEnumValue(scm.change, dest);
CBUtil.writeString(scm.keyspace, dest);
CBUtil.writeString(scm.columnFamily, dest);
}
public int encodedSize(ResultMessage msg, int version)
{
assert msg instanceof SchemaChange;
SchemaChange scm = (SchemaChange)msg;
int size = 0;
size += CBUtil.sizeOfEnumValue(scm.change);
size += CBUtil.sizeOfString(scm.keyspace);
size += CBUtil.sizeOfString(scm.columnFamily);
return size;
}
};
public CqlResult toThriftResult()
{
return new CqlResult(CqlResultType.VOID);
}
@Override
public String toString()
{
return "RESULT schema change " + change + " on " + keyspace + (columnFamily.isEmpty() ? "" : "." + columnFamily);
}
}
}