Package com.foundationdb.server.types.service

Source Code of com.foundationdb.server.types.service.TCastsRegistry$ChainedCast

/**
* 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.service;

import com.foundationdb.server.error.AkibanInternalException;
import com.foundationdb.server.types.TCast;
import com.foundationdb.server.types.TCastIdentifier;
import com.foundationdb.server.types.TCastPath;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.TPreptimeValue;
import com.foundationdb.server.types.TStrongCasts;
import com.foundationdb.server.types.mcompat.mtypes.MString;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.server.types.texpressions.Constantness;
import com.foundationdb.util.DagChecker;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public final class TCastsRegistry {

    public TCast cast(TClass source, TClass target) {
        return cast(castsBySource, source, target);
    }

    public Set<TClass> stronglyCastableFrom(TClass tClass) {
        Map<TClass, TCast> castsFrom = strongCastsBySource.get(tClass);
        return castsFrom.keySet();
    }

    public boolean isStrong(TCast cast) {
        TClass source = cast.sourceClass();
        TClass target = cast.targetClass();
        return stronglyCastableFrom(source).contains(target);
    }

    Collection<Map<TClass, TCast>> castsBySource() {
        return castsBySource.values();
    }

    TCastsRegistry(Collection<? extends TClass> tClasses, InstanceFinder finder)
    {
        castsBySource = createCasts(tClasses, finder);
        createDerivedCasts(castsBySource, finder);
        deriveCastsFromVarchar();
        strongCastsBySource = createStrongCastsMap(castsBySource, finder);
        checkDag(strongCastsBySource);
    }

    private final Map<TClass,Map<TClass,TCast>> castsBySource;
    private final Map<TClass,Map<TClass,TCast>> strongCastsBySource;

    private static final Logger logger = LoggerFactory.getLogger(TCastsRegistry.class);
    private static final Comparator<TCastIdentifier> tcastIdentifierComparator = new Comparator<TCastIdentifier>() {
        @Override
        public int compare(TCastIdentifier o1, TCastIdentifier o2) {
            String o1Str = o1.toString();
            String o2Str = o2.toString();
            return o1Str.compareTo(o2Str);
        }
    };

    // package-local; also used in testing

    static Map<TClass, Map<TClass, TCast>> createCasts(Collection<? extends TClass> tClasses,
                                                       InstanceFinder finder) {
        Map<TClass, Map<TClass, TCast>> localCastsMap = new HashMap<>(tClasses.size());

        // First, define the self casts
        for (TClass tClass : tClasses) {
            Map<TClass, TCast> map = new HashMap<>();
            map.put(tClass, new SelfCast(tClass));
            localCastsMap.put(tClass, map);
        }

        Set<TCastIdentifier> duplicates = new TreeSet<>(tcastIdentifierComparator);

        // Next, to/from varchar
        for (TClass tClass : tClasses) {
            putCast(localCastsMap, tClass.castToVarchar(), duplicates);
            putCast(localCastsMap, tClass.castFromVarchar(), duplicates);
        }

        // Now the registered casts
        for (TCast cast : finder.find(TCast.class)) {
            putCast(localCastsMap, cast, duplicates);
        }

        if (!duplicates.isEmpty())
            throw new AkibanInternalException("duplicate casts found for: " + duplicates);
        return localCastsMap;
    }

    static Map<TClass, Map<TClass, TCast>> createStrongCastsMap(Map<TClass, Map<TClass, TCast>> castsBySource,
                                                                final Set<TCastIdentifier> strongCasts) {
        Map<TClass,Map<TClass,TCast>> result = new HashMap<>();
        for (Map.Entry<TClass, Map<TClass,TCast>> origEntry : castsBySource.entrySet()) {
            final TClass source = origEntry.getKey();
            Map<TClass, TCast> filteredView = Maps.filterKeys(origEntry.getValue(), new Predicate<TClass>() {
                @Override
                public boolean apply(TClass target) {
                    return (source == target) || strongCasts.contains(new TCastIdentifier(source, target));
                }
            });
            assert ! filteredView.isEmpty() : "no strong casts (including self casts) found for " + source;
            result.put(source, new HashMap<>(filteredView));
        }
        return result;
    }

    // private

    private static TCast cast(Map<TClass, Map<TClass, TCast>> castsBySource, TClass source, TClass target) {
        TCast result = null;
        Map<TClass,TCast> castsByTarget = castsBySource.get(source);
        if (castsByTarget != null)
            result = castsByTarget.get(target);
        return result;
    }

    private static void checkDag(final Map<TClass, Map<TClass, TCast>> castsBySource) {
        DagChecker<TClass> checker = new DagChecker<TClass>() {
            @Override
            protected Set<? extends TClass> initialNodes() {
                return castsBySource.keySet();
            }

            @Override
            protected Set<? extends TClass> nodesFrom(TClass starting) {
                Set<TClass> result = new HashSet<>(castsBySource.get(starting).keySet());
                result.remove(starting);
                return result;
            }
        };
        if (!checker.isDag()) {
            List<TClass> badPath = checker.getBadNodePath();
            // create a List<String> where everything is lowercase except for the first and last instances
            // of the offending node
            List<String> names = new ArrayList<>(badPath.size());
            for (TClass tClass : badPath)
                names.add(tClass.toString().toLowerCase());
            String lastName = names.get(names.size() - 1);
            String lastNameUpper = lastName.toUpperCase();
            names.set(names.size() - 1, lastNameUpper);
            names.set(names.indexOf(lastName), lastNameUpper);
            throw new AkibanInternalException("non-DAG detected involving " + names);
        }
    }

    static void createDerivedCasts(Map<TClass,Map<TClass,TCast>> castsBySource, InstanceFinder finder) {
        for (TCastPath castPath : finder.find(TCastPath.class)) {
            List<? extends TClass> path = castPath.getPath();
            // We need this loop to protect against "jumps." For instance, let's say the cast path is
            // [ a, b, c, d, e ] and we have the following casts:
            //  "single step" casts: (a -> b), (b -> c), (c -> d), (d -> e)
            //  one "jump" cast: (a -> d),
            // The first pass of this loop will create a derived cast (a -> d -> e), but we wouldn't have created
            // (a -> c). This loop ensures that we will.
            // We work from both ends, shrinking iteratively from the beginning and recursively (within deriveCast)
            // from the end. A derived cast has to have at least three participants, so we can stop when we get
            // to a path whose size is less than 3.
            while (path.size() >= 3) {
                for (int i = path.size() - 1; i > 0; --i) {
                    deriveCast(castsBySource, path, i);
                }
                path = path.subList(1, path.size());
            }
        }
    }

    private static Map<TClass,Map<TClass,TCast>> createStrongCastsMap(Map<TClass, Map<TClass, TCast>> castsBySource,
                                                                      InstanceFinder finder)
    {
        Collection<? extends TStrongCasts> strongCastIds = finder.find(TStrongCasts.class);
        Set<TCastIdentifier> strongCasts = new HashSet<>(strongCastIds.size()); // rough guess
        for (TStrongCasts strongCastGenerator : strongCastIds) {
            for (TCastIdentifier castId : strongCastGenerator.get()) {
                TCast cast = cast(castsBySource, castId.getSource(), castId.getTarget());
                if (cast == null)
                    throw new AkibanInternalException("no cast defined for " + castId +", which is marked as strong");
                if (!strongCasts.add(castId)) {
                    logger.warn("multiple sources have listed cast {} as strong", castId);
                }
            }
        }
        return createStrongCastsMap(castsBySource, strongCasts);
    }

    /**
     * Add derived casts for any pair of TClasses (A, B) s.t. there is not a cast from A to B, but there are casts
     * from A to VARCHAR and from VARCHAR to B. This essentially uses VARCHAR as a base type. Not pretty, but effective.
     * Uses the instance variable #castsBySource for its input and output; it must be initialized with at least
     * the self-casts and declared casts.
     */
    private void deriveCastsFromVarchar() {
        final TClass COMMON = MString.VARCHAR;
        Set<TClass> tClasses = castsBySource.keySet();
        for (Map.Entry<TClass, Map<TClass, TCast>> entry : castsBySource.entrySet()) {
            TClass source = entry.getKey();
            Map<TClass, TCast> castsByTarget = entry.getValue();
            for (TClass target : tClasses) {
                if (target == source || castsByTarget.containsKey(target))
                    continue;
                TCast sourceToVarchar = cast(source, COMMON);
                if (sourceToVarchar == null)
                    continue;
                TCast varcharToTarget = cast(COMMON, target);
                if (varcharToTarget == null)
                    continue;
                TCast derived = new ChainedCast(sourceToVarchar, varcharToTarget);
                castsByTarget.put(target, derived);
            }
        }
    }

    private static TCast deriveCast(Map<TClass,Map<TClass,TCast>> castsBySource,
                                    List<? extends TClass> path, int targetIndex) {
        TClass source = path.get(0);
        TClass target = path.get(targetIndex);
        TCast alreadyThere = cast(castsBySource, source,  target);
        if (alreadyThere != null)
            return alreadyThere;
        int intermediateIndex = targetIndex - 1;
        TClass intermediateClass = path.get(intermediateIndex);
        TCast second = cast(castsBySource, intermediateClass, target);
        if (second == null)
            throw new AkibanInternalException("no explicit cast between " + intermediateClass + " and " + target
                    + " while creating cast path: " + path);
        TCast first = deriveCast(castsBySource, path, intermediateIndex);
        if (first == null)
            throw new AkibanInternalException("couldn't derive cast between " + source + " and " + intermediateClass
                    + " while creating cast path: " + path);
        TCast result = new ChainedCast(first, second);
        putCast(castsBySource, result, null);
        return result;
    }

    private static void putCast(Map<TClass, Map<TClass, TCast>> toMap, TCast cast, Set<TCastIdentifier> duplicates) {
        if (cast == null)
            return;
        TClass source = cast.sourceClass();
        TClass target = cast.targetClass();
        Map<TClass,TCast> castsByTarget = toMap.get(source);
        TCast old = castsByTarget.put(target, cast);
        if (old != null) {
            logger.error("CAST({} AS {}): {} replaced by {} ", new Object[]{
                    source, target,  old.getClass(), cast.getClass()
            });
            if (duplicates == null)
                throw new AkibanInternalException("multiple casts defined from " + source + " to " + target);
            duplicates.add(new TCastIdentifier(source, target));
        }
    }

    // nested classes

    private static class SelfCast implements TCast {

        @Override
        public Constantness constness() {
            return Constantness.UNKNOWN;
        }

        @Override
        public TClass sourceClass() {
            return tClass;
        }

        @Override
        public TClass targetClass() {
            return tClass;
        }

        @Override
        public TInstance preferredTarget(TPreptimeValue source) {
            return source.type();
        }

        @Override
        public void evaluate(TExecutionContext context, ValueSource source, ValueTarget target) {
            if (source.isNull()) {
                target.putNull();
                return;
            }
            TInstance srcInst = context.inputTypeAt(0);
            TInstance dstInst = context.outputType();
            tClass.selfCast(context, srcInst, source,  dstInst, target);
        }

        SelfCast(TClass tClass) {
            this.tClass = tClass;
        }

        private final TClass tClass;
    }

    static class ChainedCast implements TCast {

        @Override
        public Constantness constness() {
            Constantness firstConst = first.constness();
            return (firstConst == second.constness()) ? firstConst : Constantness.UNKNOWN;
        }

        @Override
        public TClass sourceClass() {
            return first.sourceClass();
        }

        @Override
        public TClass targetClass() {
            return second.targetClass();
        }

        @Override
        public TInstance preferredTarget(TPreptimeValue source) {
            TInstance intermediateTInstance = first.preferredTarget(source);
            ValueSource firstValue = source.value();
            TInstance result;
            if (firstValue != null) {
                Value intermediateValue = new Value(first.targetClass().instance(true));
                TExecutionContext context = new TExecutionContext(
                        Collections.singletonList(source.type()),
                        intermediateTInstance,
                        null // TODO is this null a problem?
                );
                try {
                    first.evaluate(context, firstValue, intermediateValue);
                    result = second.preferredTarget(new TPreptimeValue(intermediateTInstance, intermediateValue));
                } catch (Exception e) {
                    logger.error("while evaluating intermediate value for " + source + " in " + this, e);
                    result = second.preferredTarget(new TPreptimeValue(intermediateTInstance));
                }
            }
            else {
                result = second.preferredTarget(new TPreptimeValue(intermediateTInstance));
            }
            return result;
        }

        @Override
        public void evaluate(TExecutionContext context, ValueSource source, ValueTarget target) {
            if (source.isNull()) {
                target.putNull();
                return;
            }
            Value tmp = (Value) context.exectimeObjectAt(TMP_PVALUE);
            if (tmp == null) {
                tmp = new Value(first.targetClass().instance(true));
                context.putExectimeObject(TMP_PVALUE, tmp);
            }
            // TODO cache
            TExecutionContext firstContext = context.deriveContext(
                    Collections.singletonList(context.inputTypeAt(0)),
                    intermediateType
            );
            TExecutionContext secondContext = context.deriveContext(
                    Collections.singletonList(intermediateType),
                    context.outputType()
            );

            first.evaluate(firstContext, source, tmp);
            second.evaluate(secondContext, tmp, target);
        }

        private ChainedCast(TCast first, TCast second) {
            if (first.targetClass() != second.sourceClass()) {
                throw new IllegalArgumentException("can't chain casts: " + first + " and " + second);
            }
            this.first = first;
            this.second = second;
            this.intermediateType = first.targetClass().instance(true);
        }

        private final TCast first;
        private final TCast second;
        private final TInstance intermediateType;
        private static final int TMP_PVALUE = 0;
    }
}
TOP

Related Classes of com.foundationdb.server.types.service.TCastsRegistry$ChainedCast

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.