
dev.miku.r2dbc.mysql.message.server.RowMessage Maven / Gradle / Ivy
/*
* 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.codec.FieldInformation;
import dev.miku.r2dbc.mysql.constant.DataTypes;
import dev.miku.r2dbc.mysql.message.FieldValue;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import static dev.miku.r2dbc.mysql.util.AssertUtils.requireNonNull;
/**
* A message includes data fields which is a row of result.
*/
public final class RowMessage implements ReferenceCounted, ServerMessage {
static final short NULL_VALUE = 0xFB;
private static final byte BIT_MASK_INIT = 1 << 2;
private final FieldReader reader;
RowMessage(FieldReader reader) {
this.reader = requireNonNull(reader, "reader must not be null");
}
public final FieldValue[] decode(boolean isBinary, FieldInformation[] context) {
return isBinary ? binary(context) : text(context.length);
}
private FieldValue[] text(int size) {
FieldValue[] fields = new FieldValue[size];
try {
for (int i = 0; i < size; ++i) {
if (NULL_VALUE == reader.getUnsignedByte()) {
reader.skipOneByte();
fields[i] = FieldValue.nullField();
} else {
fields[i] = reader.readVarIntSizedField();
}
}
return fields;
} catch (Throwable e) {
clearFields(fields, size);
throw e;
}
}
private FieldValue[] binary(FieldInformation[] context) {
reader.skipOneByte(); // constant 0x00
int size = context.length;
// MySQL will make sure columns less than 4096, no need check overflow.
byte[] nullBitmap = reader.readSizeFixedBytes((size + 9) >> 3);
int bitmapIndex = 0;
byte bitMask = BIT_MASK_INIT;
FieldValue[] fields = new FieldValue[size];
try {
for (int i = 0; i < size; ++i) {
if ((nullBitmap[bitmapIndex] & bitMask) != 0) {
fields[i] = FieldValue.nullField();
} else {
int bytes = getFixedBinaryBytes(context[i].getType());
if (bytes > 0) {
fields[i] = reader.readSizeFixedField(bytes);
} else {
fields[i] = reader.readVarIntSizedField();
}
}
bitMask <<= 1;
// Should make sure it is 0 of byte, it may change to int in future, so try not use `bitMask == 0` only.
if ((bitMask & 0xFF) == 0) {
// An approach to circular left shift 1-bit.
bitMask = 1;
// Current byte has been completed by read.
++bitmapIndex;
}
}
return fields;
} catch (Throwable e) {
clearFields(fields, size);
throw e;
}
}
@Override
public int refCnt() {
return reader.refCnt();
}
@Override
public RowMessage retain() {
reader.retain();
return this;
}
@Override
public RowMessage retain(int increment) {
reader.retain(increment);
return this;
}
@Override
public RowMessage touch() {
reader.touch();
return this;
}
@Override
public final RowMessage touch(Object o) {
reader.touch(o);
return this;
}
@Override
public boolean release() {
return reader.release();
}
@Override
public boolean release(int decrement) {
return reader.release(decrement);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RowMessage)) {
return false;
}
RowMessage that = (RowMessage) o;
return reader.equals(that.reader);
}
@Override
public int hashCode() {
return reader.hashCode();
}
@Override
public String toString() {
return "RowMessage(encoded)";
}
/**
* @return {@literal 0} means field is var integer sized in binary result.
*/
private static int getFixedBinaryBytes(short type) {
switch (type) {
case DataTypes.TINYINT:
return Byte.BYTES;
case DataTypes.SMALLINT:
case DataTypes.YEAR:
return Short.BYTES;
case DataTypes.INT:
case DataTypes.MEDIUMINT:
return Integer.BYTES;
case DataTypes.FLOAT:
return Float.BYTES;
case DataTypes.DOUBLE:
return Double.BYTES;
case DataTypes.BIGINT:
return Long.BYTES;
default:
return 0;
}
}
private static void clearFields(FieldValue[] fields, int size) {
FieldValue field;
for (int i = 0; i < size; ++i) {
field = fields[i];
if (field != null && !field.isNull()) {
ReferenceCountUtil.safeRelease(field);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy