com.teradata.tempto.internal.query.QueryResultValueComparator.java Source code

Java tutorial

Introduction

Here is the source code for com.teradata.tempto.internal.query.QueryResultValueComparator.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.teradata.tempto.internal.query;

import com.google.common.math.DoubleMath;
import com.google.common.primitives.UnsignedBytes;

import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import static java.util.Objects.isNull;

/**
 * Comparator for query result values. It should be instantiated per column with given JDBCType.
 * This comparator should only be used for equality comparison. It tries to "non-strictly"
 * compare values by upcasting numerical values to long/double.
 */
public class QueryResultValueComparator implements Comparator<Object> {

    private static final double DOUBLE_FUZZY_MATCH_THRESHOLD = 0.00000000001;
    private final JDBCType type;

    private QueryResultValueComparator(JDBCType type) {
        this.type = type;
    }

    public static QueryResultValueComparator comparatorForType(JDBCType type) {
        return new QueryResultValueComparator(type);
    }

    @Override
    public int compare(Object actual, Object expected) {
        if (isNull(actual) && isNull(expected)) {
            return 0;
        }
        if (isNull(actual) ^ isNull(expected)) {
            return -1;
        }
        switch (type) {
        case CHAR:
        case VARCHAR:
        case LONGVARCHAR:
        case LONGNVARCHAR:
            return stringsEqual(actual, expected);
        case BINARY:
        case VARBINARY:
        case LONGVARBINARY:
            return binaryEqual(actual, expected);
        case BIT:
        case BOOLEAN:
            return booleanEqual(actual, expected);
        case TINYINT:
        case SMALLINT:
        case INTEGER:
        case BIGINT:
            return longEqual(actual, expected);
        case REAL:
        case FLOAT:
        case DOUBLE:
            return doubleEqual(actual, expected);
        case DECIMAL:
        case NUMERIC:
            return bigDecimalEqual(actual, expected);
        case DATE:
            return dateEqual(actual, expected);
        case TIME:
        case TIME_WITH_TIMEZONE:
            return timeEqual(actual, expected);
        case TIMESTAMP:
        case TIMESTAMP_WITH_TIMEZONE:
            return timestampEqual(actual, expected);
        case ARRAY:
            return arrayEqual(actual, expected);
        default:
            throw new RuntimeException("Unsupported sql type " + type);
        }
    }

    private int arrayEqual(Object actual, Object expected) {
        if (actual instanceof Array && expected instanceof List) {
            Array actualArray = (Array) actual;
            QueryResultValueComparator elementComparator;
            elementComparator = comparatorForArrayElements(actualArray);
            List actualList = arrayAsList(actualArray);
            List expectedList = (List) expected;

            if (actualList.size() != expectedList.size()) {
                return -1;
            }

            for (int i = 0; i < actualList.size(); ++i) {
                Object actualValue = actualList.get(i);
                Object expectedValue = expectedList.get(i);
                int compareResult = elementComparator.compare(actualValue, expectedValue);
                if (compareResult != 0) {
                    return compareResult;
                }
            }
            return 0;
        }
        return -1;
    }

    private QueryResultValueComparator comparatorForArrayElements(Array actualArray) {
        QueryResultValueComparator elementComparator;
        try {
            elementComparator = comparatorForType(JDBCType.valueOf(actualArray.getBaseType()));
        } catch (SQLException e) {
            throw new RuntimeException();
        }
        return elementComparator;
    }

    private List arrayAsList(Array array) {
        try {
            return Arrays.asList((Object[]) array.getArray());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private int stringsEqual(Object actual, Object expected) {
        if (actual instanceof String && expected instanceof String) {
            return ((String) actual).compareTo((String) expected);
        }
        return -1;
    }

    private int binaryEqual(Object actual, Object expected) {
        if (actual instanceof byte[] && expected instanceof byte[]) {
            return UnsignedBytes.lexicographicalComparator().compare((byte[]) actual, (byte[]) expected);
        }
        return -1;
    }

    private int booleanEqual(Object actual, Object expected) {
        if (actual instanceof Boolean && expected instanceof Boolean) {
            return ((Boolean) actual).compareTo((Boolean) expected);
        }
        return -1;
    }

    private int longEqual(Object actual, Object expected) {
        if (isIntegerValue(actual) && isIntegerValue(expected)) {
            Long actualLong = Long.valueOf(actual.toString());
            Long expectedLong = Long.valueOf(expected.toString());
            return actualLong.compareTo(expectedLong);
        }
        return -1;
    }

    private int doubleEqual(Object actual, Object expected) {
        if (isFloatingPointValue(actual) && isFloatingPointValue(expected)) {
            Double actualDouble = Double.valueOf(actual.toString());
            Double expectedDouble = Double.valueOf(expected.toString());
            Double threshold = expectedDouble * DOUBLE_FUZZY_MATCH_THRESHOLD;
            return DoubleMath.fuzzyCompare(actualDouble, expectedDouble, threshold);
        }
        return -1;
    }

    private int bigDecimalEqual(Object actual, Object expected) {
        if (actual instanceof BigDecimal && expected instanceof BigDecimal) {
            return ((BigDecimal) actual).compareTo((BigDecimal) expected);
        }
        return -1;
    }

    private int dateEqual(Object actual, Object expected) {
        if (actual instanceof Date && expected instanceof Date) {
            return ((Date) actual).compareTo((Date) expected);
        }
        return -1;
    }

    private int timeEqual(Object actual, Object expected) {
        if (actual instanceof Time && expected instanceof Time) {
            return ((Time) actual).compareTo((Time) expected);
        }
        return -1;
    }

    private int timestampEqual(Object actual, Object expected) {
        if (actual instanceof Timestamp && expected instanceof Timestamp) {
            return ((Timestamp) actual).compareTo((Timestamp) expected);
        }
        return -1;
    }

    private boolean isIntegerValue(Object value) {
        return (value instanceof Long || value instanceof Integer || value instanceof Short
                || value instanceof Byte);
    }

    private boolean isFloatingPointValue(Object value) {
        return (value instanceof Float || value instanceof Double);
    }
}