/** * 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.rowdata; import java.math.BigDecimal; import java.util.UUID; import com.foundationdb.server.AkServerUtil; 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.AkGUID; import com.foundationdb.server.types.common.BigDecimalWrapperImpl; import com.foundationdb.server.types.common.types.TBigDecimal; import com.foundationdb.server.types.common.types.TString; import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime; import com.foundationdb.server.types.mcompat.mtypes.MNumeric; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.util.AkibanAppender; import com.foundationdb.server.service.blob.BlobRef; abstract class AbstractRowDataValueSource implements ValueSource { // ValueSource interface @Override public TInstance getType() { return fieldDef().column().getType(); } @Override public boolean hasAnyValue() { return true; } @Override public boolean hasRawValue() { return ! hasCacheValue(); } @Override public boolean hasCacheValue() { return fieldDef().column().getType().typeClass() instanceof TString; } @Override public boolean canGetRawValue() { return true; } @Override public abstract boolean isNull(); @Override public boolean getBoolean() { return extractLong(signage()) != 0; } @Override public boolean getBoolean(boolean defaultValue) { return isNull() ? defaultValue : getBoolean(); } @Override public byte getInt8() { return (byte) extractLong(signage()); } @Override public short getInt16() { return (short) extractLong(signage()); } @Override public char getUInt16() { return (char) extractLong(signage()); } @Override public int getInt32() { return (int) extractLong(signage()); } @Override public long getInt64() { return extractLong(signage()); } @Override public float getFloat() { return doGetFloat(); } @Override public double getDouble() { return doGetDouble(); } @Override public byte[] getBytes() { long offsetAndWidth = getRawOffsetAndWidth(); if (offsetAndWidth == 0) { return null; } int offset = (int) offsetAndWidth + fieldDef().getPrefixSize(); int size = (int) (offsetAndWidth >>> 32) - fieldDef().getPrefixSize(); byte[] bytes = new byte[size]; System.arraycopy(bytes(), offset, bytes, 0, size); return bytes; } @Override public String getString() { final long location = getRawOffsetAndWidth(); return location == 0 ? null : AkServerUtil.decodeMySQLString(bytes(), (int) location, (int) (location >>> 32), fieldDef()); } @Override public Object getObject() { if (fieldDef().column().getType().typeClass() instanceof TString) { return getString(); } else if (fieldDef().column().getType().typeClass() instanceof TBigDecimal) { return getDecimal(); } else if (fieldDef().column().getType().typeClass() instanceof AkGUID) { return getGUID(); } else if (fieldDef().column().getType().typeClass() instanceof AkBlob) { return getBlob(); } else { assert false : "Unable to get object for type: " + fieldDef(); } return null; } // for subclasses protected abstract long getRawOffsetAndWidth(); protected abstract byte[] bytes(); protected abstract FieldDef fieldDef(); private BlobRef getBlob() { long offsetAndWidth = getRawOffsetAndWidth(); if (offsetAndWidth == 0) { return null; } int offset = (int) offsetAndWidth + fieldDef().getPrefixSize(); int size = (int) (offsetAndWidth >>> 32) - fieldDef().getPrefixSize(); byte[] bytes = new byte[size]; System.arraycopy(bytes(), offset, bytes, 0, size); return new BlobRef(bytes, BlobRef.LeadingBitState.YES); } // for use within this class private UUID getGUID() { final long location = getRawOffsetAndWidth(); int offset = (int) location; int width = (int) (location >>> 32); byte[] bytes = bytes(); final int prefixSize = fieldDef().getPrefixSize(); if ( width != 16) { throw new IllegalArgumentException(String.format( "GUID must be 16 bytes instead: %d", width)); } if (location == 0) { return null; } else { return AkGUID.bytesToUUID(bytes, offset + prefixSize); } } private BigDecimalWrapperImpl getDecimal() { AkibanAppender appender = AkibanAppender.of(new StringBuilder(fieldDef().getMaxStorageSize())); ConversionHelperBigDecimal.decodeToString(fieldDef(), bytes(), getRawOffsetAndWidth(), appender); String asString = appender.toString(); assert ! asString.isEmpty(); try { return new BigDecimalWrapperImpl(new BigDecimal(asString)); } catch (NumberFormatException e) { throw new NumberFormatException(asString); } } private double doGetDouble() { long asLong = extractLong(Signage.SIGNED); return Double.longBitsToDouble(asLong); } private float doGetFloat() { long asLong = extractLong(Signage.SIGNED); int asInt = (int) asLong; return Float.intBitsToFloat(asInt); } private long extractLong(Signage signage) { long offsetAndWidth = getCheckedOffsetAndWidth(); final int offset = (int)offsetAndWidth; final int width = (int)(offsetAndWidth >>> 32); if ((signage == Signage.SIGNED) || (width == 8)) { return AkServerUtil.getSignedIntegerByWidth(bytes(), offset, width); } else { assert signage == Signage.UNSIGNED; return AkServerUtil.getUnsignedIntegerByWidth(bytes(), offset, width); } } private Signage signage() { TClass tclass = fieldDef().column().getType().typeClass(); if (tclass instanceof MNumeric) return ((MNumeric)tclass).isUnsigned() ? Signage.UNSIGNED : Signage.SIGNED; else if (tclass == MDateAndTime.YEAR) return Signage.UNSIGNED; else return Signage.SIGNED; } private long getCheckedOffsetAndWidth() { long offsetAndWidth = getRawOffsetAndWidth(); if (offsetAndWidth == 0) { throw new RowDataException("value is null"); } return offsetAndWidth; } // object state private enum Signage { SIGNED, UNSIGNED } }