/**
* 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.types.common.types;
import com.foundationdb.server.error.AkibanInternalException;
import com.foundationdb.server.types.Attribute;
import com.foundationdb.server.types.IllegalNameException;
import com.foundationdb.server.types.TBundle;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TClassBase;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.TParser;
import com.foundationdb.server.types.aksql.AkCategory;
import com.foundationdb.server.types.common.NumericFormatter;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueSources;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.server.types.texpressions.Serialization;
import com.foundationdb.server.types.texpressions.SerializeAs;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.sql.types.TypeId;
import java.io.UnsupportedEncodingException;
import java.sql.Types;
import java.util.Arrays;
public abstract class TBinary extends TClassBase {
protected static final int MAX_BYTE_BUF = 4096;
protected static final TParser parser = new BinaryParser();
public enum Attrs implements Attribute {
@SerializeAs(Serialization.LONG_1) LENGTH
}
@Override
public boolean attributeIsPhysical(int attributeIndex) {
return false;
}
@Override
protected boolean attributeAlwaysDisplayed(int attributeIndex) {
return (defaultLength < 0);
}
public TClass widestComparable()
{
return this;
}
@Override
public void fromObject(TExecutionContext context, ValueSource in, ValueTarget out)
{
if (in.isNull()) {
out.putNull();
return;
}
byte[] bytes;
UnderlyingType underlying = ValueSources.underlyingType(in);
if (underlying == UnderlyingType.BYTES) {
bytes = in.getBytes();
}
else if (underlying == UnderlyingType.STRING) {
try {
bytes = in.getString().getBytes("utf8");
} catch (UnsupportedEncodingException e) {
throw new AkibanInternalException("while converting to bytes: " + in.getString(), e);
}
}
else {
throw new AkibanInternalException("couldn't convert to byte[]: " + in);
}
int expectedLength = context.outputType().attribute(Attrs.LENGTH);
if (bytes.length > expectedLength)
{
out.putBytes(Arrays.copyOf(bytes, expectedLength));
context.reportTruncate("BINARY string of LENGTH: " + bytes.length,
"BINARY string of LENGTH: " + expectedLength);
}
else
out.putBytes(bytes);
}
@Override
public TInstance instance(boolean nullable) {
// 'defaultLength' doesn't always mean "LENGTH"
// -1 simply means a (VAR)BINARY type, in which case, you don't want
// to create an instance with length -1, but with MAX_BYTE_BUF (4096)
return instance(defaultLength < 0 ? MAX_BYTE_BUF : defaultLength, nullable);
}
@Override
protected TInstance doPickInstance(TInstance left, TInstance right, boolean suggestedNullability) {
int len0 = left.attribute(Attrs.LENGTH);
int len1 = left.attribute(Attrs.LENGTH);
return len0 > len1 ? left : right;
}
@Override
protected void validate(TInstance type) {
int len = type.attribute(Attrs.LENGTH);
if (defaultLength < 0) {
// This is BINARY or VARBINARY, so the user set the length
if (len < 0)
throw new IllegalNameException("length must be non-negative");
}
else {
// This is one of the blob types, so the length has to be exactly what we expect
assert len == defaultLength : "expected length=" + defaultLength + " but was " + len;
}
}
protected TBinary(TypeId typeId, TBundle bundle, String name, int defaultLength) {
super(bundle.id(), name, AkCategory.STRING_BINARY, Attrs.class, NumericFormatter.FORMAT.BYTES,
1, 1, -1, UnderlyingType.BYTES, parser, (defaultLength < 0 ? MAX_BYTE_BUF : defaultLength));
this.typeId = typeId;
this.defaultLength = defaultLength;
}
private final TypeId typeId;
private final int defaultLength;
public int getDefaultLength() {
return defaultLength;
}
@Override
public int jdbcType() {
if (defaultLength < 0)
return typeId.getJDBCTypeId(); // [VAR]BINARY
else
return Types.LONGVARBINARY; // Not BLOB.
}
@Override
protected DataTypeDescriptor dataTypeDescriptor(TInstance type) {
return new DataTypeDescriptor(typeId,
type.nullability(),
type.attribute(Attrs.LENGTH));
}
@Override
public int variableSerializationSize(TInstance type, boolean average) {
return type.attribute(Attrs.LENGTH);
}
public static void putBytes(TExecutionContext context, ValueTarget target, byte[] bytes) {
int maxLen = context.outputType().attribute(Attrs.LENGTH);
if (bytes.length > maxLen) {
context.reportTruncate("bytes of length " + bytes.length, "bytes of length " + maxLen);
bytes = Arrays.copyOf(bytes, maxLen);
}
else if ((bytes.length < maxLen) &&
(((TBinary)context.outputType().typeClass()).typeId == TypeId.BIT_ID)) {
bytes = Arrays.copyOf(bytes, maxLen);
}
target.putBytes(bytes);
}
private static class BinaryParser implements TParser {
@Override
public void parse(TExecutionContext context, ValueSource in, ValueTarget out) {
String string = in.getString();
int charsetId = in.getType().attribute(StringAttribute.CHARSET);
String charsetName = StringFactory.Charset.values()[charsetId].name();
byte[] bytes;
try {
bytes = string.getBytes(charsetName);
} catch (UnsupportedEncodingException e) {
throw new AkibanInternalException("while decoding string using " + charsetName, e);
}
putBytes(context, out, bytes);
}
}
}