/** * 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.error.AkibanInternalException; import com.foundationdb.server.types.*; import com.foundationdb.server.types.aksql.AkCategory; import com.foundationdb.server.types.aksql.aktypes.AkBool; import com.foundationdb.server.types.common.NumericFormatter; import com.foundationdb.server.types.common.types.NumericAttribute; import com.foundationdb.server.types.common.types.SimpleDtdTClass; import com.foundationdb.server.types.common.types.TBigDecimal; import com.foundationdb.server.types.mcompat.MBundle; import com.foundationdb.server.types.mcompat.MParsers; import com.foundationdb.server.types.value.UnderlyingType; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.types.value.ValueTarget; import com.foundationdb.sql.types.TypeId; import com.google.common.primitives.UnsignedLongs; import java.lang.reflect.Field; import java.math.BigInteger; public class MNumeric extends SimpleDtdTClass { protected MNumeric(String name, TClassFormatter formatter, int serializationSize, UnderlyingType underlyingType, int defaultWidth, TParser parser) { super(MBundle.INSTANCE.id(), name, AkCategory.INTEGER, formatter, NumericAttribute.class, 1, 1, serializationSize, underlyingType, parser, defaultWidth, inferTypeid(name)); this.defaultWidth = defaultWidth; this.isUnsigned = name.endsWith(" unsigned"); } private static TypeId inferTypeid(String name) { // special cases first if ("int".equals(name)) return TypeId.INTEGER_ID; if ("int unsigned".equals(name)) return TypeId.INTEGER_UNSIGNED_ID; name = name.toUpperCase().replace(' ', '_') + "_ID"; try { Field field = TypeId.class.getField(name); // assume public and static. If not, Field.get will fail, which is all we can do anyway. Object instance = field.get(null); return (TypeId) instance; } catch (Exception e) { throw new AkibanInternalException("while getting field " + name, e); } } @Override public boolean attributeIsPhysical(int attributeIndex) { return false; } @Override protected boolean attributeAlwaysDisplayed(int attributeIndex) { return false; } @Override public TInstance instance(boolean nullable) { return instance(defaultWidth, nullable); } @Override protected void validate(TInstance type) { int m = type.attribute(NumericAttribute.WIDTH); if (m < 0 || m > 255) throw new TypeDeclarationException("width must be 0 < M < 256"); } @Override protected TInstance doPickInstance(TInstance left, TInstance right, boolean suggestedNullability) { int leftWidth = left.attribute(NumericAttribute.WIDTH); int rightWidth = right.attribute(NumericAttribute.WIDTH); return instance(Math.max(leftWidth, rightWidth), suggestedNullability); } public TClass widestComparable() { return BIGINT; } @Override protected boolean tryFromObject(TExecutionContext context, ValueSource in, ValueTarget out) { if (in.getType().typeClass() == AkBool.INSTANCE) { byte asInt = (byte)(in.getBoolean() ? 1 : 0); switch (out.getType().typeClass().underlyingType()) { case INT_8: out.putInt8(asInt); return true; case INT_16: out.putInt16(asInt); return true; case UINT_16: out.putUInt16((char)asInt); return true; case INT_32: out.putInt32(asInt); return true; case INT_64: out.putInt64(asInt); return true; default: // fall through and keep trying the standard ways } } return super.tryFromObject(context, in, out); } @Override public boolean isUnsigned() { return isUnsigned; } @Override public int jdbcType() { int result = super.jdbcType(); if (result == java.sql.Types.OTHER) { if ((this == MEDIUMINT) || (this == MEDIUMINT_UNSIGNED)) // TODO: Maybe it would be better to fix TypeId in the parser. result = java.sql.Types.INTEGER; else assert false : this; } return result; } @Override public int fixedSerializationSize(TInstance type) { if (isUnsigned) { // This is the size for just storing the bits. if (this == TINYINT_UNSIGNED) return 1; else if (this == SMALLINT_UNSIGNED) return 2; else if (this == MEDIUMINT_UNSIGNED) return 3; else if (this == INT_UNSIGNED) return 4; } return super.fixedSerializationSize(type); } private final int defaultWidth; private final boolean isUnsigned; // numeric types // TODO verify default widths public static final MNumeric TINYINT = new MNumeric("tinyint", NumericFormatter.FORMAT.INT_8, 1, UnderlyingType.INT_8, 5, MParsers.TINYINT); public static final MNumeric TINYINT_UNSIGNED = new MNumeric("tinyint unsigned", NumericFormatter.FORMAT.INT_16, 4, UnderlyingType.INT_16, 4, MParsers.UNSIGNED_TINYINT); public static final MNumeric SMALLINT = new MNumeric("smallint", NumericFormatter.FORMAT.INT_16, 2, UnderlyingType.INT_16, 7, MParsers.SMALLINT); public static final MNumeric SMALLINT_UNSIGNED = new MNumeric("smallint unsigned", NumericFormatter.FORMAT.INT_32, 4, UnderlyingType.INT_32, 6, MParsers.UNSIGNED_SMALLINT); public static final MNumeric MEDIUMINT = new MNumeric("mediumint", NumericFormatter.FORMAT.INT_32, 3, UnderlyingType.INT_32, 9, MParsers.MEDIUMINT); public static final MNumeric MEDIUMINT_UNSIGNED = new MNumeric("mediumint unsigned", NumericFormatter.FORMAT.INT_64, 8, UnderlyingType.INT_64, 8, MParsers.UNSIGNED_MEDIUMINT); public static final MNumeric INT = new MNumeric("int", NumericFormatter.FORMAT.INT_32, 4, UnderlyingType.INT_32, 11, MParsers.INT); public static final MNumeric INT_UNSIGNED = new MNumeric("int unsigned", NumericFormatter.FORMAT.INT_64, 8, UnderlyingType.INT_64, 10, MParsers.UNSIGNED_INT); public static final MNumeric BIGINT = new MNumeric("bigint", NumericFormatter.FORMAT.INT_64, 8, UnderlyingType.INT_64, 21, MParsers.BIGINT) { public TClass widestComparable() { return DECIMAL; } }; public static final MNumeric BIGINT_UNSIGNED = new MNumeric("bigint unsigned", NumericFormatter.FORMAT.UINT_64, 8, UnderlyingType.INT_64, 20, MParsers.UNSIGNED_BIGINT) { public TClass widestComparable() { return DECIMAL; } @Override protected ValueIO getValueIO() { return bigintUnsignedIO; } @Override protected int doCompare(TInstance typeA, ValueSource sourceA, TInstance typeB, ValueSource sourceB) { return UnsignedLongs.compare(sourceA.getInt64(), sourceB.getInt64()); } }; public static final TClass DECIMAL_UNSIGNED = new MBigDecimal("decimal unsigned", 10) { public TClass widestComparable() { return DECIMAL; } }; public static long getAsLong(TClass tClass, ValueSource source) { assert tClass instanceof MNumeric : "not an MNumeric: " + tClass; long result; switch (tClass.underlyingType()) { case INT_8: result = source.getInt8(); break; case INT_16: result = source.getInt16(); break; case UINT_16: result = source.getUInt16(); break; case INT_32: result = source.getInt32(); break; case INT_64: result = source.getInt64(); break; default: throw new AssertionError(tClass.underlyingType() + ": " + tClass); } if ( ((MNumeric)tClass).isUnsigned && result < 0) { throw new IllegalStateException("can't get unsigned integer as long because it is too big: " + UnsignedLongs.toString(result)); } return result; } public static void putAsLong(TClass tClass, ValueTarget target, long value) { assert tClass instanceof MNumeric : "not an MNumeric: " + tClass; // TODO better bounds checking? Or do we just trust the caller? if ( ((MNumeric)tClass).isUnsigned && value < 0) { throw new IllegalStateException("can't get unsigned integer as long because it is too big: " + UnsignedLongs.toString(value)); } switch (tClass.underlyingType()) { case INT_8: target.putInt8((byte)value); break; case INT_16: target.putInt16((short)value); break; case UINT_16: target.putUInt16((char)value); break; case INT_32: target.putInt32((int)value); break; case INT_64: target.putInt64(value); break; default: throw new AssertionError(tClass.underlyingType() + ": " + tClass); } } public static final TBigDecimal DECIMAL = new MBigDecimal("decimal", 11); private static final ValueIO bigintUnsignedIO = new ValueIO() { @Override public void copyCanonical(ValueSource in, TInstance typeInstance, ValueTarget out) { out.putInt64(in.getInt64()); } @Override public void writeCollating(ValueSource in, TInstance typeInstance, ValueTarget out) { String asString = UnsignedLongs.toString(in.getInt64()); BigInteger asBigint = new BigInteger(asString); out.putObject(asBigint); } @Override public void readCollating(ValueSource in, TInstance typeInstance, ValueTarget out) { BigInteger asBigint = (BigInteger) in.getObject(); long asLong = UnsignedLongs.parseUnsignedLong(asBigint.toString()); out.putInt64(asLong); } }; }