Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,5 @@ __recovery/

# Castalia statistics file (since XE7 Castalia is distributed with Delphi)
*.stat
* .idea
*
.idea

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ MIT
## Author

Gordienko Roman

133 changes: 133 additions & 0 deletions uRecordParser.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
unit uRecordParser;

interface

uses
Windows, SysUtils, uStructs, uDataPage;

const
recDeleted = $01; // ������ �������� ��� ��������
recModified = $02; // ������ ���� ��������������
recHasSegment = $04; // ������ �������� ������� (����� BLOB)
recHasNulls = $08; // ������ �������� NULL ��������
recVersioned = $10; // ������ �������������� (MVCC)
recLocalMod = $20; // ��������� �����������
recRemoteMod = $40; // �������� �����������
recLastForm = $80; // ��������� ����� ������ (� �������?)


MIN_REC_HEADER_SIZE = 18;

type

TRecordHeader = record
Flags: UShort; // rec_flags
TransactionID: SLong; // rec_transaction
BackPointer: SLong; // rec_back (SLONG - ��� ����������� PageNum � SlotNum)
NextPointer: SLong; // rec_next (SLONG - �����������)
Format: UShort; // rec_format
DataLength: UShort; // rec_length
end;

TParsedRecordFragment = record
Header: TRecordHeader;
Data: TBytes; // ����������� ������ ������ (rec_data)
OffsetInPage: Word; // �������� ��������� � �������� Data Page
IsDeleted: Boolean; // ������� ����: ��������� �������� Flags
// PageNum, SlotNum: Integer; // ����� ��������, ���� ����������� rec_back/next
end;


TParsedRecordFragmentArray = array of TParsedRecordFragment;


TRecordParser = class
private
// ���������� ������� ��� ���������� ��������� �� ������
function GetRecordHeaderFromBytes(const RecordFragmentData: TBytes): TRecordHeader;
public
// ������ ���� �������� ������ (TRecordFragment �� uDataPage)
// ����: RawFragmentData - ����� ��������� (TRecordFragment.Data)
// FragmentOffset - �������� ����� ��������� �� �������� (TRecordFragment.Offset)
// �������: TParsedRecordFragment � ������������� �������
function ParseRecordFragment(const RawFragmentData: TBytes; FragmentOffset: Word): TParsedRecordFragment;

// ������ ������ ���������� ������� (��������, ��������� uDataPage.ExtractDataFragments)
// ����: RawFragments - ������ TRecordFragment
// �������: ������ TParsedRecordFragmentArray
function ParseRecordFragments(const RawFragments: TRecordFragmentsArray): TParsedRecordFragmentArray;

// ��������������� �������: ���������, �������� �� ������ ��� ��������
function IsRecordDeleted(const Header: TRecordHeader): Boolean;

// ��������������� �������: ���������, �������������� �� ������
function IsRecordVersioned(const Header: TRecordHeader): Boolean;
end;

implementation

function TRecordParser.GetRecordHeaderFromBytes(const RecordFragmentData: TBytes): TRecordHeader;
var
Offset: Integer;
begin
if Length(RecordFragmentData) < MIN_REC_HEADER_SIZE then
raise Exception.Create('Record fragment data too small for record header.');

Offset := 0;
Result.Flags := PWord(@RecordFragmentData[Offset])^;
Inc(Offset, 2);

Result.TransactionID := PLongInt(@RecordFragmentData[Offset])^;
Inc(Offset, 4);

Result.BackPointer := PLongInt(@RecordFragmentData[Offset])^;
Inc(Offset, 4);

Result.NextPointer := PLongInt(@RecordFragmentData[Offset])^;
Inc(Offset, 4);

Result.Format := PWord(@RecordFragmentData[Offset])^;
Inc(Offset, 2);

Result.DataLength := PWord(@RecordFragmentData[Offset])^;
Inc(Offset, 2);

// ��������, ��� ����� ������ �� ������� �� ������� ������
if Offset + Result.DataLength > Length(RecordFragmentData) then
raise Exception.Create('Record header specifies data length exceeding fragment buffer size.');
end;

function TRecordParser.ParseRecordFragment(const RawFragmentData: TBytes; FragmentOffset: Word): TParsedRecordFragment;
begin
Result.Header := GetRecordHeaderFromBytes(RawFragmentData);
Result.OffsetInPage := FragmentOffset;
Result.IsDeleted := IsRecordDeleted(Result.Header);

// �������� ������ ������ (rec_data)
SetLength(Result.Data, Result.Header.DataLength);
if Result.Header.DataLength > 0 then
Move(RawFragmentData[MIN_REC_HEADER_SIZE], Result.Data[0], Result.Header.DataLength);
end;

function TRecordParser.ParseRecordFragments(const RawFragments: TRecordFragmentsArray): TParsedRecordFragmentArray;
var
i: Integer;
begin
SetLength(Result, Length(RawFragments));
for i := 0 to High(RawFragments) do
begin
Result[i] := ParseRecordFragment(RawFragments[i].Data, RawFragments[i].Offset);
end;
end;

function TRecordParser.IsRecordDeleted(const Header: TRecordHeader): Boolean;
begin
Result := (Header.Flags and recDeleted) <> 0;
end;

function TRecordParser.IsRecordVersioned(const Header: TRecordHeader): Boolean;
begin
Result := (Header.Flags and recVersioned) <> 0;
end;

end.