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

com.oceanbase.oms.logmessage.LogMessage Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 OceanBase.
 *
 * 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 com.oceanbase.oms.logmessage;

import com.oceanbase.oms.common.enums.DbTypeEnum;
import com.oceanbase.oms.logmessage.enums.DataType;
import com.oceanbase.oms.logmessage.typehelper.LogTypeHelper;
import com.oceanbase.oms.logmessage.typehelper.LogTypeHelperFactory;
import com.oceanbase.oms.logmessage.utils.BinaryMessageUtils;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.CRC32;

public class LogMessage extends DataMessage.Record {
    private static final Logger log = LoggerFactory.getLogger(LogMessage.class);

    public static final String DEFAULT_ENCODING = "ASCII";

    public static final String UTF8_ENCODING = "UTF-8";

    private static final String SEP = System.getProperty("line.separator");

    private static final int OLD_VERSION_2_HEADER_LEN = 88;

    private static final int NEW_VERSION_2_HEADER_LEN = 96;

    private static final int VERSION_3_HEADER_LEN = 104;

    private static final int VERSION_3_1_HEADER_LEN = 120;

    private static final int PREFIX_LENGTH = 12;

    private int brVersion = (byte) 0xff;

    private int srcType = (byte) 0xff;

    private int op = (byte) 0xff;

    private int lastInLogEvent = (byte) 0xff;

    private long srcCategory = -1;

    private long id = -1;

    private long timestamp = -1;

    private long encoding = -1;

    private long instanceOffset = -1;
    private long timeMarkOffset = -1;

    private long dbNameOffset = -1;

    private long tbNameOffset = -1;

    private long colNamesOffset = -1;

    private long colTypesOffset = -1;

    private long fileNameOffset = -1;

    private long fileOffset = -1;

    private long oldColsOffset = -1;

    private long newColsOffset = -1;

    private long pkValOffset = -1;

    private long pkKeysOffset = -1;

    private long ukColsOffset = -1;

    private long colsEncodingOffset = -1;

    private long filterRuleValOffset = -1;

    private long tailOffset = -1;

    private long metaVersion = -1;

    private long colFlagOffset = -1;

    private long colNotNullOffset = -1;

    /** buf parse data */
    private String dbName;

    private String tableName;

    private String serverId;

    private List primaryKeyIndexList;

    private String uniqueKeyList;

    private ByteBuf byteBuf;

    private Set keysValue;

    private List pkValues;

    private List timeMarks = null;

    private boolean keyChange = false;

    /** type size map, used to get array type bytes by type index */
    private static final int[] ELEMENT_ARRAY = {0, 1, 1, 2, 2, 4, 4, 8, 8};

    private static final int BYTE_SIZE = 1;

    private static final int INT_SIZE = Integer.SIZE / Byte.SIZE;

    private final CRC32 crc32 = new CRC32();

    private boolean isCheckCRC = false;

    public LogMessage(boolean isCheckCRC) {
        this.isCheckCRC = isCheckCRC;
    }

    private void setKeyChange(boolean isChange) {
        keyChange = isChange;
    }

    @Override
    public boolean isKeyChange() {
        return keyChange;
    }

    public int getVersion() {
        return brVersion;
    }

    @Override
    public DbTypeEnum getDbType() {
        return DataMessage.parseDBTypeCode(srcType);
    }

    @Override
    public boolean isQueryBack() {
        switch ((int) srcCategory) {
            case 1:
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean isFirstInLogEvent() {
        return lastInLogEvent == 1;
    }

    @Override
    public Type getOpt() {
        return Type.valueOf(op);
    }

    @Override
    public String getId() {
        return Long.toString(id);
    }

    @Override
    public String getDbName() {
        if (dbName == null) {
            if ((int) dbNameOffset < 0) {
                dbName = "";
            } else {
                try {
                    dbName =
                            BinaryMessageUtils.getString(
                                    byteBuf.array(), (int) dbNameOffset, UTF8_ENCODING);
                } catch (Exception e) {
                    throw new LogMessageException(e.getMessage(), e.getCause());
                }
            }
        }
        return "".endsWith(dbName) ? null : dbName;
    }

    @Override
    public String getTableName() {
        if (tableName == null) {
            if ((int) tbNameOffset < 0) {
                tableName = "";
            } else {
                try {
                    tableName =
                            BinaryMessageUtils.getString(
                                    byteBuf.array(), (int) tbNameOffset, UTF8_ENCODING);
                } catch (Exception e) {
                    throw new LogMessageException(e.getMessage(), e.getCause());
                }
            }
        }
        return "".endsWith(tableName) ? null : tableName;
    }

    @Override
    public String getCheckpoint() {
        return fileOffset + "@" + fileNameOffset;
    }

    @Override
    public String getTimestamp() {
        return Long.toString(timestamp);
    }

    @Override
    public String getServerId() {
        if (serverId == null) {
            if ((int) instanceOffset < 0) {
                serverId = "";
            } else {
                try {
                    serverId =
                            BinaryMessageUtils.getString(
                                    byteBuf.array(), (int) instanceOffset, DEFAULT_ENCODING);
                } catch (Exception e) {
                    throw new LogMessageException(e.getMessage(), e.getCause());
                }
            }
        }
        return "".endsWith(serverId) ? null : serverId;
    }

    @Override
    public void fieldListParse(FieldParseListener fieldParseListener) throws Exception {
        if (colNamesOffset < 0 || colTypesOffset < 0 || oldColsOffset < 0 || newColsOffset < 0) {
            return;
        }

        /*
         * global encoding
         *
         * 对于 DDL 的默认编码改动, DDL DrcMessage 不携带编码信息,只能使用默认编码,但是 ASCII 对于中文处理出错
         * 对于 DDL 默认编码改为 UTF-8,不改变除 DDL 之外其他行为
         */
        String encodingStr = null;
        if (this.getOpt() == Type.DDL) {
            encodingStr = UTF8_ENCODING;
        } else {
            encodingStr =
                    BinaryMessageUtils.getString(byteBuf.array(), (int) encoding, DEFAULT_ENCODING);
        }
        // get column count
        ByteBuf wrapByteBuf =
                Unpooled.wrappedBuffer(byteBuf.array()).order(ByteOrder.LITTLE_ENDIAN);
        wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + colNamesOffset + BYTE_SIZE));
        int count = wrapByteBuf.readInt();
        // op type array
        wrapByteBuf.readerIndex(PREFIX_LENGTH + (int) colTypesOffset);
        byte t = wrapByteBuf.readByte();
        int elementSize = ELEMENT_ARRAY[t & DataType.DT_MASK];
        // encoding
        int colEncodingsCount = 0;
        int currentEncodingOffset = 0;
        if (colsEncodingOffset > 0) {
            wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + colsEncodingOffset + BYTE_SIZE));
            colEncodingsCount = wrapByteBuf.readInt();
            currentEncodingOffset = (int) wrapByteBuf.readUnsignedInt();
        }
        // column name
        wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + colNamesOffset + BYTE_SIZE + INT_SIZE));
        int currentColNameOffset = (int) wrapByteBuf.readUnsignedInt();
        // old col value
        wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + oldColsOffset + BYTE_SIZE));
        int oldColCount = wrapByteBuf.readInt();
        int currentOldColOffset = -1;
        // Bug fix: if newCol count or old Count is 0, then the following offset should not be read;
        if (0 != oldColCount) {
            currentOldColOffset = (int) wrapByteBuf.readUnsignedInt();
        }
        // new col value
        wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + newColsOffset + BYTE_SIZE));
        int newColCount = wrapByteBuf.readInt();
        int currentNewColOffset = -1;
        if (0 != newColCount) {
            currentNewColOffset = (int) wrapByteBuf.readUnsignedInt();
        }
        LogTypeHelper logTypeHelper = LogTypeHelperFactory.getInstance(getDbType());
        // start loop
        for (int i = 0; i < count; i++) {
            // get real op type
            int type = 0;
            wrapByteBuf.readerIndex(
                    PREFIX_LENGTH + (int) colTypesOffset + BYTE_SIZE + INT_SIZE + i * elementSize);
            switch (elementSize) {
                case 1:
                    type = wrapByteBuf.readUnsignedByte();
                    break;
                case 2:
                    type = wrapByteBuf.readUnsignedShort();
                    break;
                case 4:
                    type = (int) wrapByteBuf.readUnsignedInt();
                    break;
                case 8:
                    type = (int) wrapByteBuf.readLong();
                    break;
            }
            boolean notNull = false;
            if (fieldParseListener.needSchemaInfo()) {
                if (colNotNullOffset > 0) {
                    wrapByteBuf.readerIndex(
                            PREFIX_LENGTH
                                    + (int) colNotNullOffset
                                    + BYTE_SIZE
                                    + INT_SIZE
                                    + i * elementSize);
                    notNull = wrapByteBuf.readBoolean();
                }
            }
            // get real encoding
            String realEncoding = encodingStr;

            // now deliver have fix encoding offset bug, encoding has been decoded correctly
            // add db2 compatible code for new version db2 reader
            // old else will also saved for old version store
            // this code will deprecated in future release
            // correct oracle code if oralce reader has update delivier version or correct type code
            if (colEncodingsCount > 0) {
                wrapByteBuf.readerIndex(
                        (int)
                                (PREFIX_LENGTH
                                        + colsEncodingOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + (i + 1) * INT_SIZE));
                int nextEncodingOffset = (int) wrapByteBuf.readUnsignedInt();
                ByteString encodingByteString =
                        new ByteString(
                                wrapByteBuf.array(),
                                PREFIX_LENGTH
                                        + currentEncodingOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + (count + 1) * INT_SIZE
                                        + (int) colsEncodingOffset,
                                nextEncodingOffset - currentEncodingOffset - 1);
                realEncoding = encodingByteString.toString();
                currentEncodingOffset = nextEncodingOffset;
            }
            realEncoding = logTypeHelper.correctEncoding(type, realEncoding);
            String columnName = null;
            if (fieldParseListener.needSchemaInfo()) {
                type = logTypeHelper.correctCode(type, realEncoding);

                // colName
                wrapByteBuf.readerIndex(
                        (int)
                                (PREFIX_LENGTH
                                        + colNamesOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + (i + 1) * INT_SIZE));
                int nextColNameOffset = (int) wrapByteBuf.readUnsignedInt();
                ByteString colNameByteString =
                        new ByteString(
                                wrapByteBuf.array(),
                                PREFIX_LENGTH
                                        + currentColNameOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + (count + 1) * INT_SIZE
                                        + (int) colNamesOffset,
                                nextColNameOffset - currentColNameOffset - 1);
                columnName = colNameByteString.toString();
                currentColNameOffset = nextColNameOffset;
            }
            // old col
            if (oldColCount != 0) {
                wrapByteBuf.readerIndex(
                        (int)
                                (PREFIX_LENGTH
                                        + oldColsOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + (i + 1) * INT_SIZE));
                int nextOldColOffset = (int) wrapByteBuf.readUnsignedInt();
                ByteString value = null;
                if (nextOldColOffset != currentOldColOffset) {
                    value =
                            new ByteString(
                                    wrapByteBuf.array(),
                                    PREFIX_LENGTH
                                            + currentOldColOffset
                                            + BYTE_SIZE
                                            + INT_SIZE
                                            + (count + 1) * INT_SIZE
                                            + (int) oldColsOffset,
                                    nextOldColOffset - currentOldColOffset - 1);
                }
                if (fieldParseListener.needSchemaInfo()) {
                    fieldParseListener.parseNotify(
                            columnName, type, realEncoding, value, notNull, true);
                } else {
                    fieldParseListener.parseNotify(type, value, realEncoding, true);
                }
                currentOldColOffset = nextOldColOffset;
            }
            // new col
            if (newColCount != 0) {
                wrapByteBuf.readerIndex(
                        (int)
                                (PREFIX_LENGTH
                                        + newColsOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + (i + 1) * INT_SIZE));
                int nextNewColOffset = (int) wrapByteBuf.readUnsignedInt();
                ByteString value = null;
                if (currentNewColOffset != nextNewColOffset) {
                    value =
                            new ByteString(
                                    wrapByteBuf.array(),
                                    PREFIX_LENGTH
                                            + currentNewColOffset
                                            + BYTE_SIZE
                                            + INT_SIZE
                                            + (count + 1) * INT_SIZE
                                            + (int) newColsOffset,
                                    nextNewColOffset - currentNewColOffset - 1);
                }
                if (fieldParseListener.needSchemaInfo()) {
                    fieldParseListener.parseNotify(
                            columnName, type, realEncoding, value, notNull, false);
                } else {
                    fieldParseListener.parseNotify(type, value, realEncoding, false);
                }
                currentNewColOffset = nextNewColOffset;
            }
        }
    }

    @Override
    public synchronized List getFieldList() {
        try {
            if (fields == null) {
                if (colNamesOffset < 0
                        || colTypesOffset < 0
                        || oldColsOffset < 0
                        || newColsOffset < 0) {
                    return fields;
                }
                LogTypeHelper logTypeHelper = LogTypeHelperFactory.getInstance(getDbType());
                /*
                 * global encoding
                 *
                 * 对于 DDL 的默认编码改动, DDL DrcMessage 不携带编码信息,只能使用默认编码,但是 ASCII 对于中文处理出错
                 * 对于 DDL 默认编码改为 UTF-8,不改变除 DDL 之外其他行为
                 */
                String encodingStr = null;
                if (this.getOpt() == Type.DDL) {
                    encodingStr = UTF8_ENCODING;
                } else {
                    encodingStr =
                            BinaryMessageUtils.getString(
                                    byteBuf.array(), (int) encoding, DEFAULT_ENCODING);
                }
                // pk info
                List pks = null;
                if ((int) pkKeysOffset > 0) {
                    pks = (BinaryMessageUtils.getArray(byteBuf.array(), (int) pkKeysOffset));
                }
                // get column count
                ByteBuf wrapByteBuf =
                        Unpooled.wrappedBuffer(byteBuf.array()).order(ByteOrder.LITTLE_ENDIAN);
                wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + colNamesOffset + BYTE_SIZE));
                int count = wrapByteBuf.readInt();
                fields = new ArrayList(count);
                // op type array
                wrapByteBuf.readerIndex(PREFIX_LENGTH + (int) colTypesOffset);
                byte t = wrapByteBuf.readByte();
                int elementSize = ELEMENT_ARRAY[t & DataType.DT_MASK];
                // encoding
                int colEncodingsCount = 0;
                int currentEncodingOffset = 0;
                if (colsEncodingOffset > 0) {
                    wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + colsEncodingOffset + BYTE_SIZE));
                    colEncodingsCount = wrapByteBuf.readInt();
                    currentEncodingOffset = (int) wrapByteBuf.readUnsignedInt();
                }
                // column name
                wrapByteBuf.readerIndex(
                        (int) (PREFIX_LENGTH + colNamesOffset + BYTE_SIZE + INT_SIZE));
                int currentColNameOffset = (int) wrapByteBuf.readUnsignedInt();
                // old col value
                wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + oldColsOffset + BYTE_SIZE));
                int oldColCount = wrapByteBuf.readInt();
                int currentOldColOffset = -1;
                // Bug fix: if newCol count or old Count is 0, then the following offset should not
                // be read;
                if (0 != oldColCount) {
                    currentOldColOffset = (int) wrapByteBuf.readUnsignedInt();
                }
                // new col value
                wrapByteBuf.readerIndex((int) (PREFIX_LENGTH + newColsOffset + BYTE_SIZE));
                int newColCount = wrapByteBuf.readInt();
                int currentNewColOffset = -1;
                if (0 != newColCount) {
                    currentNewColOffset = (int) wrapByteBuf.readUnsignedInt();
                }
                // start loop
                for (int i = 0; i < count; i++) {
                    // get pk boolean
                    boolean isPk = false;
                    if (pks != null && pks.contains(i)) {
                        isPk = true;
                    }
                    // get real op type
                    int type = 0;
                    wrapByteBuf.readerIndex(
                            PREFIX_LENGTH
                                    + (int) colTypesOffset
                                    + BYTE_SIZE
                                    + INT_SIZE
                                    + i * elementSize);
                    switch (elementSize) {
                        case 1:
                            type = wrapByteBuf.readUnsignedByte();
                            break;
                        case 2:
                            type = wrapByteBuf.readUnsignedShort();
                            break;
                        case 4:
                            type = (int) wrapByteBuf.readUnsignedInt();
                            break;
                        case 8:
                            type = (int) wrapByteBuf.readLong();
                            break;
                    }
                    // get col flag
                    int flag = 0;
                    if (colFlagOffset > 0) {
                        wrapByteBuf.readerIndex(
                                PREFIX_LENGTH
                                        + (int) colFlagOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + i * elementSize);
                        flag = wrapByteBuf.readUnsignedByte();
                    }
                    boolean notNull = false;
                    if (colNotNullOffset > 0) {
                        wrapByteBuf.readerIndex(
                                PREFIX_LENGTH
                                        + (int) colNotNullOffset
                                        + BYTE_SIZE
                                        + INT_SIZE
                                        + i * elementSize);
                        notNull = wrapByteBuf.readBoolean();
                    }
                    // get real encoding
                    String realEncoding = encodingStr;

                    // now deliver have fix encoding offset bug, encoding has been decoded correctly
                    // add db2 compatible code for new version db2 reader
                    // old else will also saved for old version store
                    // this code will deprecated in future release
                    // correct oracle code if oralce reader has update delivier version or correct
                    // type code
                    if (colEncodingsCount > 0) {
                        wrapByteBuf.readerIndex(
                                (int)
                                        (PREFIX_LENGTH
                                                + colsEncodingOffset
                                                + BYTE_SIZE
                                                + INT_SIZE
                                                + (i + 1) * INT_SIZE));
                        int nextEncodingOffset = (int) wrapByteBuf.readUnsignedInt();
                        ByteString encodingByteString =
                                new ByteString(
                                        wrapByteBuf.array(),
                                        PREFIX_LENGTH
                                                + currentEncodingOffset
                                                + BYTE_SIZE
                                                + INT_SIZE
                                                + (count + 1) * INT_SIZE
                                                + (int) colsEncodingOffset,
                                        nextEncodingOffset - currentEncodingOffset - 1);
                        realEncoding = encodingByteString.toString();
                        currentEncodingOffset = nextEncodingOffset;
                    }
                    realEncoding = logTypeHelper.correctEncoding(type, realEncoding);
                    type = logTypeHelper.correctCode(type, realEncoding);

                    // colName
                    wrapByteBuf.readerIndex(
                            (int)
                                    (PREFIX_LENGTH
                                            + colNamesOffset
                                            + BYTE_SIZE
                                            + INT_SIZE
                                            + (i + 1) * INT_SIZE));
                    int nextColNameOffset = (int) wrapByteBuf.readUnsignedInt();
                    ByteString colNameByteString =
                            new ByteString(
                                    wrapByteBuf.array(),
                                    PREFIX_LENGTH
                                            + currentColNameOffset
                                            + BYTE_SIZE
                                            + INT_SIZE
                                            + (count + 1) * INT_SIZE
                                            + (int) colNamesOffset,
                                    nextColNameOffset - currentColNameOffset - 1);
                    String columnName = colNameByteString.toString();
                    currentColNameOffset = nextColNameOffset;
                    // old col
                    if (oldColCount != 0) {
                        wrapByteBuf.readerIndex(
                                (int)
                                        (PREFIX_LENGTH
                                                + oldColsOffset
                                                + BYTE_SIZE
                                                + INT_SIZE
                                                + (i + 1) * INT_SIZE));
                        int nextOldColOffset = (int) wrapByteBuf.readUnsignedInt();
                        ByteString value = null;
                        if (nextOldColOffset != currentOldColOffset) {
                            value =
                                    new ByteString(
                                            wrapByteBuf.array(),
                                            PREFIX_LENGTH
                                                    + currentOldColOffset
                                                    + BYTE_SIZE
                                                    + INT_SIZE
                                                    + (count + 1) * INT_SIZE
                                                    + (int) oldColsOffset,
                                            nextOldColOffset - currentOldColOffset - 1);
                        }
                        Field field = new Field(columnName, type, realEncoding, value, isPk);
                        field.setFlag(flag);
                        field.setNotNull(notNull);
                        fields.add(field);
                        field.setPrev(true);
                        currentOldColOffset = nextOldColOffset;
                    }
                    // new col
                    if (newColCount != 0) {
                        wrapByteBuf.readerIndex(
                                (int)
                                        (PREFIX_LENGTH
                                                + newColsOffset
                                                + BYTE_SIZE
                                                + INT_SIZE
                                                + (i + 1) * INT_SIZE));
                        int nextNewColOffset = (int) wrapByteBuf.readUnsignedInt();
                        ByteString value = null;
                        if (currentNewColOffset != nextNewColOffset) {
                            value =
                                    new ByteString(
                                            wrapByteBuf.array(),
                                            PREFIX_LENGTH
                                                    + currentNewColOffset
                                                    + BYTE_SIZE
                                                    + INT_SIZE
                                                    + (count + 1) * INT_SIZE
                                                    + (int) newColsOffset,
                                            nextNewColOffset - currentNewColOffset - 1);
                        }
                        Field field = new Field(columnName, type, realEncoding, value, isPk);
                        field.setFlag(flag);
                        field.setNotNull(notNull);
                        fields.add(field);
                        field.setPrev(false);
                        currentNewColOffset = nextNewColOffset;
                    }
                }
            }

        } catch (Exception e) {
            fields = null;
            throw new LogMessageException(e.getMessage(), e);
        }

        return fields;
    }

    @Override
    public int getFieldCount() {
        List fields = getFieldList();
        if (fields == null) {
            return 0;
        }
        return fields.size();
    }

    public List getPrimaryKeyIndex() {
        try {
            if (primaryKeyIndexList == null) {
                if ((int) pkKeysOffset < 0) {
                    primaryKeyIndexList = new ArrayList<>();
                } else {
                    primaryKeyIndexList =
                            BinaryMessageUtils.getArray(byteBuf.array(), (int) pkKeysOffset);
                }
            }
        } catch (Exception e) {
            throw new LogMessageException(e.getMessage(), e.getCause());
        }
        return primaryKeyIndexList;
    }

    @Override
    public void parse(final byte[] data) throws Exception {
        ByteBuf inner = Unpooled.wrappedBuffer(data, 0, data.length).order(ByteOrder.LITTLE_ENDIAN);
        setByteBuf(inner);
    }

    /**
     * Get the primary data to avoid parsing
     *
     * @return primary data
     */
    @Override
    public byte[] getRawData() {
        if (byteBuf == null) {
            return null;
        }

        return byteBuf.array();
    }

    /**
     * 解析buffer
     *
     * @param byteBuf ByteBuf
     * @throws Exception 解析出错
     */
    public void setByteBuf(ByteBuf byteBuf) throws Exception {
        this.byteBuf = byteBuf;
        // omit first 12 bytes
        byteBuf.readerIndex(PREFIX_LENGTH);
        if ((byteBuf.readByte() & DataType.DT_MASK) != DataType.DT_UINT8) {
            throw new Exception("parse error");
        }
        long count = byteBuf.readInt();
        boolean old = false;
        switch ((int) count) {
            case OLD_VERSION_2_HEADER_LEN:
                old = true;
                break;
            case VERSION_3_1_HEADER_LEN:
            case VERSION_3_HEADER_LEN:
            case NEW_VERSION_2_HEADER_LEN:
                break;
            default:
                throw new Exception("");
        }
        brVersion = byteBuf.readUnsignedByte();
        srcType = byteBuf.readUnsignedByte();
        op = byteBuf.readUnsignedByte();
        lastInLogEvent = byteBuf.readByte();
        srcCategory = byteBuf.readInt();
        id = byteBuf.readLong();
        timestamp = byteBuf.readLong();
        encoding = byteBuf.readInt();
        instanceOffset = byteBuf.readInt();
        // get timeMark
        timeMarkOffset = byteBuf.readInt();
        dbNameOffset = byteBuf.readInt();
        tbNameOffset = byteBuf.readInt();
        colNamesOffset = byteBuf.readInt();
        colTypesOffset = byteBuf.readInt();

        if (!old) {
            pkValOffset = byteBuf.readInt();
            fileNameOffset = byteBuf.readLong();
            fileOffset = byteBuf.readLong();
            if (fileNameOffset < -1 || fileOffset < -1) {
                throw new IOException(
                        "f: "
                                + fileNameOffset
                                + " and o: "
                                + fileOffset
                                + " should both be signed integer");
            }
            oldColsOffset = byteBuf.readInt();
            newColsOffset = byteBuf.readInt();
        } else {
            fileNameOffset = byteBuf.readInt();
            fileOffset = byteBuf.readInt();
            oldColsOffset = byteBuf.readInt();
            newColsOffset = byteBuf.readInt();
            pkValOffset = byteBuf.readInt();
        }

        pkKeysOffset = byteBuf.readInt();
        ukColsOffset = byteBuf.readInt();

        if (brVersion > 1) {
            colsEncodingOffset = byteBuf.readLong();
        }
        // thread id& trace id
        if (brVersion == 3) {
            filterRuleValOffset = byteBuf.readInt();
            tailOffset = byteBuf.readInt();

            long version = id >> 56;
            if (version >= 1) {
                metaVersion = byteBuf.readInt();
                colFlagOffset = byteBuf.readInt();
            }
            if (version >= 2) {
                colNotNullOffset = byteBuf.readInt();
            }
        }

        // timestamp,process heartbeat between tx
        Type type = Type.valueOf(op);
        String ts = Long.toString(timestamp);
        DbTypeEnum dbTypeEnum = getDbType();
        if (dbTypeEnum == DbTypeEnum.OB_MYSQL || dbTypeEnum == DbTypeEnum.OB_ORACLE) {
            globalSafeTimestamp.set(String.valueOf(fileNameOffset));
        } else {
            if (type == Type.BEGIN) {
                globalSafeTimestamp.set(ts);
                txEnd.set(false);
            }
            if (txEnd.get()) {
                globalSafeTimestamp.set(ts);
            }
            // set txEnd
            if (type == Type.COMMIT || type == Type.ROLLBACK) {
                txEnd.set(true);
            }
        }
        safeTimestamp = globalSafeTimestamp.get();
        if (isCheckCRC) {
            checkCRC();
        }
    }

    private void checkCRC() throws IOException {
        long value = getCRCValue();
        if (value == 0L) {
            return;
        }
        crc32.update(byteBuf.array(), 0, byteBuf.array().length - 4);
        long actual = crc32.getValue();
        crc32.reset();
        if (value != actual) {
            throw new IOException("crc 32 check failed,expect:" + value + ",actual:" + actual);
        }
    }

    @Override
    public String getTraceId() {
        List list =
                BinaryMessageUtils.getByteStringList(byteBuf.array(), filterRuleValOffset);
        if (list == null || list.size() == 0 || list.size() < 3) {
            return null;
        }
        ByteString traceId = list.get(2);
        return traceId == null ? null : traceId.toString();
    }

    @Override
    public String getOB10UniqueId() {
        List list =
                BinaryMessageUtils.getByteStringList(byteBuf.array(), filterRuleValOffset);
        if (list == null || list.size() == 0 || list.size() < 3) {
            return null;
        }
        ByteString ob10UniqueId = list.get(1);
        return ob10UniqueId == null ? null : ob10UniqueId.toString();
    }

    @Override
    public String getThreadId() throws Exception {
        long threadId = 0L;
        if (tailOffset == -1) {
            return null;
        }
        List list = BinaryMessageUtils.getArray(byteBuf.array(), (int) tailOffset);
        if (list == null || list.size() == 0) {
            return null;
        }
        threadId += (long) list.get(0);
        threadId += ((long) list.get(1)) << 8;
        threadId += ((long) list.get(2)) << 16;
        threadId += ((long) list.get(3)) << 24;
        return String.valueOf(threadId);
    }

    private List> getKeys(int valueOffset, List keys) {
        if (valueOffset == -1) {
            return null;
        }
        List> result = new ArrayList>();
        ByteBuf wrapByteBuf =
                Unpooled.wrappedBuffer(byteBuf.array()).order(ByteOrder.LITTLE_ENDIAN);

        // get field count
        wrapByteBuf.readerIndex(PREFIX_LENGTH + valueOffset + 1);
        int fieldCount = wrapByteBuf.readInt();

        // parse
        for (ByteString key : keys) {
            String keyStr = key.toString();
            int m = 0;
            while (true) {
                int i = keyStr.indexOf('(', m);
                if (i == -1) {
                    break;
                }
                int j = keyStr.indexOf(')', i);
                if (j == -1) {
                    log.error("Parse key error");
                    return null;
                }
                m = j;
                String[] parts = keyStr.substring(i + 1, j).split(",");
                StringBuilder sb = new StringBuilder();
                List item = new ArrayList();
                result.add(item);
                for (String indexStr : parts) {
                    int index = Integer.parseInt(indexStr);
                    wrapByteBuf.readerIndex(PREFIX_LENGTH + valueOffset + 5 + index * 4);
                    int start = (int) wrapByteBuf.readUnsignedInt();
                    int end = (int) wrapByteBuf.readUnsignedInt();
                    // uk perhaps null
                    if (end - start == 0) {
                        item.add(null);
                        continue;
                    }
                    String k =
                            new ByteString(
                                            wrapByteBuf.array(),
                                            PREFIX_LENGTH
                                                    + valueOffset
                                                    + 5
                                                    + (fieldCount + 1) * 4
                                                    + start,
                                            end - start - 1)
                                    .toString();
                    sb.append(k);
                    item.add(k);
                }
                if (sb.length() > 0) {
                    keysValue.add(sb.toString());
                } else {
                    keysValue.add(null);
                }
            }
        }
        return result;
    }

    /**
     * 获得pk uk value,combine
     *
     * @return key集合
     */
    @Override
    public Set getKeysValue() {
        List> prev = new ArrayList>();
        List> next = new ArrayList>();

        try {
            if (keysValue != null) {
                return keysValue;
            }
            if (colNamesOffset < 0
                    || colTypesOffset < 0
                    || oldColsOffset < 0
                    || newColsOffset < 0) {
                return null;
            }

            // get key str
            keysValue = new HashSet();
            List keys =
                    BinaryMessageUtils.getByteStringList(byteBuf.array(), (int) pkValOffset);
            if (keys == null || keys.size() == 0) {
                return null;
            }
            // get value offset by op type
            switch (getOpt()) {
                case INSERT:
                case REPLACE:
                case INDEX_INSERT:
                case INDEX_REPLACE:
                    next = getKeys((int) newColsOffset, keys);
                    break;
                case DELETE:
                case INDEX_DELETE:
                    prev = getKeys((int) oldColsOffset, keys);
                    break;
                case UPDATE:
                case INDEX_UPDATE:
                    switch (getDbType()) {
                        case ORACLE:
                        case MYSQL:
                        case OB_MYSQL:
                        case OB_ORACLE:
                            prev.addAll(getKeys((int) oldColsOffset, keys));
                            next.addAll(getKeys((int) newColsOffset, keys));
                            if (!prev.equals(next)) {
                                setKeyChange(true);
                            }
                            break;
                        case UNKNOWN:
                            next.addAll(getKeys((int) newColsOffset, keys));
                            break;
                    }
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return keysValue;
    }

    @Override
    public List getPrimaryValues() {
        try {
            List filedList = getFieldList();
            List primaryKeys = getPrimaryKeyValue();
            if (primaryKeys == null) {
                primaryKeys = new ArrayList();
            }
            Map keyValue = new HashMap();
            if (filedList != null) {
                for (Field field : filedList) {
                    if ("binary".equalsIgnoreCase(field.getEncoding())) {
                        keyValue.put(
                                field.getFieldname().toLowerCase(),
                                field.getValue() == null
                                        ? null
                                        : new String(field.getValue().getBytes()));
                    } else {
                        keyValue.put(
                                field.getFieldname().toLowerCase(),
                                field.getValue() == null
                                        ? null
                                        : field.getValue().toString(field.getEncoding()));
                    }
                }
            }
            List primaryValues = new ArrayList(primaryKeys.size());
            for (String primaryKey : primaryKeys) {
                primaryValues.add(keyValue.get(primaryKey.toLowerCase()));
            }
            return primaryValues;
        } catch (Exception e) {
            throw new LogMessageException(e.getMessage(), e.getCause());
        }
    }

    /**
     * 获取主键value
     *
     * @return 主键列的列表
     */
    @Override
    public List getPrimaryKeyValue() {
        try {
            if (pkValues != null) {
                return pkValues;
            }
            if (colNamesOffset < 0 || pkKeysOffset < 0) {
                return null;
            }
            pkValues = new ArrayList();
            List pks = BinaryMessageUtils.getArray(byteBuf.array(), (int) pkKeysOffset);
            List names =
                    BinaryMessageUtils.getByteStringList(byteBuf.array(), colNamesOffset);
            if (pks != null) {
                for (int idx : pks) {
                    pkValues.add(names.get(idx).toString(DEFAULT_ENCODING));
                }
            }
            return pkValues;

        } catch (Exception e) {
            throw new LogMessageException(e.getMessage(), e.getCause());
        }
    }

    /**
     * Get tuples of index. For example, column 1 is the primary key and column 2 and 3 are unique
     * keys. The returned format is the list of two arrays, the first array is {0} and the seconds
     * array is {1,2}
     *
     * @return the tuples of index for primary and unique constraints.
     */
    @Override
    public List getPrimaryAndUniqueConstraintColumnIndexTuples() {
        List tuples = new ArrayList();
        try {
            if ((int) pkValOffset > 0) {
                List rawConstraintByteString =
                        BinaryMessageUtils.getByteStringList(byteBuf.array(), pkValOffset);
                if (rawConstraintByteString != null && !rawConstraintByteString.isEmpty()) {
                    /**
                     * The raw format is "(0,1),(2,3)" or "(", the last one is for empty primary or
                     * unique constraint
                     */
                    for (int i = 0; i < rawConstraintByteString.size(); i++) {
                        String rawConstraintStringArray = rawConstraintByteString.get(i).toString();
                        for (String rawConstraintString : rawConstraintStringArray.split("\\),")) {
                            if (StringUtils.isNotEmpty(rawConstraintString)) {
                                int m = 0;
                                while (m < rawConstraintString.length()) {
                                    int leftIndex = rawConstraintString.indexOf('(', m);
                                    if (leftIndex == -1) {
                                        break;
                                    }
                                    int rightIndex = rawConstraintString.indexOf(')', leftIndex);
                                    if (rightIndex == -1) {
                                        if (rawConstraintString.length() == 1) {
                                            throw new IOException(
                                                    "Missing index and ) for constraints: "
                                                            + rawConstraintByteString);
                                        } else {
                                            /* throw new IOException("Missing ] for constraints: " + rawConstraintByteString
                                            + "the correct format shoud like (0,1),(2,3) or ( "); */
                                            rightIndex = rawConstraintString.length();
                                        }
                                    }
                                    m = rightIndex;
                                    String[] parts =
                                            rawConstraintString
                                                    .substring(leftIndex + 1, rightIndex)
                                                    .split(",");
                                    if (parts != null && parts.length > 0) {
                                        int[] tuple = new int[parts.length];
                                        for (int j = 0; j < parts.length; j++) {
                                            tuple[j] = Integer.valueOf(parts[j]).intValue();
                                        }
                                        tuples.add(tuple);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new LogMessageException(e);
        }

        return tuples;
    }

    @Override
    public List getFirstPKValue() {
        try {
            if ((int) pkValOffset < 0) {
                return null;
            } else {
                return BinaryMessageUtils.getByteStringList(byteBuf.array(), pkValOffset);
            }

        } catch (Exception e) {
            throw new LogMessageException(e.getMessage(), e.getCause());
        }
    }

    @Override
    public List getPrimaryKeysList() {
        List primaryKeyStringList = getPrimaryKeyValue();
        List pkKeyName = new ArrayList();
        if (primaryKeyStringList != null) {
            for (String idx : primaryKeyStringList) {
                pkKeyName.add(idx);
            }
            return pkKeyName;
        } else {
            return null;
        }
    }

    @Override
    public String getPrimaryKeys() {
        List primaryKeyStringList = getPrimaryKeyValue();
        if (primaryKeyStringList != null) {
            StringBuilder pkKeyName = new StringBuilder();
            for (String idx : primaryKeyStringList) {
                if (pkKeyName.length() != 0) {
                    pkKeyName.append(",");
                }
                pkKeyName.append(idx);
            }
            return pkKeyName.toString();
        } else {
            return "";
        }
    }

    public long getMetaVersion() {
        return this.metaVersion;
    }

    @Override
    public String getUniqueColNames() {
        try {
            if (uniqueKeyList == null) {
                if ((int) ukColsOffset < 0) {
                    uniqueKeyList = "";
                    return uniqueKeyList;
                } else {
                    List uks =
                            BinaryMessageUtils.getArray(byteBuf.array(), (int) ukColsOffset);
                    List names =
                            BinaryMessageUtils.getByteStringList(byteBuf.array(), colNamesOffset);
                    StringBuilder ukKeyName = new StringBuilder();
                    if (uks != null && names != null) {
                        for (int idx : uks) {
                            if (ukKeyName.length() != 0) {
                                ukKeyName.append(",");
                            }
                            ukKeyName.append(names.get(idx).toString(DEFAULT_ENCODING));
                        }
                    }
                    uniqueKeyList = ukKeyName.toString();
                    return uniqueKeyList;
                }
            }
        } catch (Exception e) {
            throw new LogMessageException(e.getMessage(), e.getCause());
        }
        return uniqueKeyList;
    }

    public List getTimeMarks() throws IOException {
        if (timeMarkOffset == -1) {
            return null;
        } else {
            if (timeMarks == null) {
                timeMarks = BinaryMessageUtils.getArray(byteBuf.array(), (int) timeMarkOffset);
            }
            return timeMarks;
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();

        builder.append("type:" + getOpt()).append(SEP);
        builder.append("record_id:" + getId()).append(SEP);
        builder.append("db:" + getDbName()).append(SEP);
        builder.append("tb:" + getTableName()).append(SEP);
        builder.append("serverId:" + getServerId()).append(SEP);
        builder.append("checkpoint:" + getCheckpoint()).append(SEP);
        builder.append("primary_value:" + getPrimaryKeys()).append(SEP);
        builder.append("unique_keys:" + getUniqueColNames()).append(SEP);
        builder.append(SEP);
        getFieldList();
        if (fields != null) {
            for (Field field : fields) {
                builder.append(field.toString());
            }
        }
        builder.append(SEP);
        return builder.toString();
    }

    private long getCRCValue() throws IOException {
        long crcValue = 0L;
        if (tailOffset == -1) {
            return 0L;
        }
        List list = BinaryMessageUtils.getArray(byteBuf.array(), (int) tailOffset);
        if (list == null || list.size() != 12) {
            return 0L;
        }
        crcValue += (long) list.get(8);
        crcValue += ((long) list.get(9)) << 8;
        crcValue += ((long) list.get(10)) << 16;
        crcValue += ((long) list.get(11)) << 24;
        return crcValue;
    }

    @Override
    public String getTimestampUsec() throws IOException {
        long usecs = 0L;
        if (tailOffset == -1) {
            return "0";
        }
        List list = BinaryMessageUtils.getArray(byteBuf.array(), (int) tailOffset);
        if (list == null || list.size() != 12) {
            return "0";
        }
        usecs += (long) list.get(4);
        usecs += ((long) list.get(5)) << 8;
        usecs += ((long) list.get(6)) << 16;
        usecs += ((long) list.get(7)) << 24;
        return Long.toString(usecs);
    }

    public String getEncodingStr() {
        try {
            return BinaryMessageUtils.getString(byteBuf.array(), (int) encoding, DEFAULT_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new LogMessageException(e.getMessage(), e.getCause());
        }
    }

    public long getFileNameOffset() {
        return fileNameOffset;
    }

    public long getFileOffset() {
        return fileOffset;
    }

    public ByteBuf getByteBuff() {
        return byteBuf;
    }

    public void releaseContents() {
        // release reference to let gc collect mem
        this.fields = null;
        this.pkValues = null;
        this.keysValue = null;
        this.primaryKeyIndexList = null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy