/** * SQLDecimal satisfies the DataValueDescriptor interfaces (i.e., OrderableDataType). It implements * a numeric/decimal column, e.g. for * storing a column value; it can be specified when constructed * to not allow nulls. Nullability cannot be changed after construction, as it affects the storage * size and mechanism. * * <p>Because OrderableDataType is a subtype of DataType, SQLDecimal can play a role in either a * DataType/Row or a OrderableDataType/Row, interchangeably. * * <p>We assume the store has a flag for nullness of the value, and simply return a 0-length array * for the stored form when the value is null. */ public final class SQLDecimal extends NumberDataType implements VariableSizeDataValue { /** * object state. Note that scale and precision are always determined dynamically from value when * it is not null. * * <p>The field value can be null without the data value being null. In this case the value is * stored in rawData and rawScale. This is to allow the minimal amount of work to read a * SQLDecimal from disk. Creating the BigDecimal is expensive as it requires allocating three * objects, the last two are a waste in the case the row does not qualify or the row will be * written out by the sorter before being returned to the application. * * <p>This means that this field must be accessed for read indirectly through the getBigDecimal() * method, and when setting it the rawData field must be set to null. */ private BigDecimal value; /** See comments for value */ private byte[] rawData; /** See comments for value */ private int rawScale; private static final int BASE_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog(SQLDecimal.class); private static final int BIG_DECIMAL_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog(BigDecimal.class); public int estimateMemoryUsage() { int sz = BASE_MEMORY_USAGE; if (null != value) sz += BIG_DECIMAL_MEMORY_USAGE + (value.unscaledValue().bitLength() + 8) / 8; if (null != rawData) sz += rawData.length; return sz; } //////////////////////////////////////////////////////////////////// // // CLASS INTERFACE // //////////////////////////////////////////////////////////////////// /** no-arg constructor, required by Formattable */ public SQLDecimal() {} public SQLDecimal(BigDecimal val) { value = val; } public SQLDecimal(BigDecimal val, int nprecision, int scale) throws StandardException { value = val; if ((value != null) && (scale >= 0)) { value = value.setScale(scale, BigDecimal.ROUND_DOWN); } } public SQLDecimal(String val) { value = new BigDecimal(val); } /* * DataValueDescriptor interface * (mostly implemented in DataType) * */ /** @exception StandardException thrown on failure to convert */ public int getInt() throws StandardException { if (isNull()) return 0; try { long lv = getLong(); if ((lv >= Integer.MIN_VALUE) && (lv <= Integer.MAX_VALUE)) return (int) lv; } catch (StandardException se) { } throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER"); } /** @exception StandardException thrown on failure to convert */ public byte getByte() throws StandardException { if (isNull()) return (byte) 0; try { long lv = getLong(); if ((lv >= Byte.MIN_VALUE) && (lv <= Byte.MAX_VALUE)) return (byte) lv; } catch (StandardException se) { } throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "TINYINT"); } /** @exception StandardException thrown on failure to convert */ public short getShort() throws StandardException { if (isNull()) return (short) 0; try { long lv = getLong(); if ((lv >= Short.MIN_VALUE) && (lv <= Short.MAX_VALUE)) return (short) lv; } catch (StandardException se) { } throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "SMALLINT"); } /** @exception StandardException thrown on failure to convert */ public long getLong() throws StandardException { BigDecimal localValue = getBigDecimal(); if (localValue == null) return (long) 0; // Valid range for long is // greater than Long.MIN_VALUE - 1 // *and* // less than Long.MAX_VALUE + 1 // // This ensures that DECIMAL values with an integral value // equal to the Long.MIN/MAX_VALUE round correctly to those values. // e.g. 9223372036854775807.1 converts to 9223372036854775807 // this matches DB2 UDB behaviour if ((localValue.compareTo(MINLONG_MINUS_ONE) == 1) && (localValue.compareTo(MAXLONG_PLUS_ONE) == -1)) { return localValue.longValue(); } throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "BIGINT"); } /** @exception StandardException thrown on failure to convert */ public float getFloat() throws StandardException { BigDecimal localValue = getBigDecimal(); if (localValue == null) return (float) 0; // If the BigDecimal is out of range for the float // then positive or negative infinity is returned. float value = NumberDataType.normalizeREAL(localValue.floatValue()); return value; } /** * If we have a value that is greater than the maximum double, exception is thrown. Otherwise, ok. * If the value is less than can be represented by a double, ti will get set to the smallest * double value. * * @exception StandardException thrown on failure to convert */ public double getDouble() throws StandardException { BigDecimal localValue = getBigDecimal(); if (localValue == null) return (double) 0; // If the BigDecimal is out of range for double // then positive or negative infinity is returned. double value = NumberDataType.normalizeDOUBLE(localValue.doubleValue()); return value; } private BigDecimal getBigDecimal() { if ((value == null) && (rawData != null)) { value = new BigDecimal(new BigInteger(rawData), rawScale); } return value; } /** * DECIMAL implementation. Convert to a BigDecimal using getObject which will return a BigDecimal */ public int typeToBigDecimal() { return java.sql.Types.DECIMAL; } // 0 or null is false, all else is true public boolean getBoolean() { BigDecimal localValue = getBigDecimal(); if (localValue == null) return false; return localValue.compareTo(ZERO) != 0; } public String getString() { BigDecimal localValue = getBigDecimal(); if (localValue == null) return null; else if (toPlainString == null) return localValue.toString(); else { // use reflection so we can still compile using JDK1.4 // if we are prepared to require 1.5 to compile then this can be a direct call try { return (String) toPlainString.invoke(localValue, null); } catch (IllegalAccessException e) { // can't happen based on the JDK spec throw new IllegalAccessError("toPlainString"); } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else { // can't happen throw new IncompatibleClassChangeError("toPlainString"); } } } } private static final Method toPlainString; private static final Method bdPrecision; static { Method m; try { m = BigDecimal.class.getMethod("toPlainString", null); } catch (NoSuchMethodException e) { m = null; } toPlainString = m; try { m = BigDecimal.class.getMethod("precision", null); } catch (NoSuchMethodException e) { m = null; } bdPrecision = m; } public Object getObject() { /* ** BigDecimal is immutable */ return getBigDecimal(); } /** * Set the value from a correctly typed BigDecimal object. * * @throws StandardException */ void setObject(Object theValue) throws StandardException { setValue((BigDecimal) theValue); } protected void setFrom(DataValueDescriptor theValue) throws StandardException { setCoreValue(SQLDecimal.getBigDecimal(theValue)); } public int getLength() { return getDecimalValuePrecision(); } // this is for DataType's error generator public String getTypeName() { return TypeId.DECIMAL_NAME; } /* * Storable interface, implies Externalizable, TypedFormat */ /** * Return my format identifier. * * @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId */ public int getTypeFormatId() { return StoredFormatIds.SQL_DECIMAL_ID; } /* * see if the decimal value is null. */ /** @see Storable#isNull */ public boolean isNull() { return (value == null) && (rawData == null); } /** * Distill the BigDecimal to a byte array and write out: * * <UL> * <LI>scale (zero or positive) as a byte * <LI>length of byte array as a byte * <LI>the byte array * </UL> */ public void writeExternal(ObjectOutput out) throws IOException { // never called when value is null if (SanityManager.DEBUG) SanityManager.ASSERT(!isNull()); int scale; byte[] byteArray; if (value != null) { scale = value.scale(); // J2SE 5.0 introduced negative scale value for BigDecimals. // In previouse Java releases a negative scale was not allowed // (threw an exception on setScale and the constructor that took // a scale). // // Thus the Derby format for DECIMAL implictly assumed a // positive or zero scale value, and thus now must explicitly // be positive. This is to allow databases created under J2SE 5.0 // to continue to be supported under JDK 1.3/JDK 1.4, ie. to continue // the platform independence, independent of OS/cpu and JVM. // // If the scale is negative set the scale to be zero, this results // in an unchanged value with a new scale. A BigDecimal with a // negative scale by definition is a whole number. // e.g. 1000 can be represented by: // a BigDecimal with scale -3 (unscaled value of 1) // or a BigDecimal with scale 0 (unscaled value of 1000) if (scale < 0) { scale = 0; value = value.setScale(0); } BigInteger bi = value.unscaledValue(); byteArray = bi.toByteArray(); } else { scale = rawScale; byteArray = rawData; } if (SanityManager.DEBUG) { if (scale < 0) SanityManager.THROWASSERT( "DECIMAL scale at writeExternal is negative " + scale + " value " + toString()); } out.writeByte(scale); out.writeByte(byteArray.length); out.write(byteArray); } /** * Note the use of rawData: we reuse the array if the incoming array is the same length or smaller * than the array length. * * @see java.io.Externalizable#readExternal */ public void readExternal(ObjectInput in) throws IOException { // clear the previous value to ensure that the // rawData value will be used value = null; rawScale = in.readUnsignedByte(); int size = in.readUnsignedByte(); /* ** Allocate a new array if the data to read ** is larger than the existing array, or if ** we don't have an array yet. Need to use readFully below and NOT just read because read does not guarantee getting size bytes back, whereas readFully does (unless EOF). */ if ((rawData == null) || size != rawData.length) { rawData = new byte[size]; } in.readFully(rawData); } /** @see Storable#restoreToNull */ public void restoreToNull() { value = null; rawData = null; } /** @exception StandardException Thrown on error */ protected int typeCompare(DataValueDescriptor arg) throws StandardException { BigDecimal otherValue = SQLDecimal.getBigDecimal(arg); return getBigDecimal().compareTo(otherValue); } /* * DataValueDescriptor interface */ /** @see DataValueDescriptor#cloneValue */ public DataValueDescriptor cloneValue(boolean forceMaterialization) { return new SQLDecimal(getBigDecimal()); } /** @see DataValueDescriptor#getNewNull */ public DataValueDescriptor getNewNull() { return new SQLDecimal(); } /** * @see DataValueDescriptor#setValueFromResultSet * @exception SQLException Thrown on error */ public void setValueFromResultSet(ResultSet resultSet, int colNumber, boolean isNullable) throws SQLException { value = resultSet.getBigDecimal(colNumber); rawData = null; } /** * Set the value into a PreparedStatement. * * @exception SQLException Error setting value in PreparedStatement */ public final void setInto(PreparedStatement ps, int position) throws SQLException { if (isNull()) { ps.setNull(position, java.sql.Types.DECIMAL); return; } ps.setBigDecimal(position, getBigDecimal()); } /** * <B> WARNING </B> there is no checking to make sure that theValue doesn't exceed the * precision/scale of the current SQLDecimal. It is just assumed that the SQLDecimal is supposed * to take the precision/scale of the BigDecimalized String. * * @exception StandardException throws NumberFormatException when the String format is not * recognized. */ public void setValue(String theValue) throws StandardException { rawData = null; if (theValue == null) { value = null; } else { try { theValue = theValue.trim(); value = new BigDecimal(theValue); rawData = null; } catch (NumberFormatException nfe) { throw invalidFormat(); } } } /** * @see NumberDataValue#setValue * @exception StandardException Thrown on error */ public void setValue(double theValue) throws StandardException { setCoreValue(NumberDataType.normalizeDOUBLE(theValue)); } /** @see NumberDataValue#setValue */ public void setValue(float theValue) throws StandardException { setCoreValue((double) NumberDataType.normalizeREAL(theValue)); } /** @see NumberDataValue#setValue */ public void setValue(long theValue) { value = BigDecimal.valueOf(theValue); rawData = null; } /** @see NumberDataValue#setValue */ public void setValue(int theValue) { setValue((long) theValue); } /** * Only to be called when the application sets a value using BigDecimal through setBigDecimal * calls. */ public void setBigDecimal(Number theValue) throws StandardException { setCoreValue((BigDecimal) theValue); } /** * Called when setting a DECIMAL value internally or from through a procedure or function. Handles * long in addition to BigDecimal to handle identity being stored as a long but returned as a * DECIMAL. */ public void setValue(Number theValue) throws StandardException { if (SanityManager.ASSERT) { if (theValue != null && !(theValue instanceof java.math.BigDecimal) && !(theValue instanceof java.lang.Long)) SanityManager.THROWASSERT("SQLDecimal.setValue(Number) passed a " + theValue.getClass()); } if (theValue instanceof BigDecimal || theValue == null) setCoreValue((BigDecimal) theValue); else setValue(theValue.longValue()); } /** @see NumberDataValue#setValue */ public void setValue(boolean theValue) { setCoreValue(theValue ? ONE : ZERO); } /* * DataValueDescriptor interface */ /** @see DataValueDescriptor#typePrecedence */ public int typePrecedence() { return TypeId.DECIMAL_PRECEDENCE; } // END DataValueDescriptor interface private void setCoreValue(BigDecimal theValue) { value = theValue; rawData = null; } private void setCoreValue(double theValue) { value = new BigDecimal(Double.toString(theValue)); rawData = null; } /** * Normalization method - this method may be called when putting a value into a SQLDecimal, for * example, when inserting into a SQLDecimal column. See NormalizeResultSet in execution. * * <p>Note that truncation is allowed on the decimal portion of a numeric only. * * @param desiredType The type to normalize the source column to * @param source The value to normalize * @throws StandardException Thrown for null into non-nullable column, and for truncation error */ public void normalize(DataTypeDescriptor desiredType, DataValueDescriptor source) throws StandardException { int desiredScale = desiredType.getScale(); int desiredPrecision = desiredType.getPrecision(); setFrom(source); setWidth(desiredPrecision, desiredScale, true); } /* ** SQL Operators */ /** * This method implements the + operator for DECIMAL. * * @param addend1 One of the addends * @param addend2 The other addend * @param result The result of a previous call to this method, null if not called yet * @return A SQLDecimal containing the result of the addition * @exception StandardException Thrown on error */ public NumberDataValue plus( NumberDataValue addend1, NumberDataValue addend2, NumberDataValue result) throws StandardException { if (result == null) { result = new SQLDecimal(); } if (addend1.isNull() || addend2.isNull()) { result.setToNull(); return result; } result.setBigDecimal(SQLDecimal.getBigDecimal(addend1).add(SQLDecimal.getBigDecimal(addend2))); return result; } /** * This method implements the - operator for "decimal - decimal". * * @param left The value to be subtracted from * @param right The value to be subtracted * @param result The result of a previous call to this method, null if not called yet * @return A SQLDecimal containing the result of the subtraction * @exception StandardException Thrown on error */ public NumberDataValue minus(NumberDataValue left, NumberDataValue right, NumberDataValue result) throws StandardException { if (result == null) { result = new SQLDecimal(); } if (left.isNull() || right.isNull()) { result.setToNull(); return result; } result.setBigDecimal(SQLDecimal.getBigDecimal(left).subtract(SQLDecimal.getBigDecimal(right))); return result; } /** * This method implements the * operator for "double * double". * * @param left The first value to be multiplied * @param right The second value to be multiplied * @param result The result of a previous call to this method, null if not called yet * @return A SQLDecimal containing the result of the multiplication * @exception StandardException Thrown on error */ public NumberDataValue times(NumberDataValue left, NumberDataValue right, NumberDataValue result) throws StandardException { if (result == null) { result = new SQLDecimal(); } if (left.isNull() || right.isNull()) { result.setToNull(); return result; } result.setBigDecimal(SQLDecimal.getBigDecimal(left).multiply(SQLDecimal.getBigDecimal(right))); return result; } /** * This method implements the / operator for BigDecimal/BigDecimal * * @param dividend The numerator * @param divisor The denominator * @param result The result of a previous call to this method, null if not called yet * @return A SQLDecimal containing the result of the division * @exception StandardException Thrown on error */ public NumberDataValue divide( NumberDataValue dividend, NumberDataValue divisor, NumberDataValue result) throws StandardException { return divide(dividend, divisor, result, -1); } /** * This method implements the / operator for BigDecimal/BigDecimal * * @param dividend The numerator * @param divisor The denominator * @param result The result of a previous call to this method, null if not called yet * @param scale The result scale, if < 0, calculate the scale according to the actual values' * sizes * @return A SQLDecimal containing the result of the division * @exception StandardException Thrown on error */ public NumberDataValue divide( NumberDataValue dividend, NumberDataValue divisor, NumberDataValue result, int scale) throws StandardException { if (result == null) { result = new SQLDecimal(); } if (dividend.isNull() || divisor.isNull()) { result.setToNull(); return result; } BigDecimal divisorBigDecimal = SQLDecimal.getBigDecimal(divisor); if (divisorBigDecimal.compareTo(ZERO) == 0) { throw StandardException.newException(SQLState.LANG_DIVIDE_BY_ZERO); } BigDecimal dividendBigDecimal = SQLDecimal.getBigDecimal(dividend); /* ** Set the result scale to be either the passed in scale, whcih was ** calculated at bind time to be max(ls+rp-rs+1, 4), where ls,rp,rs ** are static data types' sizes, which are predictable and stable ** (for the whole result set column, eg.); otherwise dynamically ** calculates the scale according to actual values. Beetle 3901 */ result.setBigDecimal( dividendBigDecimal.divide( divisorBigDecimal, scale > -1 ? scale : Math.max( (dividendBigDecimal.scale() + SQLDecimal.getWholeDigits(divisorBigDecimal) + 1), NumberDataValue.MIN_DECIMAL_DIVIDE_SCALE), BigDecimal.ROUND_DOWN)); return result; } /** * This method implements the unary minus operator for double. * * @param result The result of a previous call to this method, null if not called yet * @return A SQLDecimal containing the result of the division * @exception StandardException Thrown on error */ public NumberDataValue minus(NumberDataValue result) throws StandardException { if (result == null) { result = new SQLDecimal(); } if (this.isNull()) { result.setToNull(); return result; } result.setBigDecimal(getBigDecimal().negate()); return result; } /** * This method implements the isNegative method. * * @return A boolean. If this.value is negative, return true. For positive values or null, return * false. */ protected boolean isNegative() { return !isNull() && (getBigDecimal().compareTo(ZERO) == -1); } /* * String display of value */ public String toString() { if (isNull()) return "NULL"; else return getString(); } /* * Hash code */ public int hashCode() { long longVal; BigDecimal localValue = getBigDecimal(); double doubleVal = (localValue != null) ? localValue.doubleValue() : 0; if (Double.isInfinite(doubleVal)) { /* ** This loses the fractional part, but it probably doesn't ** matter for numbers that are big enough to overflow a double - ** it's probably rare for numbers this big to be different only in ** their fractional parts. */ longVal = localValue.longValue(); } else { longVal = (long) doubleVal; if (longVal != doubleVal) { longVal = Double.doubleToLongBits(doubleVal); } } return (int) (longVal ^ (longVal >> 32)); } /////////////////////////////////////////////////////////////////////////////// // // VariableSizeDataValue interface // /////////////////////////////////////////////////////////////////////////////// /** * Set the precision/scale of the to the desired values. Used when CASTing. Ideally we'd recycle * normalize(), but the use is different. * * @param desiredPrecision the desired precision -- IGNORE_PREICISION if it is to be ignored. * @param desiredScale the desired scale * @param errorOnTrunc throw error on truncation (ignored -- always thrown if we truncate the * non-decimal part of the value) * @exception StandardException Thrown on non-zero truncation if errorOnTrunc is true */ public void setWidth(int desiredPrecision, int desiredScale, boolean errorOnTrunc) throws StandardException { if (isNull()) return; if (desiredPrecision != IGNORE_PRECISION && ((desiredPrecision - desiredScale) < SQLDecimal.getWholeDigits(getBigDecimal()))) { throw StandardException.newException( SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, ("DECIMAL/NUMERIC(" + desiredPrecision + "," + desiredScale + ")")); } value = value.setScale(desiredScale, BigDecimal.ROUND_DOWN); rawData = null; } /** * Return the SQL scale of this value, number of digits after the decimal point, or zero for a * whole number. This does not match the return from BigDecimal.scale() since in J2SE 5.0 onwards * that can return negative scales. */ public int getDecimalValuePrecision() { if (isNull()) return 0; BigDecimal localValue = getBigDecimal(); return SQLDecimal.getWholeDigits(localValue) + getDecimalValueScale(); } /** * Return the SQL scale of this value, number of digits after the decimal point, or zero for a * whole number. This does not match the return from BigDecimal.scale() since in J2SE 5.0 onwards * that can return negative scales. */ public int getDecimalValueScale() { if (isNull()) return 0; if (value == null) return rawScale; int scale = value.scale(); if (scale >= 0) return scale; // BigDecimal scale is negative, so number must have no fractional // part as its value is the unscaled value * 10^-scale return 0; } /** * Get a BigDecimal representing the value of a DataValueDescriptor * * @param value Non-null value to be converted * @return BigDecimal value * @throws StandardException Invalid conversion or out of range. */ public static BigDecimal getBigDecimal(DataValueDescriptor value) throws StandardException { if (SanityManager.DEBUG) { if (value.isNull()) SanityManager.THROWASSERT("NULL value passed to SQLDecimal.getBigDecimal"); } switch (value.typeToBigDecimal()) { case Types.DECIMAL: return (BigDecimal) value.getObject(); case Types.CHAR: try { return new BigDecimal(value.getString().trim()); } catch (NumberFormatException nfe) { throw StandardException.newException( SQLState.LANG_FORMAT_EXCEPTION, "java.math.BigDecimal"); } case Types.BIGINT: return BigDecimal.valueOf(value.getLong()); default: if (SanityManager.DEBUG) SanityManager.THROWASSERT( "invalid return from " + value.getClass() + ".typeToBigDecimal() " + value.typeToBigDecimal()); return null; } } /** * Calculate the number of digits to the left of the decimal point of the passed in value. * * @param decimalValue Value to get whole digits from, never null. * @return number of whole digits. */ private static int getWholeDigits(BigDecimal decimalValue) { /** if ONE > abs(value) then the number of whole digits is 0 */ decimalValue = decimalValue.abs(); if (ONE.compareTo(decimalValue) == 1) { return 0; } if (bdPrecision != null) { // use reflection so we can still compile using JDK1.4 // if we are prepared to require 1.5 to compile then this can be a // direct call try { // precision is the number of digits in the unscaled value, // subtracting the scale (positive or negative) will give the // number of whole digits. int precision = ((Integer) bdPrecision.invoke(decimalValue, null)).intValue(); return precision - decimalValue.scale(); } catch (IllegalAccessException e) { // can't happen based on the JDK spec throw new IllegalAccessError("precision"); } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else { // can't happen throw new IncompatibleClassChangeError("precision"); } } } String s = decimalValue.toString(); return (decimalValue.scale() == 0) ? s.length() : s.indexOf('.'); } }
/** * Implements an instance of a B-Tree secondary index conglomerate. A B2I object has two roles. * * <ol> * <li>The B2I object is stored on disk, and holds the store specific information needed to * access/describe the conglomerate. This includes information such as the format ids of the * columns, the conglomerate id of the base table, the location of row location column. * <li>Access to all the interfaces start by making a call off the Conglomerate interface. So for * instance to get a scan on the conglomerate method {@link #openScan openScan} should be * called. * </ol> */ public class B2I extends BTree { private static final String PROPERTY_BASECONGLOMID = "baseConglomerateId"; private static final String PROPERTY_ROWLOCCOLUMN = "rowLocationColumn"; static final int FORMAT_NUMBER = StoredFormatIds.ACCESS_B2I_V5_ID; /* ** Fields of B2I. */ /** * The id of the conglomerate which contains the base table. Row locations inserted into this * secondary index are assumed to refer to that conglomerate. Used to obtain table/row locks on * the base table rows which the index rows point at. */ long baseConglomerateId; /** * The column id (zero-based integer index) of the column which holds the row location to the base * conglomerate. The default value of RowLocationColumn is the last key column. Used to obtain * table/row locks on the base table rows with the index rows point at. Currently, * RowLocationColumn must be the last key column. */ int rowLocationColumn; private static final int BASE_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog(B2I.class); public int estimateMemoryUsage() { return BASE_MEMORY_USAGE; } /** * ************************************************************************ Constructors for This * class: ************************************************************************* */ /** * ************************************************************************ Protected locking * implmentations of abtract BTree routines: getBtreeLockingPolicy lockTable * ************************************************************************* */ /** * Create a new btree locking policy from scratch. * * @exception StandardException Standard exception policy. */ protected BTreeLockingPolicy getBtreeLockingPolicy( Transaction rawtran, int lock_level, int mode, int isolation_level, ConglomerateController base_cc, OpenBTree open_btree) throws StandardException { BTreeLockingPolicy ret_locking_policy = null; if (SanityManager.DEBUG) { SanityManager.ASSERT( (isolation_level == TransactionController.ISOLATION_SERIALIZABLE) || (isolation_level == TransactionController.ISOLATION_REPEATABLE_READ) || (isolation_level == TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK) || (isolation_level == TransactionController.ISOLATION_READ_COMMITTED) || (isolation_level == TransactionController.ISOLATION_READ_UNCOMMITTED), "bad isolation_level = " + isolation_level); } if (lock_level == TransactionController.MODE_TABLE) { ret_locking_policy = new B2ITableLocking3( rawtran, lock_level, rawtran.newLockingPolicy(LockingPolicy.MODE_CONTAINER, isolation_level, true), base_cc, open_btree); } else if (lock_level == TransactionController.MODE_RECORD) { if (isolation_level == TransactionController.ISOLATION_SERIALIZABLE) { ret_locking_policy = new B2IRowLocking3( rawtran, lock_level, rawtran.newLockingPolicy(LockingPolicy.MODE_RECORD, isolation_level, true), base_cc, open_btree); } else if ((isolation_level == TransactionController.ISOLATION_REPEATABLE_READ)) { ret_locking_policy = new B2IRowLockingRR( rawtran, lock_level, rawtran.newLockingPolicy(LockingPolicy.MODE_RECORD, isolation_level, true), base_cc, open_btree); } else if ((isolation_level == TransactionController.ISOLATION_READ_COMMITTED) || (isolation_level == TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK)) { ret_locking_policy = new B2IRowLocking2( rawtran, lock_level, rawtran.newLockingPolicy(LockingPolicy.MODE_RECORD, isolation_level, true), base_cc, open_btree); } else if (isolation_level == TransactionController.ISOLATION_READ_UNCOMMITTED) { ret_locking_policy = new B2IRowLocking1( rawtran, lock_level, rawtran.newLockingPolicy(LockingPolicy.MODE_RECORD, isolation_level, true), base_cc, open_btree); } } if (SanityManager.DEBUG) { SanityManager.ASSERT(ret_locking_policy != null, "ret_locking_policy == null"); } return (ret_locking_policy); } /** * Lock the base table. * * <p>Assumes that segment of the base container is the same as the segment of the btree segment. * * <p>RESOLVE - we really want to get the lock without opening the container. raw store will be * providing this. * * @param xact_manager Transaction to associate the lock with. * @exception StandardException Standard exception policy. */ public final ConglomerateController lockTable( TransactionManager xact_manager, int open_mode, int lock_level, int isolation_level) throws StandardException { open_mode |= TransactionController.OPENMODE_FOR_LOCK_ONLY; // open the base conglomerate - just to get the table lock. ConglomerateController cc = xact_manager.openConglomerate( this.baseConglomerateId, false, open_mode, lock_level, isolation_level); return (cc); } /** * ************************************************************************ Private methods of * B2I, arranged alphabetically. * ************************************************************************* */ private void traverseRight() { // RESOLVE - Do I have to do this??????????????? if (SanityManager.DEBUG) SanityManager.THROWASSERT("not implemented."); } /* ** Methods of B2I. */ /** * Create an empty secondary index b-tree, using the generic b-tree to do the generic part of the * creation process. * * <p>This routine opens the newly created container, adds a single page, and makes this page the * root by inserting a LeafControlRow onto this page at slot 0 and marking in that control row * that the page is a root page. * * <p>The following properties are specific to the b-tree secondary index: * * <UL> * <LI>"baseConglomerateId" (integer). The conglomerate id of the base conglomerate is never * actually accessed by the b-tree secondary index implementation, it only serves as a * namespace for row locks. This property is required. * <LI>"rowLocationColumn" (integer). The zero-based index into the row which the b-tree * secondary index will assume holds a @see RowLocation of the base row in the base * conglomerate. This value will be used for acquiring locks. In this implementation * RowLocationColumn must be the last key column. This property is required. * </UL> * * A secondary index i (a, b) on table t (a, b, c) would have rows which looked like (a, b, * row_location). baseConglomerateId is set to the conglomerate id of t. rowLocationColumns is set * to 2. allowsDuplicates would be set to false, @see BTree#create. To create a unique secondary * index set uniquenessColumns to 2, this means that the btree code will compare the key values * but not the row id when determing uniqueness. To create a nonunique secondary index set * uniquenessColumns to 3, this would mean that the uniqueness test would include the row location * and since all row locations will be unique all rows inserted into the index will be * differentiated (at least) by row location. * * @see BTree#create * @exception StandardException Standard exception policy. */ public void create( TransactionManager xact_manager, int segmentId, long input_conglomid, DataValueDescriptor[] template, ColumnOrdering[] columnOrder, int[] collationIds, Properties properties, int temporaryFlag) throws StandardException { String property_value = null; Transaction rawtran = xact_manager.getRawStoreXact(); if (properties == null) { throw (StandardException.newException( SQLState.BTREE_PROPERTY_NOT_FOUND, PROPERTY_BASECONGLOMID)); } // Get baseConglomerateId // property_value = properties.getProperty(PROPERTY_BASECONGLOMID); if (property_value == null) { throw (StandardException.newException( SQLState.BTREE_PROPERTY_NOT_FOUND, PROPERTY_BASECONGLOMID)); } if (SanityManager.DEBUG) { if (property_value == null) SanityManager.THROWASSERT(PROPERTY_BASECONGLOMID + "property not passed to B2I.create()"); } baseConglomerateId = Long.parseLong(property_value); // Get rowLocationColumn // property_value = properties.getProperty(PROPERTY_ROWLOCCOLUMN); if (SanityManager.DEBUG) { if (property_value == null) SanityManager.THROWASSERT(PROPERTY_ROWLOCCOLUMN + "property not passed to B2I.create()"); } if (property_value == null) { throw (StandardException.newException( SQLState.BTREE_PROPERTY_NOT_FOUND, PROPERTY_BASECONGLOMID)); } rowLocationColumn = Integer.parseInt(property_value); // Currently the row location column must be the last column (makes) // comparing the columns in the index easier. if (SanityManager.DEBUG) { SanityManager.ASSERT( rowLocationColumn == template.length - 1, "rowLocationColumn is not the last column in the index"); SanityManager.ASSERT(template[rowLocationColumn] instanceof RowLocation); // There must be at least one key column if (rowLocationColumn < 1) SanityManager.THROWASSERT( "rowLocationColumn (" + rowLocationColumn + ") expected to be >= 1"); } /* convert the sorting order information into a boolean array map. * If the sorting order for the columns is not provided, we * assign the default as Ascending Order. * array length is equal to template length, because column order * length changes whether it is unique or is non unique. store assumes * template length arrays. So, we make template length array and make * the last column as ascending instead of having lot of execeptions * code. */ ascDescInfo = new boolean[template.length]; for (int i = 0; i < ascDescInfo.length; i++) { if (columnOrder != null && i < columnOrder.length) ascDescInfo[i] = columnOrder[i].getIsAscending(); else ascDescInfo[i] = true; // default values - ascending order } // get collation ids from input collation ids, store it in the // conglom state. collation_ids = ConglomerateUtil.createCollationIds(template.length, collationIds); hasCollatedTypes = hasCollatedColumns(collation_ids); // Do the generic part of creating the b-tree. super.create( rawtran, segmentId, input_conglomid, template, properties, getTypeFormatId(), temporaryFlag); // open the base conglomerate - to get the lock ConglomerateController base_cc = xact_manager.openConglomerate( baseConglomerateId, false, TransactionController.OPENMODE_FOR_LOCK_ONLY, TransactionController.MODE_TABLE, TransactionController.ISOLATION_SERIALIZABLE); OpenBTree open_btree = new OpenBTree(); BTreeLockingPolicy b2i_locking_policy = new B2ITableLocking3( rawtran, TransactionController.MODE_TABLE, rawtran.newLockingPolicy( LockingPolicy.MODE_CONTAINER, TransactionController.ISOLATION_SERIALIZABLE, true), base_cc, open_btree); // The following call will "open" the new btree. Create is // an interesting case. What we really want is read only table lock // on the base conglomerate and update locks on the index. For now // just get the update lock on the base table, this is done by the // lockTable() call made by base class. open_btree.init( (TransactionManager) xact_manager, // current user xact (TransactionManager) xact_manager, // current xact (ContainerHandle) null, // have init open the container. rawtran, false, (ContainerHandle.MODE_FORUPDATE), TransactionController.MODE_TABLE, b2i_locking_policy, // get table level lock. this, (LogicalUndo) null, // no logical undo necessary, as // initEmptyBtree() // work will be done single user and // rows will not move. (DynamicCompiledOpenConglomInfo) null); // Open the newly created container, and insert the first control row. LeafControlRow.initEmptyBtree(open_btree); open_btree.close(); base_cc.close(); } /* ** Methods of Conglomerate */ /** * Retrieve the maximum value row in an ordered conglomerate. * * <p>Returns true and fetches the rightmost row of an ordered conglomerate into "fetchRow" if * there is at least one row in the conglomerate. If there are no rows in the conglomerate it * returns false. * * <p>Non-ordered conglomerates will not implement this interface, calls will generate a * StandardException. * * <p>RESOLVE - this interface is temporary, long term equivalent (and more) functionality will be * provided by the openBackwardScan() interface. * * @param xact_manager The TransactionController under which this operation takes place. * @param conglomId The identifier of the conglomerate to open the scan for. * @param open_mode Specifiy flags to control opening of table. OPENMODE_FORUPDATE - if set open * the table for update otherwise open table shared. * @param lock_level One of (MODE_TABLE, MODE_RECORD, or MODE_NONE). * @param isolation_level The isolation level to lock the conglomerate at. One of * (ISOLATION_READ_COMMITTED or ISOLATION_SERIALIZABLE). * @param scanColumnList A description of which columns to return from every fetch in the scan. * template, and scanColumnList work together to describe the row to be returned by the scan - * see RowUtil for description of how these three parameters work together to describe a * "row". * @param fetchRow The row to retrieve the maximum value into. * @return boolean indicating if a row was found and retrieved or not. * @exception StandardException Standard exception policy. */ public boolean fetchMaxOnBTree( TransactionManager xact_manager, Transaction rawtran, long conglomId, int open_mode, int lock_level, LockingPolicy locking_policy, int isolation_level, FormatableBitSet scanColumnList, DataValueDescriptor[] fetchRow) throws StandardException { boolean row_exists; // row level locking implementation. // RESOLVE (revisit implementation after all the Xena rowlocking // changes have been made). Can probably come up with single // path implementation. // Create a new b-tree secondary index scan. B2IMaxScan b2is = new B2IMaxScan(); // Initialize it. b2is.init( xact_manager, rawtran, open_mode, lock_level, locking_policy, isolation_level, true /* get locks on base table as part of open */, scanColumnList, this, new B2IUndo()); row_exists = b2is.fetchMax(fetchRow); b2is.close(); return (row_exists); } /** * Bulk Load a B-tree secondary index. * * @see Conglomerate#load * @exception StandardException Standard Derby Error policy. raise * SQLState.STORE_CONGLOMERATE_DUPLICATE_KEY_EXCEPTION if a duplicate key is detected in the * load. */ public long load( TransactionManager xact_manager, boolean createConglom, RowLocationRetRowSource rowSource) throws StandardException { long num_rows_loaded = 0; B2IController b2ic = new B2IController(); try { int open_mode = TransactionController.OPENMODE_FORUPDATE; if (createConglom) { open_mode |= (ContainerHandle.MODE_UNLOGGED | ContainerHandle.MODE_CREATE_UNLOGGED); } // Do the actual open of the container in the super class. b2ic.init( xact_manager, // current transaction xact_manager.getRawStoreXact(), // current raw store xact false, // Not holdable open_mode, TransactionController.MODE_TABLE, xact_manager .getRawStoreXact() .newLockingPolicy( LockingPolicy.MODE_CONTAINER, TransactionController.ISOLATION_SERIALIZABLE, true), true, this, new B2IUndo(), (B2IStaticCompiledInfo) null, (DynamicCompiledOpenConglomInfo) null); num_rows_loaded = b2ic.load(xact_manager, createConglom, rowSource); } finally { b2ic.close(); } return (num_rows_loaded); } /** * Open a b-tree controller. * * @see Conglomerate#open * @exception StandardException Standard exception policy. */ public ConglomerateController open( TransactionManager xact_manager, Transaction rawtran, boolean hold, int open_mode, int lock_level, LockingPolicy locking_policy, StaticCompiledOpenConglomInfo static_info, DynamicCompiledOpenConglomInfo dynamic_info) throws StandardException { // Create a new b-tree secondary index controller. B2IController b2ic = new B2IController(); // Do the actual open of the container in the super class. b2ic.init( xact_manager, // current transaction rawtran, // current raw store transaction hold, // holdability open_mode, lock_level, locking_policy, true, this, new B2IUndo(), (B2IStaticCompiledInfo) static_info, dynamic_info); // Return it to the caller. return b2ic; } /** * Open a b-tree secondary index scan controller. * * @see Conglomerate#openScan * @see BTree#openScan * @exception StandardException Standard exception policy. */ public ScanManager openScan( TransactionManager xact_manager, Transaction rawtran, boolean hold, int open_mode, int lock_level, LockingPolicy locking_policy, int isolation_level, FormatableBitSet scanColumnList, DataValueDescriptor[] startKeyValue, int startSearchOperator, Qualifier qualifier[][], DataValueDescriptor[] stopKeyValue, int stopSearchOperator, StaticCompiledOpenConglomInfo static_info, DynamicCompiledOpenConglomInfo dynamic_info) throws StandardException { // Create a new b-tree secondary index scan. B2IForwardScan b2is = new B2IForwardScan(); // Initialize it. b2is.init( xact_manager, rawtran, hold, open_mode, lock_level, locking_policy, isolation_level, true /* get locks on base table as part of open */, scanColumnList, startKeyValue, startSearchOperator, qualifier, stopKeyValue, stopSearchOperator, this, new B2IUndo(), (B2IStaticCompiledInfo) static_info, dynamic_info); // Return it to the caller. return b2is; } /** * Open a b-tree compress scan. * * <p>B2I does not support a compress scan. * * <p> * * @see Conglomerate#defragmentConglomerate * @exception StandardException Standard exception policy. */ public ScanManager defragmentConglomerate( TransactionManager xact_manager, Transaction rawtran, boolean hold, int open_mode, int lock_level, LockingPolicy locking_policy, int isolation_level) throws StandardException { throw StandardException.newException(SQLState.BTREE_UNIMPLEMENTED_FEATURE); } public void purgeConglomerate(TransactionManager xact_manager, Transaction rawtran) throws StandardException { // currently on work to do in btree's for purge rows, purging // happens best when split is about to happen. return; } public void compressConglomerate(TransactionManager xact_manager, Transaction rawtran) throws StandardException { B2IController b2ic = new B2IController(); try { int open_mode = TransactionController.OPENMODE_FORUPDATE; // Do the actual open of the container in the super class. b2ic.init( xact_manager, // current transaction xact_manager.getRawStoreXact(), // current raw store xact false, // Not holdable open_mode, TransactionController.MODE_TABLE, xact_manager .getRawStoreXact() .newLockingPolicy( LockingPolicy.MODE_CONTAINER, TransactionController.ISOLATION_SERIALIZABLE, true), true, this, new B2IUndo(), (B2IStaticCompiledInfo) null, (DynamicCompiledOpenConglomInfo) null); b2ic.getContainer().compressContainer(); } finally { b2ic.close(); } return; } /** * Return an open StoreCostController for the conglomerate. * * <p>Return an open StoreCostController which can be used to ask about the estimated row counts * and costs of ScanController and ConglomerateController operations, on the given conglomerate. * * <p> * * @param xact_manager The TransactionController under which this operation takes place. * @param rawtran raw transaction context in which scan is managed. * @return The open StoreCostController. * @exception StandardException Standard exception policy. * @see StoreCostController */ public StoreCostController openStoreCost(TransactionManager xact_manager, Transaction rawtran) throws StandardException { B2ICostController b2icost = new B2ICostController(); b2icost.init(xact_manager, this, rawtran); return (b2icost); } /** * Drop this b-tree secondary index. * * @see Conglomerate#drop * @see BTree#drop * @exception StandardException Standard exception policy. */ public void drop(TransactionManager xact_manager) throws StandardException { // HACK to get around problem where index is dropped after the base // table. ConglomerateController base_cc = null; /* Get X table lock to make sure no thread is accessing index */ base_cc = lockTable( xact_manager, TransactionController.OPENMODE_FORUPDATE, TransactionController.MODE_TABLE, TransactionController.ISOLATION_REPEATABLE_READ); xact_manager.getRawStoreXact().dropContainer(id); if (base_cc != null) base_cc.close(); } /** * Return static information about the conglomerate to be included in a a compiled plan. * * <p>The static info would be valid until any ddl was executed on the conglomid, and would be up * to the caller to throw away when that happened. This ties in with what language already does * for other invalidation of static info. The type of info in this would be containerid and array * of format id's from which templates can be created. The info in this object is read only and * can be shared among as many threads as necessary. * * <p> * * @return The static compiled information. * @param conglomId The identifier of the conglomerate to open. * @exception StandardException Standard exception policy. */ public StaticCompiledOpenConglomInfo getStaticCompiledConglomInfo( TransactionController xact_manager, long conglomId) throws StandardException { return (new B2IStaticCompiledInfo(xact_manager, this)); } /* ** Methods of Storable (via Conglomerate via BTree). ** This class is responsible for re/storing its ** own state and calling its superclass to store its'. */ /* * Storable interface, implies Externalizable, TypedFormat */ /** * Return my format identifier. * * @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId */ public int getTypeFormatId() { return StoredFormatIds.ACCESS_B2I_V5_ID; } /** * Store the stored representation of the column value in the stream. * * <p>For more detailed description of the ACCESS_B2I_V3_ID format see documentation at top of * file. * * @see java.io.Externalizable#writeExternal */ public void writeExternal_v10_2(ObjectOutput out) throws IOException { super.writeExternal(out); out.writeLong(baseConglomerateId); out.writeInt(rowLocationColumn); // write the columns ascend/descend information as bits FormatableBitSet ascDescBits = new FormatableBitSet(ascDescInfo.length); for (int i = 0; i < ascDescInfo.length; i++) { if (ascDescInfo[i]) ascDescBits.set(i); } ascDescBits.writeExternal(out); } /** * Store the stored representation of the column value in the stream. * * <p>For more detailed description of the ACCESS_B2I_V3_ID and ACCESS_B2I_V4_ID formats see * documentation at top of file. * * @see java.io.Externalizable#writeExternal */ public void writeExternal_v10_3(ObjectOutput out) throws IOException { // First part of ACCESS_B2I_V4_ID format is the ACCESS_B2I_V3_ID format. writeExternal_v10_2(out); if (conglom_format_id == StoredFormatIds.ACCESS_B2I_V4_ID || conglom_format_id == StoredFormatIds.ACCESS_B2I_V5_ID) { // Now append sparse array of collation ids ConglomerateUtil.writeCollationIdArray(collation_ids, out); } } /** * Store the stored representation of the column value in the stream. * * <p>For more detailed description of the ACCESS_B2I_V3_ID and ACCESS_B2I_V5_ID formats see * documentation at top of file. * * @see java.io.Externalizable#writeExternal */ public void writeExternal(ObjectOutput out) throws IOException { writeExternal_v10_3(out); if (conglom_format_id == StoredFormatIds.ACCESS_B2I_V5_ID) out.writeBoolean(isUniqueWithDuplicateNulls()); } /** * Restore the in-memory representation from the stream. * * <p> * * @exception ClassNotFoundException Thrown if the stored representation is serialized and a class * named in the stream could not be found. * @see java.io.Externalizable#readExternal */ private final void localReadExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); baseConglomerateId = in.readLong(); rowLocationColumn = in.readInt(); // read the column sort order info FormatableBitSet ascDescBits = new FormatableBitSet(); ascDescBits.readExternal(in); ascDescInfo = new boolean[ascDescBits.getLength()]; for (int i = 0; i < ascDescBits.getLength(); i++) ascDescInfo[i] = ascDescBits.isSet(i); // In memory maintain a collation id per column in the template. collation_ids = new int[format_ids.length]; if (SanityManager.DEBUG) { SanityManager.ASSERT(!hasCollatedTypes); } // initialize all the entries to COLLATION_TYPE_UCS_BASIC, // and then reset as necessary. For version ACCESS_B2I_V3_ID, // this is the default and no resetting is necessary. for (int i = 0; i < format_ids.length; i++) collation_ids[i] = StringDataValue.COLLATION_TYPE_UCS_BASIC; // initialize the unique with null setting to false, to be reset // below when read from disk. For version ACCESS_B2I_V3_ID and // ACCESS_B2I_V4_ID, this is the default and no resetting is necessary. setUniqueWithDuplicateNulls(false); if (conglom_format_id == StoredFormatIds.ACCESS_B2I_V4_ID || conglom_format_id == StoredFormatIds.ACCESS_B2I_V5_ID) { // current format id, read collation info from disk if (SanityManager.DEBUG) { // length must include row location column and at least // one other field. SanityManager.ASSERT(collation_ids.length >= 2, "length = " + collation_ids.length); } hasCollatedTypes = ConglomerateUtil.readCollationIdArray(collation_ids, in); } else if (conglom_format_id != StoredFormatIds.ACCESS_B2I_V3_ID) { // Currently only V3, V4 and V5 should be possible in a Derby DB. // Actual work for V3 is handled by default code above, so no // special work is necessary. if (SanityManager.DEBUG) { SanityManager.THROWASSERT("Unexpected format id: " + conglom_format_id); } } if (conglom_format_id == StoredFormatIds.ACCESS_B2I_V5_ID) { setUniqueWithDuplicateNulls(in.readBoolean()); } } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { localReadExternal(in); } public void readExternalFromArray(ArrayInputStream in) throws IOException, ClassNotFoundException { localReadExternal(in); } }