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

io.deephaven.server.table.ops.AggSpecAdapter Maven / Gradle / Ivy

The newest version!
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.server.table.ops;

import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import com.google.rpc.Code;
import io.deephaven.api.ColumnName;
import io.deephaven.api.SortColumn;
import io.deephaven.api.agg.spec.AggSpec;
import io.deephaven.api.agg.spec.AggSpec.Visitor;
import io.deephaven.api.agg.spec.AggSpecSortedFirst;
import io.deephaven.api.agg.spec.AggSpecSortedLast;
import io.deephaven.api.agg.spec.AggSpecWAvg;
import io.deephaven.api.agg.spec.AggSpecWSum;
import io.deephaven.api.object.UnionObject;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecAbsSum;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecApproximatePercentile;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecAvg;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecCountDistinct;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecDistinct;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecFirst;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecFormula;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecFreeze;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecGroup;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecLast;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecMax;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecMedian;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecMin;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecNonUniqueSentinel;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecPercentile;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecSorted;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecSortedColumn;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecStd;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecSum;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecTDigest;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecUnique;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecVar;
import io.deephaven.proto.backplane.grpc.AggSpec.AggSpecWeighted;
import io.deephaven.proto.backplane.grpc.AggSpec.TypeCase;
import io.deephaven.proto.backplane.grpc.NullValue;
import io.deephaven.proto.util.Exceptions;
import io.deephaven.server.grpc.GrpcErrorHelper;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static io.deephaven.server.grpc.GrpcErrorHelper.extractField;

class AggSpecAdapter {

    enum Singleton {
        INSTANCE;

        private final Adapters adapters;

        Singleton() {
            adapters = new Adapters();
            AggSpec.visitAll(adapters);
        }

        Adapters adapters() {
            return adapters;
        }
    }

    public static void validate(io.deephaven.proto.backplane.grpc.AggSpec spec) {
        // It's a bit unfortunate that generated protobuf objects don't have the names as constants (like it does with
        // field numbers). For example, AggSpec.TYPE_NAME.
        GrpcErrorHelper.checkHasOneOf(spec, "type");
        GrpcErrorHelper.checkHasNoUnknownFields(spec);
        Singleton.INSTANCE.adapters.validate(spec);
    }

    public static void validate(AggSpecUnique aggSpecUnique) {
        GrpcErrorHelper.checkHasNoUnknownFields(aggSpecUnique);
        GrpcErrorHelper.checkHasField(aggSpecUnique, AggSpecUnique.NON_UNIQUE_SENTINEL_FIELD_NUMBER);
        validate(aggSpecUnique.getNonUniqueSentinel());
    }

    public static void validate(AggSpecNonUniqueSentinel nonUniqueSentinel) {
        GrpcErrorHelper.checkHasOneOf(nonUniqueSentinel, "type");
        GrpcErrorHelper.checkHasNoUnknownFieldsRecursive(nonUniqueSentinel);
        final AggSpecNonUniqueSentinel.TypeCase type = nonUniqueSentinel.getTypeCase();
        switch (type) {
            case STRING_VALUE:
            case INT_VALUE:
            case LONG_VALUE:
            case FLOAT_VALUE:
            case DOUBLE_VALUE:
            case BOOL_VALUE:
                // All native proto types valid
                return;
            case NULL_VALUE:
                if (nonUniqueSentinel.getNullValue() != NullValue.NULL_VALUE) {
                    throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
                            "AggSpecNonUniqueSentinel null_value out of range");
                }
                return;
            case BYTE_VALUE:
                if (nonUniqueSentinel.getByteValue() != (byte) nonUniqueSentinel.getByteValue()) {
                    throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
                            "AggSpecNonUniqueSentinel byte_value out of range");
                }
                return;
            case SHORT_VALUE:
                if (nonUniqueSentinel.getShortValue() != (short) nonUniqueSentinel.getShortValue()) {
                    throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
                            "AggSpecNonUniqueSentinel short_value out of range");
                }
                return;
            case CHAR_VALUE:
                if (nonUniqueSentinel.getCharValue() != (char) nonUniqueSentinel.getCharValue()) {
                    throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
                            "AggSpecNonUniqueSentinel char_value out of range");
                }
                return;
            case TYPE_NOT_SET:
                // Should be caught by checkHasOneOf, fall-through to internal error if not.
            default:
                throw Exceptions.statusRuntimeException(Code.INTERNAL,
                        String.format("Server missing AggSpecNonUniqueSentinel type %s", type));
        }
    }

    public static AggSpec adapt(io.deephaven.proto.backplane.grpc.AggSpec spec) {
        return Singleton.INSTANCE.adapters.adapt(spec);
    }

    private static io.deephaven.api.agg.spec.AggSpecApproximatePercentile adapt(
            AggSpecApproximatePercentile percentile) {
        return percentile.hasCompression()
                ? AggSpec.approximatePercentile(percentile.getPercentile(), percentile.getCompression())
                : AggSpec.approximatePercentile(percentile.getPercentile());
    }

    private static io.deephaven.api.agg.spec.AggSpecCountDistinct adapt(AggSpecCountDistinct countDistinct) {
        return AggSpec.countDistinct(countDistinct.getCountNulls());
    }

    private static io.deephaven.api.agg.spec.AggSpecDistinct adapt(AggSpecDistinct distinct) {
        return AggSpec.distinct(distinct.getIncludeNulls());
    }

    private static io.deephaven.api.agg.spec.AggSpecFormula adapt(AggSpecFormula formula) {
        return AggSpec.formula(formula.getFormula(), formula.getParamToken());
    }

    private static io.deephaven.api.agg.spec.AggSpecMedian adapt(AggSpecMedian median) {
        return AggSpec.median(median.getAverageEvenlyDivided());
    }

    private static io.deephaven.api.agg.spec.AggSpecPercentile adapt(AggSpecPercentile percentile) {
        return AggSpec.percentile(percentile.getPercentile(), percentile.getAverageEvenlyDivided());
    }

    private static SortColumn adapt(AggSpecSortedColumn sortedColumn) {
        return SortColumn.asc(ColumnName.of(sortedColumn.getColumnName()));
    }

    private static AggSpecSortedFirst adaptFirst(AggSpecSorted sorted) {
        AggSpecSortedFirst.Builder builder = AggSpecSortedFirst.builder();
        for (AggSpecSortedColumn sortedColumn : sorted.getColumnsList()) {
            builder.addColumns(adapt(sortedColumn));
        }
        return builder.build();
    }

    private static AggSpecSortedLast adaptLast(AggSpecSorted sorted) {
        AggSpecSortedLast.Builder builder = AggSpecSortedLast.builder();
        for (AggSpecSortedColumn sortDescriptor : sorted.getColumnsList()) {
            builder.addColumns(adapt(sortDescriptor));
        }
        return builder.build();
    }

    private static io.deephaven.api.agg.spec.AggSpecTDigest adapt(AggSpecTDigest tDigest) {
        return tDigest.hasCompression() ? AggSpec.tDigest(tDigest.getCompression()) : AggSpec.tDigest();
    }

    private static io.deephaven.api.agg.spec.AggSpecUnique adapt(AggSpecUnique unique) {
        UnionObject nonUniqueSentinel = adapt(unique.getNonUniqueSentinel());
        return io.deephaven.api.agg.spec.AggSpecUnique.of(unique.getIncludeNulls(), nonUniqueSentinel);
    }

    private static UnionObject adapt(AggSpecNonUniqueSentinel nonUniqueSentinel) {
        switch (nonUniqueSentinel.getTypeCase()) {
            case NULL_VALUE:
                return null;
            case STRING_VALUE:
                return UnionObject.of(nonUniqueSentinel.getStringValue());
            case INT_VALUE:
                return UnionObject.of(nonUniqueSentinel.getIntValue());
            case LONG_VALUE:
                return UnionObject.of(nonUniqueSentinel.getLongValue());
            case FLOAT_VALUE:
                return UnionObject.of(nonUniqueSentinel.getFloatValue());
            case DOUBLE_VALUE:
                return UnionObject.of(nonUniqueSentinel.getDoubleValue());
            case BOOL_VALUE:
                return UnionObject.of(nonUniqueSentinel.getBoolValue());
            case BYTE_VALUE:
                return UnionObject.of((byte) nonUniqueSentinel.getByteValue());
            case SHORT_VALUE:
                return UnionObject.of((short) nonUniqueSentinel.getShortValue());
            case CHAR_VALUE:
                return UnionObject.of((char) nonUniqueSentinel.getCharValue());
            case TYPE_NOT_SET:
                throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "AggSpecNonUniqueSentinel type not set");
            default:
                throw Exceptions.statusRuntimeException(Code.INTERNAL, String
                        .format("Server is missing AggSpecNonUniqueSentinel case %s", nonUniqueSentinel.getTypeCase()));
        }
    }

    private static AggSpecWAvg adaptWeightedAvg(AggSpecWeighted weighted) {
        return AggSpec.wavg(weighted.getWeightColumn());
    }

    private static AggSpecWSum adaptWeightedSum(AggSpecWeighted weighted) {
        return AggSpec.wsum(weighted.getWeightColumn());
    }

    static class Adapters implements Visitor {
        final Map> adapters = new HashMap<>();
        final Set unimplemented = new HashSet<>();

        public void validate(io.deephaven.proto.backplane.grpc.AggSpec spec) {
            get(spec.getTypeCase()).validate(spec);
        }

        public AggSpec adapt(io.deephaven.proto.backplane.grpc.AggSpec spec) {
            return get(spec.getTypeCase()).adapt(spec);
        }

        private Adapter get(TypeCase type) {
            final Adapter adapter = adapters.get(type);
            if (adapter != null) {
                return adapter;
            }
            if (unimplemented.contains(type)) {
                throw Exceptions.statusRuntimeException(Code.UNIMPLEMENTED,
                        String.format("AggSpec type %s is unimplemented", type));
            }
            throw Exceptions.statusRuntimeException(Code.INTERNAL,
                    String.format("Server is missing AggSpec type %s", type));
        }

        private  void add(
                TypeCase typeCase,
                Class iClazz,
                // Used to help with type-safety
                @SuppressWarnings("unused") Class tClazz,
                Consumer validator,
                Function adapter) {
            try {
                if (adapters.put(typeCase, Adapter.create(typeCase, iClazz, validator, adapter)) != null) {
                    throw new IllegalStateException("Adapters have been constructed incorrectly");
                }
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                throw new IllegalStateException("Adapters logical error", e);
            }
        }

        private  void add(
                TypeCase typeCase,
                Class iClazz,
                // Used to help with type-safety
                @SuppressWarnings("unused") Class tClazz,
                Consumer validator,
                Supplier supplier) {
            add(typeCase, iClazz, tClazz, validator, m -> supplier.get());
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecAbsSum absSum) {
            add(
                    TypeCase.ABS_SUM,
                    AggSpecAbsSum.class,
                    io.deephaven.api.agg.spec.AggSpecAbsSum.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::absSum);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecApproximatePercentile approxPct) {
            add(
                    TypeCase.APPROXIMATE_PERCENTILE,
                    AggSpecApproximatePercentile.class,
                    io.deephaven.api.agg.spec.AggSpecApproximatePercentile.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecAvg avg) {
            add(
                    TypeCase.AVG,
                    AggSpecAvg.class,
                    io.deephaven.api.agg.spec.AggSpecAvg.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::avg);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecCountDistinct countDistinct) {
            add(
                    TypeCase.COUNT_DISTINCT,
                    AggSpecCountDistinct.class,
                    io.deephaven.api.agg.spec.AggSpecCountDistinct.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecDistinct distinct) {
            add(
                    TypeCase.DISTINCT,
                    AggSpecDistinct.class,
                    io.deephaven.api.agg.spec.AggSpecDistinct.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecFirst first) {
            add(
                    TypeCase.FIRST,
                    AggSpecFirst.class,
                    io.deephaven.api.agg.spec.AggSpecFirst.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::first);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecFormula formula) {
            add(
                    TypeCase.FORMULA,
                    AggSpecFormula.class,
                    io.deephaven.api.agg.spec.AggSpecFormula.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecFreeze freeze) {
            add(
                    TypeCase.FREEZE,
                    AggSpecFreeze.class,
                    io.deephaven.api.agg.spec.AggSpecFreeze.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::freeze);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecGroup group) {
            add(
                    TypeCase.GROUP,
                    AggSpecGroup.class,
                    io.deephaven.api.agg.spec.AggSpecGroup.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::group);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecLast last) {
            add(
                    TypeCase.LAST,
                    AggSpecLast.class,
                    io.deephaven.api.agg.spec.AggSpecLast.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::last);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecMax max) {
            add(
                    TypeCase.MAX,
                    AggSpecMax.class,
                    io.deephaven.api.agg.spec.AggSpecMax.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::max);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecMedian median) {
            add(
                    TypeCase.MEDIAN,
                    AggSpecMedian.class,
                    io.deephaven.api.agg.spec.AggSpecMedian.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecMin min) {
            add(
                    TypeCase.MIN,
                    AggSpecMin.class,
                    io.deephaven.api.agg.spec.AggSpecMin.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::min);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecPercentile pct) {
            add(
                    TypeCase.PERCENTILE,
                    AggSpecPercentile.class,
                    io.deephaven.api.agg.spec.AggSpecPercentile.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(AggSpecSortedFirst sortedFirst) {
            add(
                    TypeCase.SORTED_FIRST,
                    AggSpecSorted.class,
                    AggSpecSortedFirst.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adaptFirst);
        }

        @Override
        public void visit(AggSpecSortedLast sortedLast) {
            add(
                    TypeCase.SORTED_LAST,
                    AggSpecSorted.class,
                    AggSpecSortedLast.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adaptLast);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecStd std) {
            add(
                    TypeCase.STD,
                    AggSpecStd.class,
                    io.deephaven.api.agg.spec.AggSpecStd.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::std);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecSum sum) {
            add(
                    TypeCase.SUM,
                    AggSpecSum.class,
                    io.deephaven.api.agg.spec.AggSpecSum.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::sum);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecTDigest tDigest) {
            add(
                    TypeCase.T_DIGEST,
                    AggSpecTDigest.class,
                    io.deephaven.api.agg.spec.AggSpecTDigest.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecUnique unique) {
            add(
                    TypeCase.UNIQUE,
                    AggSpecUnique.class,
                    io.deephaven.api.agg.spec.AggSpecUnique.class,
                    AggSpecAdapter::validate,
                    AggSpecAdapter::adapt);
        }

        @Override
        public void visit(AggSpecWAvg wAvg) {
            add(
                    TypeCase.WEIGHTED_AVG,
                    AggSpecWeighted.class,
                    AggSpecWAvg.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adaptWeightedAvg);
        }

        @Override
        public void visit(AggSpecWSum wSum) {
            add(
                    TypeCase.WEIGHTED_SUM,
                    AggSpecWeighted.class,
                    AggSpecWSum.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpecAdapter::adaptWeightedSum);
        }

        @Override
        public void visit(io.deephaven.api.agg.spec.AggSpecVar var) {
            add(
                    TypeCase.VAR,
                    AggSpecVar.class,
                    io.deephaven.api.agg.spec.AggSpecVar.class,
                    GrpcErrorHelper::checkHasNoUnknownFieldsRecursive,
                    AggSpec::var);

        }

        // New types _can_ be added as unimplemented.
        // If so, please create and link to a ticket.
        // @Override
        // public void visit(SomeNewType someNewType) {
        // unimplemented.add(TypeCase.SOME_NEW_TYPE);
        // }
    }

    private static class Adapter {

        public static  Adapter create(
                TypeCase typeCase,
                Class clazz,
                Consumer validator,
                Function adapter)
                throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            final FieldDescriptor field = extractField(io.deephaven.proto.backplane.grpc.AggSpec.getDescriptor(),
                    typeCase.getNumber(), clazz);
            return new Adapter<>(field, validator, adapter);
        }

        private final FieldDescriptor field;
        private final Consumer validator;
        private final Function adapter;

        private Adapter(FieldDescriptor field, Consumer validator, Function adapter) {
            this.field = field;
            this.validator = Objects.requireNonNull(validator);
            this.adapter = Objects.requireNonNull(adapter);
        }

        public void validate(io.deephaven.proto.backplane.grpc.AggSpec spec) {
            // noinspection unchecked
            validator.accept((I) spec.getField(field));
        }

        public T adapt(io.deephaven.proto.backplane.grpc.AggSpec spec) {
            // noinspection unchecked
            return adapter.apply((I) spec.getField(field));
        }
    }
}