/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.store.format.protobuf;
import com.foundationdb.server.error.*;
import com.foundationdb.server.rowdata.ConversionHelperBigDecimal;
import com.foundationdb.server.service.blob.BlobRef;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.aksql.aktypes.AkBlob;
import com.foundationdb.server.types.aksql.aktypes.AkBool;
import com.foundationdb.server.types.common.BigDecimalWrapper;
import com.foundationdb.server.types.common.BigDecimalWrapperImpl;
import com.foundationdb.server.types.common.types.DecimalAttribute;
import com.foundationdb.server.types.common.types.TBigDecimal;
import com.foundationdb.server.types.mcompat.mtypes.MApproximateNumber;
import com.foundationdb.server.types.mcompat.mtypes.MBinary;
import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime;
import com.foundationdb.server.types.mcompat.mtypes.MNumeric;
import com.foundationdb.server.types.mcompat.mtypes.MString;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.util.AkibanAppender;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.ByteString;
import com.google.protobuf.DynamicMessage;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
public abstract class ProtobufRowConversion
{
/** Get the Protobuf field type. */
public abstract Type getType();
// TODO: Should use ValueTarget, but NewRow does not currently.
protected abstract Object valueFromRaw(Object raw);
protected abstract Object rawFromValue(ValueSource value);
/** Get the field as a suitable Object. */
public Object getValue(DynamicMessage message, FieldDescriptor field) {
Object raw = message.getField(field);
if (raw == null) {
return null;
}
else {
return valueFromRaw(raw);
}
}
/** Set the field from the given value. */
public void setValue(DynamicMessage.Builder builder, FieldDescriptor field,
ValueSource value) {
if (!value.isNull()) {
builder.setField(field, rawFromValue(value));
}
}
public int getDecimalScale() {
return -1;
}
public static ProtobufRowConversion forTInstance(TInstance type) {
TClass tclass = TInstance.tClass(type);
if (tclass instanceof TBigDecimal) {
int precision = type.attribute(DecimalAttribute.PRECISION);
if (precision < 19) { // log10(Long.MAX_VALUE) = 18.965
return new DecimalAsLong(type);
}
else {
return new DecimalAsBytes(type);
}
}
else {
return TYPE_MAPPING.get(tclass);
}
}
private static final Map<TClass,ProtobufRowConversion> TYPE_MAPPING = new HashMap<>();
static {
TYPE_MAPPING.put(AkBool.INSTANCE,
new CompatibleConversion(Type.TYPE_BOOL, UnderlyingType.BOOL));
TYPE_MAPPING.put(MNumeric.BIGINT,
new IntegerConversion(Type.TYPE_SINT64, UnderlyingType.INT_64));
TYPE_MAPPING.put(MNumeric.BIGINT_UNSIGNED,
new IntegerConversion(Type.TYPE_UINT64, UnderlyingType.INT_64));
TYPE_MAPPING.put(MApproximateNumber.DOUBLE,
new CompatibleConversion(Type.TYPE_DOUBLE, UnderlyingType.DOUBLE));
TYPE_MAPPING.put(MApproximateNumber.DOUBLE_UNSIGNED,
TYPE_MAPPING.get(MApproximateNumber.DOUBLE));
TYPE_MAPPING.put(MApproximateNumber.FLOAT,
new CompatibleConversion(Type.TYPE_FLOAT, UnderlyingType.FLOAT));
TYPE_MAPPING.put(MApproximateNumber.FLOAT_UNSIGNED,
TYPE_MAPPING.get(MApproximateNumber.FLOAT));
TYPE_MAPPING.put(MNumeric.INT,
new IntegerConversion(Type.TYPE_SINT32, UnderlyingType.INT_32));
TYPE_MAPPING.put(MNumeric.INT_UNSIGNED,
new IntegerConversion(Type.TYPE_UINT32, UnderlyingType.INT_32));
TYPE_MAPPING.put(MNumeric.MEDIUMINT,
new IntegerConversion(Type.TYPE_SINT32, UnderlyingType.INT_32));
TYPE_MAPPING.put(MNumeric.MEDIUMINT_UNSIGNED,
new IntegerConversion(Type.TYPE_UINT32, UnderlyingType.INT_32));
TYPE_MAPPING.put(MNumeric.SMALLINT,
new IntegerConversion(Type.TYPE_SINT32, UnderlyingType.INT_16));
TYPE_MAPPING.put(MNumeric.SMALLINT_UNSIGNED,
new IntegerConversion(Type.TYPE_UINT32, UnderlyingType.INT_16));
TYPE_MAPPING.put(MNumeric.TINYINT,
new IntegerConversion(Type.TYPE_SINT32, UnderlyingType.INT_8));
TYPE_MAPPING.put(MNumeric.TINYINT_UNSIGNED,
new IntegerConversion(Type.TYPE_UINT32, UnderlyingType.INT_8));
TYPE_MAPPING.put(MDateAndTime.DATE,
new DateConversion());
TYPE_MAPPING.put(MDateAndTime.DATETIME,
new DatetimeConversion());
TYPE_MAPPING.put(MDateAndTime.YEAR,
new YearConversion());
TYPE_MAPPING.put(MDateAndTime.TIME,
new TimeConversion());
TYPE_MAPPING.put(MDateAndTime.TIMESTAMP,
new IntegerConversion(Type.TYPE_UINT32, UnderlyingType.INT_32));
TYPE_MAPPING.put(MBinary.VARBINARY,
new BytesConversion());
TYPE_MAPPING.put(MBinary.BINARY,
TYPE_MAPPING.get(MBinary.VARBINARY));
TYPE_MAPPING.put(AkBlob.INSTANCE,
new BlobConversion());
TYPE_MAPPING.put(MString.VARCHAR,
new CompatibleConversion(Type.TYPE_STRING, UnderlyingType.STRING));
TYPE_MAPPING.put(MString.CHAR,
TYPE_MAPPING.get(MString.VARCHAR));
TYPE_MAPPING.put(MString.TINYTEXT,
TYPE_MAPPING.get(MString.VARCHAR));
TYPE_MAPPING.put(MString.TEXT,
TYPE_MAPPING.get(MString.VARCHAR));
TYPE_MAPPING.put(MString.MEDIUMTEXT,
TYPE_MAPPING.get(MString.VARCHAR));
TYPE_MAPPING.put(MString.LONGTEXT,
TYPE_MAPPING.get(MString.VARCHAR));
}
static final class CompatibleConversion extends ProtobufRowConversion {
private final Type type;
private final UnderlyingType underlying;
public CompatibleConversion(Type type, UnderlyingType underlying) {
this.type = type;
this.underlying = underlying;
}
@Override
public Type getType() {
return type;
}
@Override
protected Object valueFromRaw(Object raw) {
return raw;
}
@Override
protected Object rawFromValue(ValueSource value) {
switch (underlying) {
case BOOL:
return value.getBoolean();
case FLOAT:
return value.getFloat();
case DOUBLE:
return value.getDouble();
case STRING:
default:
return value.getString();
}
}
}
static final class IntegerConversion extends ProtobufRowConversion {
private final Type type;
private final UnderlyingType underlying;
public IntegerConversion(Type type, UnderlyingType underlying) {
this.type = type;
this.underlying = underlying;
}
@Override
public Type getType() {
return type;
}
@Override
protected Object valueFromRaw(Object raw) {
Number n = (Number)raw;
switch (underlying) {
case INT_8:
return n.byteValue();
case UINT_16:
return (char)n.shortValue();
case INT_16:
return n.shortValue();
case INT_32:
return n.intValue();
case INT_64:
return n.longValue();
default:
return n;
}
}
@Override
protected Object rawFromValue(ValueSource value) {
long lval;
switch (underlying) {
case INT_8:
lval = value.getInt8();
break;
case UINT_16:
lval = value.getUInt16();
break;
case INT_16:
lval = value.getInt16();
break;
case INT_32:
lval = value.getInt32();
break;
case INT_64:
default:
lval = value.getInt64();
}
switch (type) {
case TYPE_FIXED32:
case TYPE_INT32:
case TYPE_SINT32:
case TYPE_UINT32:
return (int)lval;
case TYPE_FIXED64:
case TYPE_INT64:
case TYPE_SINT64:
case TYPE_UINT64:
default:
return lval;
}
}
}
static final class BytesConversion extends ProtobufRowConversion {
@Override
public Type getType() {
return Type.TYPE_BYTES;
}
@Override
protected Object valueFromRaw(Object raw) {
return ((ByteString)raw).toByteArray();
}
@Override
protected Object rawFromValue(ValueSource value) {
return ByteString.copyFrom(value.getBytes());
}
}
static final class BlobConversion extends ProtobufRowConversion {
@Override
public Type getType() {
return Type.TYPE_BYTES;
}
@Override
protected Object valueFromRaw(Object raw) {
return new BlobRef(((ByteString)raw).toByteArray());
}
@Override
protected Object rawFromValue(ValueSource value) {
Object bl = value.getObject();
if (bl instanceof BlobRef) {
BlobRef blob = (BlobRef)bl;
return ByteString.copyFrom(blob.getValue());
}
throw new AkibanInternalException("bl must be a blob object");
}
}
static final class DateConversion extends ProtobufRowConversion {
@Override
public Type getType() {
return Type.TYPE_STRING;
}
@Override
protected Object valueFromRaw(Object raw) {
String date = (String)raw;
return MDateAndTime.parseAndEncodeDate(date);
}
@Override
protected Object rawFromValue(ValueSource value) {
int date = value.getInt32();
return MDateAndTime.dateToString(date);
}
}
static final class DatetimeConversion extends ProtobufRowConversion {
@Override
public Type getType() {
return Type.TYPE_STRING;
}
@Override
protected Object valueFromRaw(Object raw) {
String datetime = (String)raw;
return MDateAndTime.parseAndEncodeDateTime(datetime);
}
@Override
protected Object rawFromValue(ValueSource value) {
long datetime = value.getInt64();
return MDateAndTime.dateTimeToString(datetime);
}
}
static final class TimeConversion extends ProtobufRowConversion {
@Override
public Type getType() {
return Type.TYPE_SINT32;
}
@Override
protected Object valueFromRaw(Object raw) {
int secs = (Integer)raw;
boolean negate = false;
if (secs < 0) {
secs = - secs;
negate = true;
}
int h = secs / 3600;
secs -= h * 3600;
int m = secs / 60;
secs -= m * 60;
int s = secs;
int hhmmss = h * 10000 + m * 100 + s;
if (negate) {
hhmmss = - hhmmss;
}
return hhmmss;
}
@Override
protected Object rawFromValue(ValueSource value) {
int hhmmss = value.getInt32();
boolean negate = false;
if (hhmmss < 0) {
hhmmss = - hhmmss;
negate = true;
}
int h = hhmmss / 10000;
hhmmss -= h * 10000;
int m = hhmmss / 100;
hhmmss -= m * 100;
int s = hhmmss;
int secs = h * 3600 + m * 60 + s;
if (negate) {
secs = - secs;
}
return secs;
}
}
static final class YearConversion extends ProtobufRowConversion {
@Override
public Type getType() {
return Type.TYPE_SINT32;
}
@Override
protected Object valueFromRaw(Object raw) {
return (short)(((Integer)raw) - 1900);
}
@Override
protected Object rawFromValue(ValueSource value) {
short y = value.getInt16();
return y + 1900;
}
}
static final class DecimalAsLong extends ProtobufRowConversion {
private final TInstance type;
public DecimalAsLong(TInstance type) {
this.type = type;
}
@Override
public Type getType() {
return Type.TYPE_SINT64;
}
@Override
public int getDecimalScale() {
return type.attribute(DecimalAttribute.SCALE);
}
@Override
protected Object valueFromRaw(Object raw) {
Long lval = (Long)raw;
BigDecimal decimal = new BigDecimal(BigInteger.valueOf(lval), getDecimalScale());
return new BigDecimalWrapperImpl(decimal);
}
@Override
protected Object rawFromValue(ValueSource value) {
BigDecimalWrapper wrapper = TBigDecimal.getWrapper(value, type);
return wrapper.asBigDecimal().unscaledValue().longValue();
}
}
// TODO: This is whatever byte encoding ConversionHelperBigDecimal uses.
// Is that what we want?
static final class DecimalAsBytes extends ProtobufRowConversion {
private final TInstance type;
public DecimalAsBytes(TInstance type) {
this.type = type;
}
@Override
public Type getType() {
return Type.TYPE_BYTES;
}
@Override
public int getDecimalScale() {
return type.attribute(DecimalAttribute.SCALE);
}
@Override
protected Object valueFromRaw(Object raw) {
ByteString bytes = (ByteString)raw;
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
StringBuilder sb = new StringBuilder();
ConversionHelperBigDecimal.decodeToString(bytes.toByteArray(), 0, precision, scale, AkibanAppender.of(sb));
return new BigDecimalWrapperImpl(sb.toString());
}
@Override
protected Object rawFromValue(ValueSource value) {
BigDecimalWrapper wrapper = TBigDecimal.getWrapper(value, type);
int precision = type.attribute(DecimalAttribute.PRECISION);
int scale = type.attribute(DecimalAttribute.SCALE);
return ByteString.copyFrom(ConversionHelperBigDecimal.bytesFromObject(wrapper.asBigDecimal(), precision, scale));
}
}
}