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

com.taosdata.jdbc.rs.RestfulResultSet Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
package com.taosdata.jdbc.rs;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import com.taosdata.jdbc.AbstractResultSet;
import com.taosdata.jdbc.TSDBConstants;
import com.taosdata.jdbc.TSDBError;
import com.taosdata.jdbc.TSDBErrorNumbers;
import com.taosdata.jdbc.enums.DataType;
import com.taosdata.jdbc.enums.TimestampPrecision;
import com.taosdata.jdbc.utils.Utils;

import java.math.BigDecimal;
import java.sql.*;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RestfulResultSet extends AbstractResultSet {

    public static DateTimeFormatter rfc3339Parser = new DateTimeFormatterBuilder()
            .parseCaseInsensitive()
            .appendValue(ChronoField.YEAR, 4)
            .appendLiteral('-')
            .appendValue(ChronoField.MONTH_OF_YEAR, 2)
            .appendLiteral('-')
            .appendValue(ChronoField.DAY_OF_MONTH, 2)
            .appendLiteral('T')
            .appendValue(ChronoField.HOUR_OF_DAY, 2)
            .appendLiteral(':')
            .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
            .appendLiteral(':')
            .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
            .optionalStart()
            .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
            .optionalEnd()
            .appendOffset("+HHMM", "Z").toFormatter()
            .withResolverStyle(ResolverStyle.STRICT)
            .withChronology(IsoChronology.INSTANCE);

    Pattern pattern = Pattern.compile("^[0-9a-zT\\-:]+\\.([0-9]+).*$");

    private final Statement statement;
    // data
    private final List> resultSet = new ArrayList<>();
    // meta
    private final List columnNames = new ArrayList<>();
    private final List columns = new ArrayList<>();
    private final RestfulResultSetMetaData metaData;

    private volatile boolean isClosed;
    private int pos = -1;

    /**
     * 由一个result的Json构造结果集,对应执行show databases, show tables等这些语句,返回结果集,但无法获取结果集对应的meta,统一当成String处理
     *
     * @param resultJson: 包含data信息的结果集,有sql返回的结果集
     ***/
    public RestfulResultSet(String database, Statement statement, JSONObject resultJson) throws SQLException {
        this.statement = statement;

        // get column metadata
        JSONArray columnMeta = resultJson.getJSONArray("column_meta");
        // get row data
        JSONArray data = resultJson.getJSONArray("data");

        // parse column_meta
        parseColumnMeta_new(columnMeta);
        this.metaData = new RestfulResultSetMetaData(database, columns);

        if (data == null || data.isEmpty())
            return;
        // parse row data
        for (int rowIndex = 0; rowIndex < data.size(); rowIndex++) {
            List row = new ArrayList<>();
            JSONArray jsonRow = data.getJSONArray(rowIndex);
            for (int colIndex = 0; colIndex < this.metaData.getColumnCount(); colIndex++) {
                row.add(parseColumnData(jsonRow, colIndex, columns.get(colIndex)));
            }
            resultSet.add(row);
        }
    }

    /***
     * use this method after TDengine-2.0.18.0 to parse column meta, restful add column_meta in resultSet
     * @Param columnMeta
     */
    private void parseColumnMeta_new(JSONArray columnMeta) {
        columnNames.clear();
        columns.clear();
        for (int colIndex = 0; colIndex < columnMeta.size(); colIndex++) {
            JSONArray col = columnMeta.getJSONArray(colIndex);
            String col_name = col.getString(0);
            String typeName = col.getString(1);
            DataType type = DataType.getDataType(typeName);
            int col_type = type.getJdbcTypeValue();
            int col_length = col.getInteger(2);
            columnNames.add(col_name);
            columns.add(new Field(col_name, col_type, col_length, "", type.getTaosTypeValue()));
        }
    }

    private Object parseColumnData(JSONArray row, int colIndex, Field field) throws SQLException {
        int taosType = field.taos_type;
        switch (taosType) {
            case TSDBConstants.TSDB_DATA_TYPE_NULL:
                return null;
            case TSDBConstants.TSDB_DATA_TYPE_BOOL:
                return row.getBoolean(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_TINYINT:
                return row.getByte(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_SMALLINT:
                return row.getShort(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_INT:
                return row.getInteger(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_BIGINT:
                return row.getLong(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_FLOAT:
                return row.getFloat(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_DOUBLE:
                return row.getDouble(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_TIMESTAMP:
                return parseTimestampColumnData(row, colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_BINARY:
                int type = field.type;
                if (Types.BINARY == type) {
                    return row.getString(colIndex) == null ? null : row.getString(colIndex).getBytes();
                }else {
                    return row.getString(colIndex) == null ? null : row.getString(colIndex);
                }
            case TSDBConstants.TSDB_DATA_TYPE_NCHAR:
                return row.getString(colIndex) == null ? null : row.getString(colIndex);
            case TSDBConstants.TSDB_DATA_TYPE_JSON:
                //  all json tag or just a json tag value
                return row.get(colIndex) != null && (row.get(colIndex) instanceof String || row.get(colIndex) instanceof JSONObject)
                        ? JSON.toJSONString(row.get(colIndex), SerializerFeature.WriteMapNullValue)
                        : row.get(colIndex);
            default:
                return row.get(colIndex);
        }
    }

    public Timestamp parseTimestampColumnData(JSONArray row, int colIndex) throws SQLException {
        if (row.get(colIndex) == null)
            return null;

        String value = row.getString(colIndex);
        int index = value.lastIndexOf(":");
        // ns timestamp: yyyy-MM-ddTHH:mm:ss.SSSSSSSSS+0x:00
        if (index > 19) {
            // ns timestamp: yyyy-MM-ddTHH:mm:ss.SSSSSSSSS+0x00
            value = value.substring(0, index) + value.substring(index + 1);
        }
        ZonedDateTime parse = ZonedDateTime.parse(value, rfc3339Parser);
        Matcher matcher = pattern.matcher(value);
        int len = 0;
        if (matcher.find()) {
            len = matcher.group(1).length();
        }
        if (len > 6) {
            this.timestampPrecision = TimestampPrecision.NS;
        } else if (len > 3) {
            this.timestampPrecision = TimestampPrecision.US;
        } else {
            this.timestampPrecision = TimestampPrecision.MS;
        }
        return Timestamp.from(parse.toInstant());

    }

    public static class Field {
        String name;
        int type;
        int length;
        String note;
        int taos_type;

        public Field(String name, int type, int length, String note, int taos_type) {
            this.name = name;
            this.type = type;
            this.length = length;
            this.note = note;
            this.taos_type = taos_type;
        }

        public int getTaosType() {
            return taos_type;
        }
    }

    @Override
    public boolean next() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        pos++;
        return pos <= resultSet.size() - 1;
    }

    @Override
    public void close() throws SQLException {
        synchronized (RestfulResultSet.class) {
            this.isClosed = true;
        }
    }

    @Override
    public String getString(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return null;
        if (value instanceof byte[])
            return new String((byte[]) value);
        return value.toString();
    }

    @Override
    public boolean getBoolean(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return false;
        if (value instanceof Boolean)
            return (boolean) value;
        return Boolean.parseBoolean(value.toString());
    }

    @Override
    public byte getByte(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return 0;
        long valueAsLong = Long.parseLong(value.toString());
        if (valueAsLong == Byte.MIN_VALUE)
            return 0;
        if (valueAsLong < Byte.MIN_VALUE || valueAsLong > Byte.MAX_VALUE)
            throwRangeException(value.toString(), columnIndex, Types.TINYINT);

        return (byte) valueAsLong;
    }

    private void throwRangeException(String valueAsString, int columnIndex, int jdbcType) throws SQLException {
        throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_NUMERIC_VALUE_OUT_OF_RANGE,
                "'" + valueAsString + "' in column '" + columnIndex + "' is outside valid range for the jdbcType " + TSDBConstants.jdbcType2TaosTypeName(jdbcType));
    }

    @Override
    public short getShort(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return 0;
        long valueAsLong = Long.parseLong(value.toString());
        if (valueAsLong == Short.MIN_VALUE)
            return 0;
        if (valueAsLong < Short.MIN_VALUE || valueAsLong > Short.MAX_VALUE)
            throwRangeException(value.toString(), columnIndex, Types.SMALLINT);
        return (short) valueAsLong;
    }

    @Override
    public int getInt(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return 0;
        long valueAsLong = Long.parseLong(value.toString());
        if (valueAsLong == Integer.MIN_VALUE)
            return 0;
        if (valueAsLong < Integer.MIN_VALUE || valueAsLong > Integer.MAX_VALUE)
            throwRangeException(value.toString(), columnIndex, Types.INTEGER);
        return (int) valueAsLong;
    }

    @Override
    public long getLong(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return 0;
        if (value instanceof Timestamp) {
            Timestamp ts = (Timestamp) value;
            switch (this.timestampPrecision) {
                case TimestampPrecision.MS:
                default:
                    return ts.getTime();
                case TimestampPrecision.US:
                    return ts.getTime() * 1000 + ts.getNanos() / 1000 % 1000;
                case TimestampPrecision.NS:
                    return ts.getTime() * 1000_000 + ts.getNanos() % 1000_000;
            }
        }
        long valueAsLong = 0;
        try {
            valueAsLong = Long.parseLong(value.toString());
            if (valueAsLong == Long.MIN_VALUE)
                return 0;
        } catch (NumberFormatException e) {
            throwRangeException(value.toString(), columnIndex, Types.BIGINT);
        }
        return valueAsLong;
    }

    @Override
    public float getFloat(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return 0;
        if (value instanceof Float)
            return (float) value;
        if (value instanceof Double)
            return new Float((Double) value);
        return Float.parseFloat(value.toString());
    }

    @Override
    public double getDouble(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null) {
            return 0;
        }
        if (value instanceof Double)
            return (double) value;
        if (value instanceof Float)
            return (float) value;
        return Double.parseDouble(value.toString());
    }

    @Override
    public byte[] getBytes(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return null;
        if (value instanceof byte[])
            return (byte[]) value;
        if (value instanceof String)
            return ((String) value).getBytes();
        if (value instanceof Long)
            return Longs.toByteArray((long) value);
        if (value instanceof Integer)
            return Ints.toByteArray((int) value);
        if (value instanceof Short)
            return Shorts.toByteArray((short) value);
        if (value instanceof Byte)
            return new byte[]{(byte) value};

        return value.toString().getBytes();
    }

    @Override
    public Date getDate(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return null;
        if (value instanceof Timestamp)
            return new Date(((Timestamp) value).getTime());
        return Utils.parseDate(value.toString());
    }

    @Override
    public Time getTime(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return null;
        if (value instanceof Timestamp)
            return new Time(((Timestamp) value).getTime());
        Time time = null;
        try {
            time = Utils.parseTime(value.toString());
        } catch (DateTimeParseException ignored) {
        }
        return time;
    }

    @Override
    public Timestamp getTimestamp(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return null;
        if (value instanceof Timestamp)
            return (Timestamp) value;
        if (value instanceof Long) {
            if (1_0000_0000_0000_0L > (long) value)
                return Timestamp.from(Instant.ofEpochMilli((long) value));
            long epochSec = (long) value / 1000_000L;
            long nanoAdjustment = (long) value % 1000_000L * 1000;
            return Timestamp.from(Instant.ofEpochSecond(epochSec, nanoAdjustment));
        }
        Timestamp ret;
        try {
            ret = Utils.parseTimestamp(value.toString());
        } catch (Exception e) {
            ret = null;
            wasNull = true;
        }
        return ret;
    }

    @Override
    public ResultSetMetaData getMetaData() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        return this.metaData;
    }

    @Override
    public Object getObject(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        return value;
    }

    @Override
    public int findColumn(String columnLabel) throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        int columnIndex = columnNames.indexOf(columnLabel);
        if (columnIndex == -1)
            throw new SQLException("cannot find Column in resultSet");
        return columnIndex + 1;
    }

    @Override
    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
        checkAvailability(columnIndex, resultSet.get(pos).size());

        Object value = resultSet.get(pos).get(columnIndex - 1);
        wasNull = value == null;
        if (value == null)
            return null;
        if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte)
            return new BigDecimal(Long.parseLong(value.toString()));
        if (value instanceof Double || value instanceof Float)
            return BigDecimal.valueOf(Double.parseDouble(value.toString()));
        if (value instanceof Timestamp)
            return new BigDecimal(((Timestamp) value).getTime());
        BigDecimal ret;
        try {
            ret = new BigDecimal(value.toString());
        } catch (Exception e) {
            ret = null;
        }
        return ret;
    }

    @Override
    public boolean isBeforeFirst() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        return this.pos == -1 && this.resultSet.size() != 0;
    }

    @Override
    public boolean isAfterLast() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        return this.pos >= resultSet.size() && this.resultSet.size() != 0;
    }

    @Override
    public boolean isFirst() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        return this.pos == 0;
    }

    @Override
    public boolean isLast() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        if (this.resultSet.size() == 0)
            return false;
        return this.pos == (this.resultSet.size() - 1);
    }

    @Override
    public void beforeFirst() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        synchronized (this) {
            if (this.resultSet.size() > 0) {
                this.pos = -1;
            }
        }
    }

    @Override
    public void afterLast() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        synchronized (this) {
            if (this.resultSet.size() > 0) {
                this.pos = this.resultSet.size();
            }
        }
    }

    @Override
    public boolean first() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        if (this.resultSet.size() == 0)
            return false;

        synchronized (this) {
            this.pos = 0;
        }
        return true;
    }

    @Override
    public boolean last() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        if (this.resultSet.size() == 0)
            return false;
        synchronized (this) {
            this.pos = this.resultSet.size() - 1;
        }
        return true;
    }

    @Override
    public int getRow() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);
        int row;
        synchronized (this) {
            if (this.pos < 0 || this.pos >= this.resultSet.size())
                return 0;
            row = this.pos + 1;
        }
        return row;
    }

    @Override
    public boolean absolute(int row) throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_UNSUPPORTED_METHOD);
    }

    @Override
    public boolean relative(int rows) throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_UNSUPPORTED_METHOD);
    }

    @Override
    public boolean previous() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_UNSUPPORTED_METHOD);
    }

    @Override
    public String getNString(int columnIndex) throws SQLException {
        return getString(columnIndex);
    }

    @Override
    public Statement getStatement() throws SQLException {
        if (isClosed())
            throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_RESULTSET_CLOSED);

        return this.statement;
    }

    @Override
    public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
        // TODO:did not use the specified timezone in cal
        return getTimestamp(columnIndex);
    }

    @Override
    public boolean isClosed() throws SQLException {
        return isClosed;
    }

}