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

com.mysql.cj.protocol.a.MysqlTextValueDecoder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015, 2022, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2.0, as published by the
 * Free Software Foundation.
 *
 * This program is also distributed with certain software (including but not
 * limited to OpenSSL) that is licensed under separate terms, as designated in a
 * particular file or component or in included license documentation. The
 * authors of MySQL hereby grant you an additional permission to link the
 * program and your derivative works with the separately licensed software that
 * they have included with MySQL.
 *
 * Without limiting anything contained in the foregoing, this file, which is
 * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * 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 General Public License, version 2.0,
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.cj.protocol.a;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.mysql.cj.Messages;
import com.mysql.cj.exceptions.DataReadException;
import com.mysql.cj.exceptions.NumberOutOfRange;
import com.mysql.cj.protocol.InternalDate;
import com.mysql.cj.protocol.InternalTime;
import com.mysql.cj.protocol.InternalTimestamp;
import com.mysql.cj.protocol.ValueDecoder;
import com.mysql.cj.result.Field;
import com.mysql.cj.result.ValueFactory;
import com.mysql.cj.util.StringUtils;

/**
 * Implementation of {@link com.mysql.cj.protocol.ValueDecoder} for the MySQL text protocol. All values will be received as LengthEncodedString values.
 * 

* Refer to MySQL documentation for format of values as strings. *

* Numeric values are returned as ASCII (encoding=63/binary). */ public class MysqlTextValueDecoder implements ValueDecoder { /** Buffer length of MySQL date string: 'YYYY-MM-DD'. */ public static final int DATE_BUF_LEN = 10; /** Min string length of MySQL time string: 'HH:MM:SS'. */ public static final int TIME_STR_LEN_MIN = 8; /** Max string length of MySQL time string (with microseconds): '-HHH:MM:SS'. */ public static final int TIME_STR_LEN_MAX_NO_FRAC = 10; /** Max string length of MySQL time string (with microseconds): '-HHH:MM:SS.mmmmmm'. */ public static final int TIME_STR_LEN_MAX_WITH_MICROS = TIME_STR_LEN_MAX_NO_FRAC + 7; /** String length of MySQL timestamp string (no microseconds): 'YYYY-MM-DD HH:MM:SS'. */ public static final int TIMESTAMP_STR_LEN_NO_FRAC = 19; /** Max string length of MySQL timestamp (with microsecs): 'YYYY-MM-DD HH:MM:SS.mmmmmm'. */ public static final int TIMESTAMP_STR_LEN_WITH_MICROS = TIMESTAMP_STR_LEN_NO_FRAC + 7; /** String length of String timestamp with nanos. This does not come from MySQL server but we support it via string conversion. */ public static final int TIMESTAMP_STR_LEN_WITH_NANOS = TIMESTAMP_STR_LEN_NO_FRAC + 10; public static final Pattern TIME_PTRN = Pattern.compile("[-]{0,1}\\d{2,3}:\\d{2}:\\d{2}(\\.\\d{1,9})?"); /** Max string length of a signed long = 9223372036854775807 (19+1 for minus sign) */ public static final int MAX_SIGNED_LONG_LEN = 20; public T decodeDate(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromDate(getDate(bytes, offset, length)); } public T decodeTime(byte[] bytes, int offset, int length, int scale, ValueFactory vf) { return vf.createFromTime(getTime(bytes, offset, length, scale)); } public T decodeTimestamp(byte[] bytes, int offset, int length, int scale, ValueFactory vf) { return vf.createFromTimestamp(getTimestamp(bytes, offset, length, scale)); } @Override public T decodeDatetime(byte[] bytes, int offset, int length, int scale, ValueFactory vf) { return vf.createFromDatetime(getTimestamp(bytes, offset, length, scale)); } public T decodeUInt1(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromLong(getInt(bytes, offset, offset + length)); } public T decodeInt1(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromLong(getInt(bytes, offset, offset + length)); } public T decodeUInt2(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromLong(getInt(bytes, offset, offset + length)); } public T decodeInt2(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromLong(getInt(bytes, offset, offset + length)); } public T decodeUInt4(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromLong(getLong(bytes, offset, offset + length)); } public T decodeInt4(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromLong(getInt(bytes, offset, offset + length)); } public T decodeUInt8(byte[] bytes, int offset, int length, ValueFactory vf) { // treat as a signed long if possible to avoid BigInteger overhead if (length <= (MAX_SIGNED_LONG_LEN - 1) && bytes[offset] >= '0' && bytes[offset] <= '8') { return decodeInt8(bytes, offset, length, vf); } return vf.createFromBigInteger(getBigInteger(bytes, offset, length)); } public T decodeInt8(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromLong(getLong(bytes, offset, offset + length)); } public T decodeFloat(byte[] bytes, int offset, int length, ValueFactory vf) { return decodeDouble(bytes, offset, length, vf); } public T decodeDouble(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromDouble(getDouble(bytes, offset, length)); } public T decodeDecimal(byte[] bytes, int offset, int length, ValueFactory vf) { BigDecimal d = new BigDecimal(StringUtils.toAsciiCharArray(bytes, offset, length)); return vf.createFromBigDecimal(d); } public T decodeByteArray(byte[] bytes, int offset, int length, Field f, ValueFactory vf) { return vf.createFromBytes(bytes, offset, length, f); } public T decodeBit(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromBit(bytes, offset, length); } @Override public T decodeSet(byte[] bytes, int offset, int length, Field f, ValueFactory vf) { return decodeByteArray(bytes, offset, length, f, vf); } @Override public T decodeYear(byte[] bytes, int offset, int length, ValueFactory vf) { return vf.createFromYear(getLong(bytes, offset, offset + length)); } public static int getInt(byte[] buf, int offset, int endpos) throws NumberFormatException { long l = getLong(buf, offset, endpos); if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { throw new NumberOutOfRange(Messages.getString("StringUtils.badIntFormat", new Object[] { StringUtils.toString(buf, offset, endpos - offset) })); } return (int) l; } public static long getLong(byte[] buf, int offset, int endpos) throws NumberFormatException { int base = 10; int s = offset; /* Skip white space. */ while (s < endpos && Character.isWhitespace((char) buf[s])) { ++s; } if (s == endpos) { throw new NumberFormatException(StringUtils.toString(buf)); } /* Check for a sign. */ boolean negative = false; if ((char) buf[s] == '-') { negative = true; ++s; } else if ((char) buf[s] == '+') { ++s; } /* Save the pointer so we can check later if anything happened. */ int save = s; long cutoff = Long.MAX_VALUE / base; long cutlim = (int) (Long.MAX_VALUE % base); if (negative) { cutlim++; } boolean overflow = false; long i = 0; for (; s < endpos; s++) { char c = (char) buf[s]; if (c >= '0' && c <= '9') { c -= '0'; } else if (Character.isLetter(c)) { c = (char) (Character.toUpperCase(c) - 'A' + 10); } else { break; } if (c >= base) { break; } /* Check for overflow. */ if ((i > cutoff) || ((i == cutoff) && (c > cutlim))) { overflow = true; } else { i *= base; i += c; } } // no digits were parsed after a possible +/- if (s == save) { throw new NumberFormatException( Messages.getString("StringUtils.badIntFormat", new Object[] { StringUtils.toString(buf, offset, endpos - offset) })); } if (overflow) { throw new NumberOutOfRange(Messages.getString("StringUtils.badIntFormat", new Object[] { StringUtils.toString(buf, offset, endpos - offset) })); } /* Return the result of the appropriate sign. */ return (negative ? (-i) : i); } public static BigInteger getBigInteger(byte[] buf, int offset, int length) throws NumberFormatException { BigInteger i = new BigInteger(StringUtils.toAsciiString(buf, offset, length)); return i; } public static Double getDouble(byte[] bytes, int offset, int length) { return Double.parseDouble(StringUtils.toAsciiString(bytes, offset, length)); } public static boolean isDate(String s) { return s.length() == DATE_BUF_LEN && s.charAt(4) == '-' && s.charAt(7) == '-'; // TODO also check proper date parts ranges } public static boolean isTime(String s) { Matcher matcher = TIME_PTRN.matcher(s); return matcher.matches(); } public static boolean isTimestamp(String s) { Pattern DATETIME_PTRN = Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9}){0,1}"); Matcher matcher = DATETIME_PTRN.matcher(s); return matcher.matches(); } public static InternalDate getDate(byte[] bytes, int offset, int length) { if (length != DATE_BUF_LEN) { throw new DataReadException(Messages.getString("ResultSet.InvalidLengthForType", new Object[] { length, "DATE" })); } int year = getInt(bytes, offset, offset + 4); int month = getInt(bytes, offset + 5, offset + 7); int day = getInt(bytes, offset + 8, offset + 10); return new InternalDate(year, month, day); } public static InternalTime getTime(byte[] bytes, int offset, int length, int scale) { int pos = 0; // used to track the length of the current time segment during parsing int segmentLen; if (length < TIME_STR_LEN_MIN || length > TIME_STR_LEN_MAX_WITH_MICROS) { throw new DataReadException(Messages.getString("ResultSet.InvalidLengthForType", new Object[] { length, "TIME" })); } boolean negative = false; if (bytes[offset] == '-') { pos++; negative = true; } // parse hours field for (segmentLen = 0; Character.isDigit((char) bytes[offset + pos + segmentLen]); segmentLen++) { ; } if (segmentLen == 0 || bytes[offset + pos + segmentLen] != ':') { throw new DataReadException( Messages.getString("ResultSet.InvalidFormatForType", new Object[] { "TIME", StringUtils.toString(bytes, offset, length) })); } int hours = getInt(bytes, offset + pos, offset + pos + segmentLen); if (negative) { hours *= -1; } pos += segmentLen + 1; // +1 for ':' character // parse minutes field for (segmentLen = 0; Character.isDigit((char) bytes[offset + pos + segmentLen]); segmentLen++) { ; } if (segmentLen != 2 || bytes[offset + pos + segmentLen] != ':') { throw new DataReadException( Messages.getString("ResultSet.InvalidFormatForType", new Object[] { "TIME", StringUtils.toString(bytes, offset, length) })); } int minutes = getInt(bytes, offset + pos, offset + pos + segmentLen); pos += segmentLen + 1; // parse seconds field for (segmentLen = 0; offset + pos + segmentLen < offset + length && Character.isDigit((char) bytes[offset + pos + segmentLen]); segmentLen++) { ; } if (segmentLen != 2) { throw new DataReadException( Messages.getString("ResultSet.InvalidFormatForType", new Object[] { StringUtils.toString(bytes, offset, length), "TIME" })); } int seconds = getInt(bytes, offset + pos, offset + pos + segmentLen); pos += segmentLen; // parse optional microsecond fractional value int nanos = 0; if (length > pos) { pos++; // skip '.' character for (segmentLen = 0; offset + pos + segmentLen < offset + length && Character.isDigit((char) bytes[offset + pos + segmentLen]); segmentLen++) { ; } if (segmentLen + pos != length) { throw new DataReadException( Messages.getString("ResultSet.InvalidFormatForType", new Object[] { StringUtils.toString(bytes, offset, length), "TIME" })); } nanos = getInt(bytes, offset + pos, offset + pos + segmentLen); // scale out nanos appropriately. mysql supports up to 6 digits of fractional seconds, each additional digit increasing the range by a factor of // 10. one digit is tenths, two is hundreths, etc nanos = nanos * (int) Math.pow(10, 9 - segmentLen); } return new InternalTime(hours, minutes, seconds, nanos, scale); } public static InternalTimestamp getTimestamp(byte[] bytes, int offset, int length, int scale) { if (length < TIMESTAMP_STR_LEN_NO_FRAC || (length > TIMESTAMP_STR_LEN_WITH_MICROS && length != TIMESTAMP_STR_LEN_WITH_NANOS)) { throw new DataReadException(Messages.getString("ResultSet.InvalidLengthForType", new Object[] { length, "TIMESTAMP" })); } else if (length != TIMESTAMP_STR_LEN_NO_FRAC) { // need at least two extra bytes for fractional, '.' and a digit if (bytes[offset + TIMESTAMP_STR_LEN_NO_FRAC] != (byte) '.' || length < TIMESTAMP_STR_LEN_NO_FRAC + 2) { throw new DataReadException( Messages.getString("ResultSet.InvalidFormatForType", new Object[] { StringUtils.toString(bytes, offset, length), "TIMESTAMP" })); } } // delimiter verification if (bytes[offset + 4] != (byte) '-' || bytes[offset + 7] != (byte) '-' || bytes[offset + 10] != (byte) ' ' || bytes[offset + 13] != (byte) ':' || bytes[offset + 16] != (byte) ':') { throw new DataReadException( Messages.getString("ResultSet.InvalidFormatForType", new Object[] { StringUtils.toString(bytes, offset, length), "TIMESTAMP" })); } int year = getInt(bytes, offset, offset + 4); int month = getInt(bytes, offset + 5, offset + 7); int day = getInt(bytes, offset + 8, offset + 10); int hours = getInt(bytes, offset + 11, offset + 13); int minutes = getInt(bytes, offset + 14, offset + 16); int seconds = getInt(bytes, offset + 17, offset + 19); // nanos from MySQL fractional int nanos; if (length == TIMESTAMP_STR_LEN_WITH_NANOS) { nanos = getInt(bytes, offset + 20, offset + length); } else { nanos = (length == TIMESTAMP_STR_LEN_NO_FRAC) ? 0 : getInt(bytes, offset + 20, offset + length); // scale out nanos appropriately. mysql supports up to 6 digits of fractional seconds, each additional digit increasing the range by a factor of // 10. one digit is tenths, two is hundreths, etc nanos = nanos * (int) Math.pow(10, 9 - (length - TIMESTAMP_STR_LEN_NO_FRAC - 1)); } return new InternalTimestamp(year, month, day, hours, minutes, seconds, nanos, scale); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy