PinotUtils.java

/*
 * 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.facebook.presto.pinot;

import org.apache.pinot.spi.utils.TimestampUtils;

import java.util.Optional;
import java.util.function.Function;

import static com.facebook.presto.pinot.PinotErrorCode.PINOT_DECODE_ERROR;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Long.parseLong;
import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
import static java.net.HttpURLConnection.HTTP_OK;

public class PinotUtils
{
    private static final String PINOT_INFINITY = "���";
    private static final String PINOT_POSITIVE_INFINITY = "+" + PINOT_INFINITY;
    private static final String PINOT_NEGATIVE_INFINITY = "-" + PINOT_INFINITY;
    private static final Double PRESTO_INFINITY = Double.POSITIVE_INFINITY;
    private static final Double PRESTO_NEGATIVE_INFINITY = Double.NEGATIVE_INFINITY;

    private PinotUtils()
    {
    }

    static boolean isValidPinotHttpResponseCode(int status)
    {
        return status >= HTTP_OK && status < HTTP_MULT_CHOICE;
    }

    public static <T> T doWithRetries(int retries, Function<Integer, T> caller)
    {
        PinotException firstError = null;
        checkState(retries > 0, "Invalid num of retries %d", retries);
        for (int i = 0; i < retries; ++i) {
            try {
                return caller.apply(i);
            }
            catch (PinotException e) {
                if (firstError == null) {
                    firstError = e;
                }
                if (!e.getPinotErrorCode().isRetriable()) {
                    throw e;
                }
            }
        }
        throw firstError;
    }

    public static long parseTimestamp(String value)
    {
        try {
            return parseLong(value);
        }
        catch (NumberFormatException e) {
            // Sometimes Pinot returns float point string in a field that we expect timestamp.
            // This can happen because of JsonParser, Pinot Query Engine, etc.
            // In this case we may still go through by reading a float value
            try {
                return parseDouble(value).longValue();
            }
            catch (Exception ignoredEx) {
                return TimestampUtils.toMillisSinceEpoch(value);
            }
        }
    }

    public static Double parseDouble(String value)
    {
        try {
            return Double.valueOf(value);
        }
        catch (NumberFormatException ne) {
            switch (value) {
                case PINOT_INFINITY:
                case PINOT_POSITIVE_INFINITY:
                    return PRESTO_INFINITY;
                case PINOT_NEGATIVE_INFINITY:
                    return PRESTO_NEGATIVE_INFINITY;
            }
            throw new PinotException(PINOT_DECODE_ERROR, Optional.empty(), "Cannot decode double value from pinot " + value, ne);
        }
    }
}