/* * Copyright 2018-2020 the original author or authors. * * 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 dev.miku.r2dbc.mysql.message.server; import dev.miku.r2dbc.mysql.collation.CharCollation; import dev.miku.r2dbc.mysql.constant.Capabilities; import dev.miku.r2dbc.mysql.constant.ColumnDefinitions; import dev.miku.r2dbc.mysql.constant.DataTypes; import dev.miku.r2dbc.mysql.util.VarIntUtils; import dev.miku.r2dbc.mysql.ConnectionContext; import io.netty.buffer.ByteBuf; import reactor.util.annotation.Nullable; import java.nio.charset.Charset; import java.util.Objects; import static dev.miku.r2dbc.mysql.util.AssertUtils.require; import static dev.miku.r2dbc.mysql.util.AssertUtils.requireNonNull; /** * Column or parameter definition metadata message. */ public final class DefinitionMetadataMessage implements ServerMessage { @Nullable private final String database; private final String table; @Nullable private final String originTable; private final String column; @Nullable private final String originColumn; private final int collationId; private final long size; private final short type; private final short definitions; private final short decimals; private DefinitionMetadataMessage( @Nullable String database, String table, @Nullable String originTable, String column, @Nullable String originColumn, int collationId, long size, short type, short definitions, short decimals ) { require(size >= 0, "size must not be a negative integer"); require(collationId > 0, "collationId must be a positive integer"); this.database = database; this.table = requireNonNull(table, "table must not be null"); this.originTable = originTable; this.column = requireNonNull(column, "column must not be null"); this.originColumn = originColumn; this.collationId = collationId; this.size = size; this.type = type; this.definitions = definitions; this.decimals = decimals; } public String getColumn() { return column; } public int getCollationId() { return collationId; } public long getSize() { return size; } public short getType() { return type; } public short getDefinitions() { return definitions; } public short getDecimals() { return decimals; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof DefinitionMetadataMessage)) { return false; } DefinitionMetadataMessage that = (DefinitionMetadataMessage) o; return collationId == that.collationId && size == that.size && type == that.type && definitions == that.definitions && decimals == that.decimals && Objects.equals(database, that.database) && table.equals(that.table) && Objects.equals(originTable, that.originTable) && column.equals(that.column) && Objects.equals(originColumn, that.originColumn); } @Override public int hashCode() { return Objects.hash(database, table, originTable, column, originColumn, collationId, size, type, definitions, decimals); } @Override public String toString() { return String.format("DefinitionMetadataMessage{database='%s', table='%s' (origin:'%s'), column='%s' (origin:'%s'), collationId=%d, size=%d, type=%d, definitions=%x, decimals=%d}", database, table, originTable, column, originColumn, collationId, size, type, definitions, decimals); } static DefinitionMetadataMessage decode(ByteBuf buf, ConnectionContext context) { if ((context.getCapabilities() & Capabilities.PROTOCOL_41) == 0) { return decode320(buf, context); } else { return decode41(buf, context); } } private static DefinitionMetadataMessage decode320(ByteBuf buf, ConnectionContext context) { CharCollation collation = context.getClientCollation(); Charset charset = collation.getCharset(); String table = readVarIntSizedString(buf, charset); String column = readVarIntSizedString(buf, charset); buf.skipBytes(1); // Constant 0x3 int size = buf.readUnsignedMediumLE(); buf.skipBytes(1); // Constant 0x1 short type = buf.readUnsignedByte(); buf.skipBytes(1); // Constant 0x3 short definitions = buf.readShortLE(); short decimals = buf.readUnsignedByte(); return new DefinitionMetadataMessage( null, table, null, column, null, collation.getId(), size, type, definitions, decimals ); } private static DefinitionMetadataMessage decode41(ByteBuf buf, ConnectionContext context) { buf.skipBytes(4); // "def" which sized by var integer CharCollation collation = context.getClientCollation(); Charset charset = collation.getCharset(); String database = readVarIntSizedString(buf, charset); String table = readVarIntSizedString(buf, charset); String originTable = readVarIntSizedString(buf, charset); String column = readVarIntSizedString(buf, charset); String originColumn = readVarIntSizedString(buf, charset); VarIntUtils.readVarInt(buf); // skip constant 0x0c encoded by var integer int collationId = buf.readUnsignedShortLE(); long size = buf.readUnsignedIntLE(); short type = buf.readUnsignedByte(); short definitions = buf.readShortLE(); if (DataTypes.JSON == type && collationId == CharCollation.BINARY_ID) { collationId = collation.getId(); } if ((definitions & ColumnDefinitions.SET) != 0) { // Maybe need to check if it is a string-like type? type = DataTypes.SET; } else if ((definitions & ColumnDefinitions.ENUMERABLE) != 0) { // Maybe need to check if it is a string-like type? type = DataTypes.ENUMERABLE; } return new DefinitionMetadataMessage( database, table, originTable, column, originColumn, collationId, size, type, definitions, buf.readUnsignedByte() ); } private static String readVarIntSizedString(ByteBuf buf, Charset charset) { int bytes = (int) VarIntUtils.readVarInt(buf); // JVM can NOT support string which length upper than maximum of int32 if (bytes == 0) { return ""; } String result = buf.toString(buf.readerIndex(), bytes, charset); buf.skipBytes(bytes); return result; } }