Package com.foundationdb.server.rowdata

Source Code of com.foundationdb.server.rowdata.RowData

/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*/

package com.foundationdb.server.rowdata;

import com.foundationdb.ais.model.AkibanInformationSchema;
import com.foundationdb.ais.model.Table;
import com.foundationdb.server.AkServerUtil;
import com.foundationdb.server.rowdata.encoding.EncodingException;
import com.foundationdb.util.AkibanAppender;

/**
* Represent one or more rows of table data. The backing store is a byte array
* supplied in the constructor. The {@link #RowData(byte[], int, int)}
* constructor allows use of a partially filled reusable buffer.
*
* This class provides methods for both interpreting and constructing row data
* structures in the byte array. After a call to {@link #reset(int, int)} the
* index will point to the first field of the first row represented in the
* buffer.
*
* <pre>
*   +0: record length (Z)
*   +4: signature bytes, e.g., 'AB'
*   +6: field count (short)
*   +8: rowDefId (int)
*  +12: null-map (1 bit per schema-defined column)
*   +M: fixed-length field
*   +N: fixed-length field ...
*   +Q: variable-length field
*   +R: variable-length field ...
*   +Z-6: signature bytes: e.g., 'BA'
*   +Z-4: record length (Z)
* </pre>
*
* @author peter
*/
public class RowData {

    public final static int O_LENGTH_A = 0;

    public final static int O_SIGNATURE_A = 4;

    public final static int O_FIELD_COUNT = 6;

    public final static int O_ROW_DEF_ID = 8;

    public final static int O_NULL_MAP = 12;

    public final static int O_SIGNATURE_B = -6;

    public final static int O_LENGTH_B = -4;

    public final static int MINIMUM_RECORD_LENGTH = 18;

    // Arbitrary sanity bound on maximum size
    public final static int MAXIMUM_RECORD_LENGTH = 8 * 1024 * 1024;

    public final static char SIGNATURE_A = (char) ('A' + ('B' << 8));

    public final static char SIGNATURE_B = (char) ('B' + ('A' << 8));

    public final static int ENVELOPE_SIZE = 12;

    public final static int LEFT_ENVELOPE_SIZE = 6;

    public final static int RIGHT_ENVELOPE_SIZE = 6;

    public final static int CREATE_ROW_INITIAL_SIZE = 500;

    private byte[] bytes;

    private int bufferStart;

    private int bufferEnd;

    private int rowStart;
    private int rowEnd;


    public RowData() {
    }

    public RowData(final byte[] bytes) {
        this.bytes = bytes;
        reset(0, bytes.length);
    }

    public RowData(final byte[] bytes, final int offset, final int length) {
        this.bytes = bytes;
        reset(offset, length);
    }

    public void reset(final int offset, final int length) {
        this.bufferStart = offset;
        this.bufferEnd = offset + length;
        rowStart = rowEnd = bufferStart;
    }

    public void reset(final byte[] bytes) {
        this.bytes = bytes;
        reset(0, bytes.length);
    }

    public void reset(final byte[] bytes, final int offset, final int length) {
        this.bytes = bytes;
        reset(offset, length);
    }

    /**
     * Interpret the length and signature fixed fields of the row at the
     * specified offset. This method sets the {@link #rowStart} and {@link #rowEnd}
     * fields so that subsequent calls to interpret fields are supported.
     *
     * @param offset
     *            byte offset to record start within the buffer
     */
    public boolean prepareRow(final int offset) throws CorruptRowDataException {
        if (offset == bufferEnd) {
            return false;
        }
        if (offset < 0 || offset + MINIMUM_RECORD_LENGTH > bufferEnd) {
            throw new CorruptRowDataException("Invalid offset: " + offset);
        } else {
            validateRow(offset);
            rowStart = offset;
            rowEnd = offset + AkServerUtil.getInt(bytes, O_LENGTH_A + offset);
            return true;
        }
    }

    public void validateRow(final int offset) throws CorruptRowDataException {

        if (offset < 0 || offset + MINIMUM_RECORD_LENGTH > bufferEnd) {
            throw new CorruptRowDataException("Invalid offset: " + offset);
        } else {
            final int recordLength = AkServerUtil.getInt(bytes, O_LENGTH_A + offset);
            if (recordLength < 0 || recordLength + offset > bufferEnd) {
                throw new CorruptRowDataException("Invalid record length: "
                        + recordLength + " at offset: " + offset);
            }
            if (AkServerUtil.getUShort(bytes, O_SIGNATURE_A + offset) != SIGNATURE_A) {
                throw new CorruptRowDataException(
                        "Invalid signature at offset: " + offset);
            }
            final int trailingLength = AkServerUtil.getInt(bytes, offset + recordLength + O_LENGTH_B);
            if (trailingLength != recordLength) {
                throw new CorruptRowDataException(
                        "Invalid trailing record length " + trailingLength
                                + " in record at offset: " + offset);
            }
            if (AkServerUtil.getUShort(bytes, offset + recordLength + O_SIGNATURE_B) != SIGNATURE_B) {
                throw new CorruptRowDataException(
                        "Invalid signature at offset: " + offset);
            }
        }
    }

    public int getBufferStart() {
        return bufferStart;
    }

    public int getBufferLength() {
        return bufferEnd - bufferStart;
    }

    public int getBufferEnd() {
        return bufferEnd;
    }

    public int getRowStart() {
        return rowStart;
    }

    public int getRowStartData() {
        return rowStart + O_NULL_MAP + (getFieldCount() + 7) / 8;
    }

    public int getRowEnd() {
        return rowEnd;
    }

    public int getRowSize() {
        return rowEnd - rowStart;
    }

    public int getInnerStart() {
        return rowStart + O_FIELD_COUNT;
    }

    public int getInnerSize() {
        return rowEnd - rowStart + O_SIGNATURE_B - O_FIELD_COUNT;
    }

    public int getFieldCount() {
        return AkServerUtil.getUShort(bytes, rowStart + O_FIELD_COUNT);
    }

    public int getRowDefId() {
        return AkServerUtil.getInt(bytes, rowStart + O_ROW_DEF_ID);
    }

    public byte[] getBytes() {
        return bytes;
    }

    public int getColumnMapByte(final int offset) {
        return bytes[offset + rowStart + O_NULL_MAP] & 0xFF;
    }

    public boolean isNull(final int fieldIndex) {
        if (fieldIndex < 0 || fieldIndex >= getFieldCount()) {
            throw new IllegalArgumentException("No such field " + fieldIndex
                    + " in " + this);
        } else {
            return (getColumnMapByte(fieldIndex / 8) & (1 << (fieldIndex % 8))) != 0;
        }
    }

    public long getIntegerValue(final int offset, final int width) {
        checkOffsetAndWidth(offset, width);
        return AkServerUtil.getSignedIntegerByWidth(bytes, offset, width);
    }

    public long getUnsignedIntegerValue(final int offset, final int width) {
        checkOffsetAndWidth(offset, width);
        return AkServerUtil.getUnsignedIntegerByWidth(bytes, offset, width);
    }

    public String getStringValue(final int offset, final int width, final FieldDef fieldDef) {
        if (offset == 0 && width == 0) {
            return null;
        }
        checkOffsetAndWidth(offset, width);
        return AkServerUtil.decodeMySQLString(bytes, offset, width, fieldDef);
    }

    private void checkOffsetAndWidth(int offset, int width) {
        if (offset < rowStart || offset + width >= rowEnd) {
            throw new IllegalArgumentException(String.format("Bad location: {offset=%d width=%d start=%d end=%d}",
                    offset, width, rowStart, rowEnd));
        }
    }

    public RowData copy()
    {
        byte[] copyBytes = new byte[rowEnd - rowStart];
        System.arraycopy(bytes, rowStart, copyBytes, 0, rowEnd - rowStart);
        RowData copy = new RowData(copyBytes);
        copy.prepareRow(0);
        return copy;
    }

    public void createRow(final RowDef rowDef, final Object[] values, boolean growBuffer)
    {
        if (growBuffer && !(bufferStart == 0 && bufferEnd == bytes.length)) {
            // This RowData is embedded in a larger buffer. Can't grow it safely.
            throw new CannotGrowBufferException();
        }
        RuntimeException exception = null;
        do {
            try {
                exception = null;
                createRow(rowDef, values);
            } catch (ArrayIndexOutOfBoundsException e) {
                exception = e;
            } catch (EncodingException e) {
                if (e.getCause() instanceof ArrayIndexOutOfBoundsException) {
                    exception = e;
                } else {
                    throw e;
                }
            }
            if (exception != null && growBuffer) {
                int newSize = bytes.length == 0 ? CREATE_ROW_INITIAL_SIZE : bytes.length * 2;
                reset(new byte[newSize]);
            }
        } while (growBuffer && exception != null);
        if (exception != null) {
            throw exception;
        }
    }

    public void createRow(final RowDef rowDef, final Object[] values)
    {
        if (values.length > rowDef.getFieldCount()) {
            throw new IllegalArgumentException("Too many values.");
        }

        RowDataBuilder builder = new RowDataBuilder(rowDef, this);
        builder.startAllocations();
        for (int i=0; i < values.length; ++i) {
            FieldDef fieldDef = rowDef.getFieldDef(i);
            builder.allocate(fieldDef, values[i]);
        }
        builder.startPuts();
        for (Object object : values) {
            builder.putObject(object);
        }
        rowEnd = builder.finalOffset();
    }

    public void updateNonNullLong(FieldDef fieldDef, long rowId)
    {
        // Offset is in low 32 bits of fieldLocation return value
        int offset = (int) fieldDef.getRowDef().fieldLocation(this, fieldDef.getFieldIndex());
        AkServerUtil.putLong(bytes, offset, rowId);
    }

    @Override
    public String toString() {
        return toString(RowDefBuilder.LATEST_FOR_DEBUGGING);
    }

    public String toString(AkibanInformationSchema ais) {
        if (ais == null) {
            return toStringWithoutRowDef("No AIS");
        }
        int rowDefID = getRowDefId();
        Table table = ais.getTable(rowDefID);
        if(table == null) {
            return toStringWithoutRowDef("Unknown RowDefID(" + rowDefID + ")");
        }
        return toString(table.rowDef());
    }

    public String toString(final RowDef rowDef)
    {
        if (rowDef == null) {
            return toStringWithoutRowDef("No RowDef provided");
        }
        final AkibanAppender sb = AkibanAppender.of(new StringBuilder());
        RowDataValueSource source = new RowDataValueSource();
        try {
            sb.append(rowDef.table().getName().getTableName());
            for (int i = 0; i < getFieldCount(); i++) {
                final FieldDef fieldDef = rowDef.getFieldDef(i);
                sb.append(i == 0 ? '(' : ',');
                final long location = fieldDef.getRowDef().fieldLocation(
                    this, fieldDef.getFieldIndex());
                if (location == 0) {
                    // sb.append("null");
                } else {
                    source.bind(fieldDef, this);
                    fieldDef.column().getType().format(source, sb);
                }
            }
            sb.append(')');
        } catch (Exception e) {
            int size = Math.min(getRowSize(), 64);
            if (size > 0 && rowStart >= 0) {
                sb.append(AkServerUtil.dump(bytes, rowStart, size));
            }
            return sb.toString();
        }
        return sb.toString();
    }

    /** Returns a hex-dump of the backing buffer. */
    public String toStringWithoutRowDef(String missingRowDefExplanation) {
        final AkibanAppender sb = AkibanAppender.of(new StringBuilder());
        try {
            sb.append("RowData[");
            sb.append(missingRowDefExplanation);
            sb.append("]?(rowDefId=");
            sb.append(getRowDefId());
            sb.append(": ");
            AkServerUtil.hex(sb, bytes, rowStart, rowEnd - rowStart);
        } catch (Exception e) {
            int size = Math.min(getRowSize(), 64);
            if (size > 0 && rowStart >= 0) {
                sb.append(AkServerUtil.dump(bytes, rowStart, size));
            }
            return sb.toString();
        }
        return sb.toString();
    }
}
TOP

Related Classes of com.foundationdb.server.rowdata.RowData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.