All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.engine.util.BigDecimalUtils Maven / Gradle / Ivy

There is a newer version: 0.37.1
Show newest version
/**
 * Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
 */
package io.deephaven.engine.util;

import io.deephaven.chunk.ObjectChunk;
import io.deephaven.chunk.attributes.Values;
import io.deephaven.engine.rowset.RowSequence;
import io.deephaven.engine.rowset.RowSet;
import io.deephaven.engine.table.ChunkSource;
import io.deephaven.engine.table.ColumnSource;
import io.deephaven.engine.table.Table;

import java.math.BigDecimal;
import java.util.Properties;

/**
 * Utilities to support BigDecimal exhaust.
 *
 * Parquet and Avro decimal types make a whole column decimal type have a fixed precision and scale; BigDecimal columns
 * in Deephaven are, each value, arbitrary precision (its own precision and scale).
 *
 * For static tables, it is possible to compute overall precision and scale values that fit every existing value. For
 * refreshing tables, we need the user to tell us.
 */
public class BigDecimalUtils {
    private static final PrecisionAndScale EMPTY_TABLE_PRECISION_AND_SCALE = new PrecisionAndScale(1, 1);
    private static final int TARGET_CHUNK_SIZE = 4096;
    public static final int INVALID_PRECISION_OR_SCALE = -1;

    /**
     * Immutable way to store and pass precision and scale values.
     */
    public static class PrecisionAndScale {
        public final int precision;
        public final int scale;

        public PrecisionAndScale(final int precision, final int scale) {
            this.precision = precision;
            this.scale = scale;
        }
    }

    /**
     * Compute an overall precision and scale that would fit all existing values in a table.
     *
     * @param t a Deephaven table
     * @param colName a Column for {@code t}, which should be of {@code BigDecimal} type
     * @return a {@code PrecisionAndScale} object result.
     */
    public static PrecisionAndScale computePrecisionAndScale(
            final Table t, final String colName) {
        final ColumnSource src = t.getColumnSource(colName, BigDecimal.class);
        return computePrecisionAndScale(t.getRowSet(), src);
    }

    /**
     * Compute an overall precision and scale that would fit all existing values in a column source. Note that this
     * requires a full table scan to ensure the correct values are determined.
     *
     * @param rowSet The rowset for the provided column
     * @param source a {@code ColumnSource} of {@code BigDecimal} type
     * @return a {@code PrecisionAndScale} object result.
     */
    public static PrecisionAndScale computePrecisionAndScale(
            final RowSet rowSet,
            final ColumnSource source) {
        if (rowSet.isEmpty()) {
            return EMPTY_TABLE_PRECISION_AND_SCALE;
        }

        // We will walk the entire table to determine the max(precision - scale) and
        // max(scale), which corresponds to max(digits left of the decimal point), max(digits right of the decimal
        // point). Then we convert to (precision, scale) before returning.
        int maxPrecisionMinusScale = -1;
        int maxScale = -1;
        try (final ChunkSource.GetContext context = source.makeGetContext(TARGET_CHUNK_SIZE);
                final RowSequence.Iterator it = rowSet.getRowSequenceIterator()) {
            while (it.hasMore()) {
                final RowSequence rowSeq = it.getNextRowSequenceWithLength(TARGET_CHUNK_SIZE);
                final ObjectChunk chunk =
                        source.getChunk(context, rowSeq).asObjectChunk();
                for (int i = 0; i < chunk.size(); ++i) {
                    final BigDecimal x = chunk.get(i);
                    if (x == null) {
                        continue;
                    }

                    final int precision = x.precision();
                    final int scale = x.scale();
                    final int precisionMinusScale = precision - scale;
                    if (precisionMinusScale > maxPrecisionMinusScale) {
                        maxPrecisionMinusScale = precisionMinusScale;
                    }
                    if (scale > maxScale) {
                        maxScale = scale;
                    }
                }
            }
        }

        // If these are < 0, then every value we visited was null
        if (maxPrecisionMinusScale < 0 && maxScale < 0) {
            return EMPTY_TABLE_PRECISION_AND_SCALE;
        }

        return new PrecisionAndScale(maxPrecisionMinusScale + maxScale, maxScale);
    }

    /**
     * Immutable way to store and pass properties to get precision and scale for a given named column.
     */
    public static class PropertyNames {
        public final String columnName;
        public final String precisionProperty;
        public final String scaleProperty;

        public PropertyNames(final String columnName) {
            this.columnName = columnName;
            precisionProperty = columnName + ".precision";
            scaleProperty = columnName + ".scale";
        }
    }

    private static int getPrecisionAndScaleFromColumnProperties(
            final String columnName,
            final String property,
            final Properties columnProperties,
            final boolean allowNulls) {
        if (columnProperties == null) {
            return INVALID_PRECISION_OR_SCALE;
        }
        final String propertyValue = columnProperties.getProperty(property);
        if (propertyValue == null) {
            if (!allowNulls) {
                throw new IllegalArgumentException(
                        "column name '" + columnName + "' has type " + BigDecimal.class.getSimpleName() + "" +
                                " but no property '" + property + "' defined.");
            }
            return INVALID_PRECISION_OR_SCALE;
        }
        final int parsedResult;
        try {
            parsedResult = Integer.parseInt(propertyValue);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Couldn't parse as int value '" + propertyValue + "' for property " + property);
        }
        if (parsedResult < 1) {
            throw new IllegalArgumentException("Invalid value '" + parsedResult + "' for property " + property);
        }
        return parsedResult;
    }

    /**
     * Get a {@code PrecisionAndScale} value from a {@Properties} object.
     *
     * @param propertyNames The property names to read.
     * @param columnProperties The {@Properties} object from where to read the properties
     * @param allowNulls If true, do not throw when a property is missing, instead set the value to
     *        {@Code INVALID_PRECISION_OR_SCALE}
     * @return A {@PrecisionAndScale} object with the values read.
     */
    public static PrecisionAndScale getPrecisionAndScaleFromColumnProperties(
            final PropertyNames propertyNames,
            final Properties columnProperties,
            final boolean allowNulls) {
        final int precision = getPrecisionAndScaleFromColumnProperties(
                propertyNames.columnName,
                propertyNames.precisionProperty,
                columnProperties,
                allowNulls);
        final int scale = getPrecisionAndScaleFromColumnProperties(
                propertyNames.columnName,
                propertyNames.scaleProperty,
                columnProperties,
                allowNulls);
        return new PrecisionAndScale(precision, scale);
    }

    /**
     * Set the given names and values in the supplied {@code Properties} object.
     *
     * @param props Properties where the given property names and values would be set.
     * @param names Property names to set
     * @param values Property values to set
     */
    public static void setProperties(final Properties props, final PropertyNames names,
            final PrecisionAndScale values) {
        props.setProperty(names.precisionProperty, Integer.toString(values.precision));
        props.setProperty(names.scaleProperty, Integer.toString(values.scale));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy