package com.tesora.dve.db.mysql.libmy; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import com.tesora.dve.exceptions.PECodingException; import com.tesora.dve.exceptions.PEException; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; public class MyBinaryResultRow extends MyResponseMessage { static final Logger log = LoggerFactory.getLogger(MyBinaryResultRow.class); //TODO: need to figure out a way to reduce downstream need to collect rowset headers. introduce flags and allow autoinc modifications? -sgossard List<DecodedMeta> fieldConverters; List<ByteBuf> fieldSlices; public MyBinaryResultRow(List<DecodedMeta> fieldConverters) { this.fieldConverters = fieldConverters; this.fieldSlices = new ArrayList<ByteBuf>(); for (int i=0; i< fieldConverters.size();i++){ this.fieldSlices.add(null); } } protected MyBinaryResultRow(List<DecodedMeta> fieldConverters, List<ByteBuf> fieldSlices) { this.fieldConverters = fieldConverters; this.fieldSlices = fieldSlices; } @Override public MyMessageType getMessageType() { throw new PECodingException(MyBinaryResultRow.class.getSimpleName()); } public MyBinaryResultRow append(DecodedMeta valueFunc, Object value){ ByteBuf valueBuf = Unpooled.buffer().order(ByteOrder.LITTLE_ENDIAN); valueFunc.writeObject(valueBuf,value); return this.append(valueFunc,valueBuf); } public MyBinaryResultRow append(DecodedMeta valueFunc, ByteBuf fieldSlice){ List<DecodedMeta> copyOfFuncs = new ArrayList<>(this.fieldConverters); copyOfFuncs.add(valueFunc); List<ByteBuf> newFields = new ArrayList<>(this.fieldSlices); newFields.add(fieldSlice); return new MyBinaryResultRow(copyOfFuncs,newFields); } @Override public void marshallMessage(ByteBuf cb) { cb.writeZero(1);//binary row marker byte[] bitmapArray = constructNullMap(); cb.writeBytes(bitmapArray); marshallRawValues(cb); } private byte[] constructNullMap() { MyNullBitmap constructBitMap = new MyNullBitmap(fieldSlices.size(), MyNullBitmap.BitmapType.RESULT_ROW); for (int i=0;i<fieldSlices.size();i++){ if (fieldSlices.get(i) == null) constructBitMap.setBit(i + 1); } return constructBitMap.getBitmapArray(); } public void marshallRawValues(ByteBuf cb) { for (int i=0;i<fieldSlices.size();i++){ ByteBuf fieldSlice = fieldSlices.get(i); if (fieldSlice != null) cb.writeBytes(fieldSlice.slice()); } } @Override public void unmarshallMessage(ByteBuf cb) throws PEException { int expectedFieldCount = fieldConverters.size(); int expectedBitmapLength = MyNullBitmap.computeSize(expectedFieldCount,MyNullBitmap.BitmapType.RESULT_ROW); cb = cb.order(ByteOrder.LITTLE_ENDIAN); cb.skipBytes(1);//skip the bin row marker. byte[] nullBitmap = new byte[expectedBitmapLength]; cb.readBytes(nullBitmap); MyNullBitmap resultBitmap = new MyNullBitmap(nullBitmap,expectedFieldCount, MyNullBitmap.BitmapType.RESULT_ROW); ByteBuf values = cb; for (int i=0;i < expectedFieldCount;i++){ ByteBuf existing = fieldSlices.get(i); ByteBuf nextSlice = null; int startIndex = values.readerIndex(); if ( ! resultBitmap.getBit(i + 1) ) { fieldConverters.get(i).readObject(values);//TODO: we throw out the unmarshalled value, we could cache it. int endingOffset = values.readerIndex(); nextSlice = values.slice(startIndex,endingOffset - startIndex); } if (existing != null) existing.release(); fieldSlices.set(i,nextSlice); } if (cb.readableBytes() > 0) { log.warn("Decoded binary row had {} leftover bytes, re-encoding may fail.",cb.readableBytes()); cb.skipBytes(cb.readableBytes());//consume rest of buffer. } } public MyBinaryResultRow projection(int[] desiredFields){ int expectedFieldCount = desiredFields.length; ArrayList<ByteBuf> newSlices = new ArrayList<>(expectedFieldCount); ArrayList<DecodedMeta> newConverters = new ArrayList<>(expectedFieldCount); for (int targetIndex=0;targetIndex<expectedFieldCount;targetIndex++){ int sourceIndex = desiredFields[targetIndex]; newConverters.add(fieldConverters.get(sourceIndex));//use the source index, not the target index.. if (fieldSlices.get(sourceIndex) == null) { newSlices.add(null); } else { ByteBuf fieldSlice = fieldSlices.get(sourceIndex); ByteBuf copySlice = Unpooled.buffer(fieldSlice.readableBytes()).order(ByteOrder.LITTLE_ENDIAN); copySlice.writeBytes(fieldSlice.slice()); newSlices.add(copySlice); } } return new MyBinaryResultRow(newConverters, newSlices); } public int size(){ return fieldSlices.size(); } public DecodedMeta getValueFunction(int itemNumber){ return fieldConverters.get(itemNumber); } public ByteBuf getSlice(int itemNumber) { ByteBuf field = fieldSlices.get(itemNumber); if (field == null) return null; else return field.slice(); } public boolean isNull(int itemNumber){ return fieldSlices.get(itemNumber) == null; } public Object getValue(int itemNumber) throws PEException { ByteBuf slice = getSlice(itemNumber); if (slice == null) return null; else return fieldConverters.get(itemNumber).readObject(slice); } public int sizeInBytes() { int totalSize = super.MESSAGE_HEADER_LENGTH; totalSize+= 1; totalSize+= MyNullBitmap.computeSize(fieldSlices.size(), MyNullBitmap.BitmapType.RESULT_ROW); for (int i=0;i<fieldSlices.size();i++){ ByteBuf fieldSlice = fieldSlices.get(i); if (fieldSlice != null) totalSize+= fieldSlice.readableBytes(); } return totalSize; } }