diff --git a/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/page/SdiPage.java b/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/page/SdiPage.java index 1bb2661..d31c360 100644 --- a/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/page/SdiPage.java +++ b/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/page/SdiPage.java @@ -3,6 +3,19 @@ */ package com.alibaba.innodb.java.reader.page; +import com.alibaba.innodb.java.reader.exception.ReaderException; +import com.alibaba.innodb.java.reader.page.index.*; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import static com.alibaba.innodb.java.reader.Constants.BYTES_OF_INFIMUM; +import static com.alibaba.innodb.java.reader.Constants.BYTES_OF_SUPREMUM; +import static com.alibaba.innodb.java.reader.SizeOf.*; +import static com.alibaba.innodb.java.reader.SizeOf.SIZE_OF_PAGE_DIR_SLOT; +import static com.google.common.base.Preconditions.checkState; + /** * Since MySQL8.0, there is SDI, a.k.a Serialized Dictionary Information(SDI). * @@ -10,8 +23,55 @@ */ public class SdiPage extends AbstractPage { + private IndexHeader indexHeader; + + private FsegHeader fsegHeader; + + private RecordHeader infimumHeader; + + private RecordHeader supremumHeader; + + private int[] dirSlots; + + private List sdiRecordList = new LinkedList<>(); + public SdiPage(InnerPage innerPage) { super(innerPage); + this.indexHeader = IndexHeader.fromSlice(sliceInput); + if (this.indexHeader.getFormat() != PageFormat.COMPACT) { + throw new ReaderException("only support COMPACT format"); + } + this.fsegHeader = FsegHeader.fromSlice(sliceInput); + + this.infimumHeader = RecordHeader.fromSlice(sliceInput); + checkState(Arrays.equals(sliceInput.readByteArray(SIZE_OF_MUM_RECORD), BYTES_OF_INFIMUM)); + + this.supremumHeader = RecordHeader.fromSlice(sliceInput); + checkState(Arrays.equals(sliceInput.readByteArray(SIZE_OF_MUM_RECORD), BYTES_OF_SUPREMUM)); + + int dirSlotNum = this.indexHeader.getNumOfDirSlots(); + dirSlots = new int[dirSlotNum]; + sliceInput.setPosition(SIZE_OF_PAGE - SIZE_OF_FIL_TRAILER - dirSlotNum * SIZE_OF_PAGE_DIR_SLOT); + for (int i = 0; i < dirSlotNum; i++) { + dirSlots[dirSlotNum - i - 1] = sliceInput.readUnsignedShort(); + } + sliceInput.setPosition(dirSlots[0]); + RecordHeader recordHeader = null; + while (true) { + sliceInput.decrPosition(SIZE_OF_REC_HEADER); + recordHeader = RecordHeader.fromSlice(sliceInput); + if (recordHeader.getRecordType() == RecordType.INFIMUM) { + sliceInput.setPosition(sliceInput.position() + recordHeader.getNextRecOffset()); + continue; + } + if (recordHeader.getRecordType() == RecordType.SUPREMUM) { + break; + } + int pos = sliceInput.position(); + SdiRecord record = new SdiRecord(sliceInput, recordHeader); + this.sdiRecordList.add(record); + sliceInput.setPosition(pos + recordHeader.getNextRecOffset()); + } } } diff --git a/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/page/index/SdiRecord.java b/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/page/index/SdiRecord.java new file mode 100644 index 0000000..4499c95 --- /dev/null +++ b/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/page/index/SdiRecord.java @@ -0,0 +1,80 @@ +package com.alibaba.innodb.java.reader.page.index; + +import com.alibaba.innodb.java.reader.util.SliceInput; +import com.alibaba.innodb.java.reader.util.ZlibUtil; +import lombok.Data; + +import java.nio.charset.StandardCharsets; + +import static com.alibaba.innodb.java.reader.SizeOf.SIZE_OF_REC_HEADER; + +/** + * @author jie xu + * @description + * @created 2020/12/21 + **/ +@Data +public class SdiRecord { + + private int contentLength; + + private RecordHeader recordHeader; + + private int sdiType; + + private long sdiId; + + private long sdiTransId; + + private int unzipLength; + + private int zipLength; + + private String content; + + public SdiRecord(SliceInput sliceInput, RecordHeader recordHeader) { + this.recordHeader = recordHeader; + int position = sliceInput.position(); + this.contentLength = parseContentLength(sliceInput); + sliceInput.setPosition(position); + this.sdiType = sliceInput.readInt(); + this.sdiId = sliceInput.readLong(); + this.sdiTransId = sliceInput.read6BytesInt(); + //skip rollBackRef + sliceInput.setPosition(sliceInput.position() + 7); + this.unzipLength = sliceInput.readInt(); + this.zipLength = sliceInput.readInt(); + if (this.contentLength != zipLength && this.contentLength != unzipLength) { + throw new RuntimeException("sdi record parse error!"); + } + byte[] buffer = new byte[this.contentLength]; + sliceInput.readBytes(buffer); + if (this.contentLength == zipLength) { + buffer = ZlibUtil.decompress(buffer); + } + this.content = new String(buffer, 0, buffer.length, StandardCharsets.US_ASCII); + sliceInput.setPosition(position); + } + + private int parseContentLength(SliceInput sliceInput) { + int resu = 0; + sliceInput.decrPosition(SIZE_OF_REC_HEADER + 1); + int first = sliceInput.readUnsignedByte(); + if ((first & 0x80) != 0) { + resu = (first & 0x3f) << 8; + if ((first & 0x40) != 0x00) { + //todo big data length, read from overflowPage (BLOB page?) + throw new RuntimeException("unsupported record length"); + } else { + sliceInput.decrPosition(2); + int second = sliceInput.readUnsignedByte(); + sliceInput.readUnsignedByte(); + resu += second; + } + } else { + resu = first; + } + return resu; + } + +} diff --git a/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/util/ZlibUtil.java b/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/util/ZlibUtil.java new file mode 100644 index 0000000..ac16bf5 --- /dev/null +++ b/innodb-java-reader/src/main/java/com/alibaba/innodb/java/reader/util/ZlibUtil.java @@ -0,0 +1,37 @@ +package com.alibaba.innodb.java.reader.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.Inflater; + +/** + * @author jie xu + * @description + * @created 2020/12/22 + **/ +public class ZlibUtil { + private static final int BUF_SIZE = 1024; + public static byte[] decompress(byte[] data) { + Inflater decompresser = new Inflater(); + decompresser.reset(); + decompresser.setInput(data); + ByteArrayOutputStream o = new ByteArrayOutputStream(data.length); + try { + byte[] buf = new byte[BUF_SIZE]; + while (!decompresser.finished()) { + int i = decompresser.inflate(buf); + o.write(buf, 0, i); + } + return o.toByteArray(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + try { + decompresser.end(); + o.close(); + } catch (IOException ignored) { + + } + } + } +}