/** * 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.mcompat.mtypes; import com.foundationdb.server.collation.AkCollator; import com.foundationdb.server.collation.AkCollatorFactory; import com.foundationdb.server.types.ValueIO; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.common.types.StringAttribute; import com.foundationdb.server.types.common.types.StringFactory; import com.foundationdb.server.types.common.types.TString; import com.foundationdb.server.types.mcompat.MBundle; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.types.value.ValueTarget; import com.foundationdb.sql.types.TypeId; import com.foundationdb.util.Strings; import java.io.UnsupportedEncodingException; import java.util.Arrays; public class MString extends TString { public static TInstance varcharFor(String string) { return string == null ? MString.VARCHAR.instance(0, true) : MString.VARCHAR.instance(string.length(), false); } public static TInstance varchar() { return MString.VARCHAR.instance(true); } public static final MString CHAR = new MString(TypeId.CHAR_ID, "char"); public static final MString VARCHAR = new MString(TypeId.VARCHAR_ID, "varchar"); public static final MString TINYTEXT = new MString(TypeId.TINYTEXT_ID, "tinytext", 255); public static final MString TEXT = new MString(TypeId.TEXT_ID, "text", 65535); public static final MString MEDIUMTEXT = new MString(TypeId.MEDIUMTEXT_ID, "mediumtext", 16777215); public static final MString LONGTEXT = new MString(TypeId.LONGTEXT_ID, "longtext", Integer.MAX_VALUE); // TODO not big enough! @Override protected ValueIO getValueIO() { return PVALUE_IO; } @Override public void selfCast(TExecutionContext context, TInstance sourceInstance, ValueSource source, TInstance targetInstance, ValueTarget target) { int maxTargetLen = targetInstance.attribute(StringAttribute.MAX_LENGTH); String sourceString = source.getString(); String truncated = Strings.truncateIfNecessary(sourceString, maxTargetLen); if (sourceString != truncated) { context.reportTruncate(sourceString, truncated); sourceString = truncated; } target.putString(sourceString, null); } private MString(TypeId typeId, String name, int fixedSize) { super(typeId, MBundle.INSTANCE, name, -1, fixedSize); } private MString(TypeId typeId, String name) { super(typeId, MBundle.INSTANCE, name, -1); } @Override public boolean compatibleForCompare(TClass other) { return super.compatibleForCompare(other) || ((this == CHAR) && (other == VARCHAR)) || ((this == VARCHAR) && (other == CHAR)); } // Generally, text fields are rarely used in comparisons other than 'EQUAL' // Hence, it is not necessary to widen the width to capture the widest type // If the input does not fit into *this* width, it is definitely not going to be equal // Might need to flag 'TRUNCATION' as an error, not warning public TClass widestComparable() { return this; } @Override public void fromObject(TExecutionContext context, ValueSource in, ValueTarget out) { if (in.isNull()) { out.putNull(); return; } int expectedLen = context.outputType().attribute(StringAttribute.MAX_LENGTH); int charsetId = context.outputType().attribute(StringAttribute.CHARSET); int collatorId = context.outputType().attribute(StringAttribute.COLLATION); switch (TInstance.underlyingType(in.getType())) { case STRING: String inStr = in.getString(); String ret = Strings.truncateIfNecessary(inStr, expectedLen); if (inStr != ret) { context.reportTruncate(inStr, ret); } out.putString(ret, AkCollatorFactory.getAkCollator(collatorId)); break; case BYTES: byte bytes[] = in.getBytes(); byte truncated[]; if (bytes.length > expectedLen) { truncated = Arrays.copyOf(bytes, expectedLen); context.reportTruncate("BYTES string of length " + bytes.length, "BYTES string of length " + expectedLen); } else truncated = bytes; try { out.putString(new String(truncated, StringFactory.Charset.of(charsetId)) , AkCollatorFactory.getAkCollator(collatorId)); } catch (UnsupportedEncodingException e) { context.reportBadValue(e.getMessage()); } break; default: throw new IllegalArgumentException("Unexpected UnderlyingType: " + in.getType()); } } private static final ValueIO PVALUE_IO = new ValueIO() { @Override public void copyCanonical(ValueSource in, TInstance typeInstance, ValueTarget out) { out.putString(in.getString(), null); } @Override public void writeCollating(ValueSource inValue, TInstance inInstance, ValueTarget out) { final AkCollator collator = getCollator(inInstance); if (inValue.getObject() instanceof byte[]) { out.putBytes((byte[])inValue.getObject()); } else if (inValue.getObject() instanceof String || inValue.getObject() == null) { out.putString((String)inValue.getObject(), collator); } else { throw new UnsupportedOperationException("Unexpected inValue: " + inValue.getObject()); } } @Override public void readCollating(ValueSource in, TInstance typeInstance, ValueTarget out) { if (in.canGetRawValue()) out.putString(in.getString(), null); else if (in.hasCacheValue()) out.putObject(in.getObject()); else throw new AssertionError("no value"); } }; }