Skip to content

Commit 64efa8d

Browse files
committed
Added support for parsing index records
1 parent dfd2a8b commit 64efa8d

File tree

7 files changed

+196
-22
lines changed

7 files changed

+196
-22
lines changed

src/OrcaMDF.RawCore.Tests/OrcaMDF.RawCore.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
<Compile Include="RawDatabaseFixtures.cs" />
6767
<Compile Include="RawPageHeaderTests.cs" />
6868
<Compile Include="RawPageTests.cs" />
69+
<Compile Include="Records\RawIndexRecordTests.cs" />
6970
<Compile Include="Records\RawPrimaryRecordTests.cs" />
7071
<Compile Include="Utilities\SQL2012\SQL2012SysallocunitsTests.cs" />
7172
<Compile Include="Utilities\SQL2012\SQL2012SyscolparsTests.cs" />
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using NUnit.Framework;
2+
using OrcaMDF.Framework;
3+
using OrcaMDF.RawCore.Records;
4+
using OrcaMDF.RawCore.Types;
5+
6+
namespace OrcaMDF.RawCore.Tests.Records
7+
{
8+
public class RawIndexRecordTests : BaseFixture
9+
{
10+
[Test]
11+
public void BranchLevelNonClusteredWithNullBitmapAndVariableLength2012_FirstRecord()
12+
{
13+
var rawBytes = TestHelper.GetBytesFromByteString(@"
14+
36ca0000 00760200 00010002 00000100 3e006100
15+
30004000 61006400 76006500 6e007400 75007200
16+
65002d00 77006f00 72006b00 73002e00 63006f00
17+
6d00
18+
");
19+
20+
var record = new RawIndexRecord(new ArrayDelimiter<byte>(rawBytes), 11, 1);
21+
22+
// First record has implicit null values
23+
Assert.AreEqual(1, record.ChildFileID);
24+
Assert.AreEqual(630, record.ChildPageID);
25+
}
26+
27+
[Test]
28+
public void BranchLevelNonClusteredWithNullBitmapAndVariableLength2012_SecondRecord()
29+
{
30+
var rawBytes = TestHelper.GetBytesFromByteString(@"
31+
36d90000 00780200 00010002 00000100 46006300
32+
68007200 69007300 36004000 61006400 76006500
33+
6e007400 75007200 65002d00 77006f00 72006b00
34+
73002e00 63006f00 6d00
35+
");
36+
37+
var record = new RawIndexRecord(new ArrayDelimiter<byte>(rawBytes), 11, 1);
38+
39+
Assert.AreEqual(1, record.ChildFileID);
40+
Assert.AreEqual(632, record.ChildPageID);
41+
42+
dynamic row = RawColumnParser.Parse(record, new IRawType[] {
43+
RawType.NVarchar("EmailAddress"),
44+
RawType.Int("CustomerID")
45+
});
46+
47+
Assert.AreEqual("chris6@adventure-works.com", row.EmailAddress);
48+
Assert.AreEqual(217, row.CustomerID);
49+
}
50+
51+
[Test]
52+
public void LeafLevelNonclusteredWithNullBitmapAndVariableLength2012_A()
53+
{
54+
var rawBytes = TestHelper.GetBytesFromByteString(@"
55+
36a50100 00020000 01004400 66006f00 72007200
56+
65007300 74003000 40006100 64007600 65006e00
57+
74007500 72006500 2d007700 6f007200 6b007300
58+
2e006300 6f006d00
59+
");
60+
61+
var record = new RawIndexRecord(new ArrayDelimiter<byte>(rawBytes), 5, 0);
62+
63+
Assert.AreEqual(4, record.FixedLengthData.Count);
64+
Assert.AreEqual(true, record.HasNullBitmap);
65+
Assert.AreEqual(true, record.HasVariableLengthColumns);
66+
Assert.AreEqual(2, record.NullBitmapColumnCount);
67+
Assert.IsNull(record.ChildPageID);
68+
Assert.IsNull(record.ChildFileID);
69+
70+
dynamic row = RawColumnParser.Parse(record, new IRawType[] {
71+
RawType.NVarchar("EmailAddress"),
72+
RawType.Int("CustomerID")
73+
});
74+
75+
Assert.AreEqual("forrest0@adventure-works.com", row.EmailAddress);
76+
Assert.AreEqual(421, row.CustomerID);
77+
}
78+
79+
[Test]
80+
public void LeafLevelNonclusteredWithNullBitmapAndVariableLength2012_B()
81+
{
82+
var rawBytes = TestHelper.GetBytesFromByteString(@"
83+
36947500 00020000 01004200 6a006500 73007300
84+
69006500 30004000 61006400 76006500 6e007400
85+
75007200 65002d00 77006f00 72006b00 73002e00
86+
63006f00 6d00
87+
");
88+
89+
var record = new RawIndexRecord(new ArrayDelimiter<byte>(rawBytes), 5, 0);
90+
91+
Assert.AreEqual(4, record.FixedLengthData.Count);
92+
Assert.AreEqual(true, record.HasNullBitmap);
93+
Assert.AreEqual(true, record.HasVariableLengthColumns);
94+
Assert.AreEqual(2, record.NullBitmapColumnCount);
95+
Assert.IsNull(record.ChildPageID);
96+
Assert.IsNull(record.ChildFileID);
97+
98+
dynamic row = RawColumnParser.Parse(record, new IRawType[] {
99+
RawType.NVarchar("EmailAddress"),
100+
RawType.Int("CustomerID")
101+
});
102+
103+
Assert.AreEqual("jessie0@adventure-works.com", row.EmailAddress);
104+
Assert.AreEqual(30100, row.CustomerID);
105+
}
106+
}
107+
}

src/OrcaMDF.RawCore/OrcaMDF.RawCore.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<Compile Include="RawDataFile.cs" />
4747
<Compile Include="RawPage.cs" />
4848
<Compile Include="RawPageHeader.cs" />
49+
<Compile Include="Records\RawIndexRecord.cs" />
4950
<Compile Include="Records\RawPrimaryRecord.cs" />
5051
<Compile Include="Records\RawRecord.cs" />
5152
<Compile Include="Types\RawBinary.cs" />

src/OrcaMDF.RawCore/RawColumnParser.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ public static IEnumerable<dynamic> BestEffortParse(IEnumerable<RawPage> pages, I
2525

2626
try
2727
{
28-
var primaryRecord = record as RawPrimaryRecord;
29-
30-
if (primaryRecord != null)
31-
parsedRecord = Parse(primaryRecord, schema);
28+
parsedRecord = Parse(record, schema);
3229
}
3330
catch
3431
{ }
@@ -41,15 +38,15 @@ public static IEnumerable<dynamic> BestEffortParse(IEnumerable<RawPage> pages, I
4138

4239
public static IEnumerable<dynamic> Parse(IEnumerable<RawPage> pages, IRawType[] schema)
4340
{
44-
return pages.SelectMany(x => x.Records).Cast<RawPrimaryRecord>().Select(x => Parse(x, schema));
41+
return pages.SelectMany(x => x.Records).Select(x => Parse(x, schema));
4542
}
4643

47-
public static IEnumerable<dynamic> Parse(IEnumerable<RawPrimaryRecord> records, IRawType[] schema)
44+
public static IEnumerable<dynamic> Parse(IEnumerable<RawRecord> records, IRawType[] schema)
4845
{
4946
return records.Select(x => Parse(x, schema));
5047
}
5148

52-
public static dynamic Parse(RawPrimaryRecord record, IRawType[] schema)
49+
public static dynamic Parse(RawRecord record, IRawType[] schema)
5350
{
5451
return Parse(
5552
record.FixedLengthData != null ? record.FixedLengthData.ToArray() : null,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using OrcaMDF.Framework;
2+
using System;
3+
using System.Collections;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace OrcaMDF.RawCore.Records
8+
{
9+
public class RawIndexRecord : RawRecord
10+
{
11+
public int? ChildPageID { get; private set; }
12+
public short? ChildFileID { get; private set; }
13+
14+
private short pminlen;
15+
16+
public RawIndexRecord(ArrayDelimiter<byte> bytes, short pminlen, byte level) : base(bytes)
17+
{
18+
this.pminlen = pminlen;
19+
20+
// Fixed length size
21+
FixedLengthSize = (short)(pminlen - 1);
22+
23+
// Fixed length data
24+
FixedLengthData = new ArrayDelimiter<byte>(bytes.SourceArray, bytes.Offset + 1, FixedLengthSize);
25+
26+
// Null bitmap column count
27+
NullBitmapColumnCount = BitConverter.ToInt16(bytes.SourceArray, FixedLengthData.Offset + FixedLengthData.Count);
28+
29+
// Null bitmap
30+
NullBitmapRawBytes = new ArrayDelimiter<byte>(bytes.SourceArray, FixedLengthData.Offset + FixedLengthData.Count + 2, (NullBitmapColumnCount + 7) / 8);
31+
NullBitmap = new BitArray(NullBitmapRawBytes.ToArray());
32+
33+
// Variable length offset array
34+
if (HasVariableLengthColumns)
35+
{
36+
int endOfNullBitmapPointer = FixedLengthData.Offset + FixedLengthData.Count + 2 + NullBitmapRawBytes.Count;
37+
38+
// Number of pointers
39+
NumberOfVariableLengthOffsetArrayEntries = BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + endOfNullBitmapPointer);
40+
41+
// Pointers
42+
VariableLengthOffsetArray = new List<short>();
43+
for (int i = 0; i < NumberOfVariableLengthOffsetArrayEntries; i++)
44+
VariableLengthOffsetArray.Add(BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + endOfNullBitmapPointer + 2 + i * 2));
45+
46+
// Values
47+
int endOfVariableLengthOffsetArrayPointer = endOfNullBitmapPointer + 2 + NumberOfVariableLengthOffsetArrayEntries.Value * 2;
48+
int previousPointer = endOfVariableLengthOffsetArrayPointer;
49+
50+
VariableLengthOffsetValues = new List<ArrayDelimiter<byte>>();
51+
foreach (short entry in VariableLengthOffsetArray)
52+
{
53+
VariableLengthOffsetValues.Add(new ArrayDelimiter<byte>(bytes.SourceArray, bytes.Offset + previousPointer, entry - previousPointer));
54+
previousPointer = entry;
55+
}
56+
}
57+
58+
// If we're at a non-leaf level, parse the page pointers
59+
if (level > 0)
60+
{
61+
ChildPageID = BitConverter.ToInt32(bytes.SourceArray, bytes.Offset + 1 + FixedLengthData.Count - 6);
62+
ChildFileID = BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + 1 + FixedLengthData.Count - 2);
63+
}
64+
}
65+
}
66+
}

src/OrcaMDF.RawCore/Records/RawPrimaryRecord.cs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,32 @@ namespace OrcaMDF.RawCore.Records
99
public class RawPrimaryRecord : RawRecord
1010
{
1111
public bool IsGhostForwardedRecord { get; private set; }
12-
public short NullBitmapPointer { get; private set; }
13-
public ArrayDelimiter<byte> FixedLengthData { get; private set; }
14-
public short NullBitmapColumnCount { get; private set; }
15-
public BitArray NullBitmap { get; private set; }
16-
public short? NumberOfVariableLengthOffsetArrayEntries { get; private set; }
17-
public List<short> VariableLengthOffsetArray { get; private set; }
18-
public List<ArrayDelimiter<byte>> VariableLengthOffsetValues { get; private set; }
1912

2013
internal byte RawStatusByteB { get; private set; }
21-
internal ArrayDelimiter<byte> NullBitmapRawBytes {get; private set; }
2214

2315
public RawPrimaryRecord(ArrayDelimiter<byte> bytes) : base(bytes)
2416
{
2517
// Status byte B
2618
RawStatusByteB = bytes[1];
2719
IsGhostForwardedRecord = (bytes[1] & 0x01) == 0x01; // First bit
2820

29-
// Null bitmap pointer
30-
NullBitmapPointer = BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + 2);
21+
// Fixed length size
22+
FixedLengthSize = (short)(BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + 2) - 4);
3123

3224
// Fixed length data
33-
FixedLengthData = new ArrayDelimiter<byte>(bytes.SourceArray, bytes.Offset + 4, NullBitmapPointer - 4);
25+
FixedLengthData = new ArrayDelimiter<byte>(bytes.SourceArray, bytes.Offset + 4, FixedLengthSize);
3426

3527
// Null bitmap column count
36-
NullBitmapColumnCount = BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + NullBitmapPointer);
28+
NullBitmapColumnCount = BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + 4 + FixedLengthSize);
3729

3830
// Null bitmap
39-
NullBitmapRawBytes = new ArrayDelimiter<byte>(bytes.SourceArray, bytes.Offset + NullBitmapPointer + 2, (NullBitmapColumnCount + 7) / 8);
31+
NullBitmapRawBytes = new ArrayDelimiter<byte>(bytes.SourceArray, bytes.Offset + 4 + FixedLengthSize + 2, (NullBitmapColumnCount + 7) / 8);
4032
NullBitmap = new BitArray(NullBitmapRawBytes.ToArray());
4133

4234
// Variable length offset array
4335
if (HasVariableLengthColumns)
4436
{
45-
int endOfNullBitmapPointer = NullBitmapPointer + 2 + NullBitmapRawBytes.Count;
37+
int endOfNullBitmapPointer = 4 + FixedLengthSize + 2 + NullBitmapRawBytes.Count;
4638

4739
// Number of pointers
4840
NumberOfVariableLengthOffsetArrayEntries = BitConverter.ToInt16(bytes.SourceArray, bytes.Offset + endOfNullBitmapPointer);

src/OrcaMDF.RawCore/Records/RawRecord.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using OrcaMDF.Framework;
2+
using System.Collections;
3+
using System.Collections.Generic;
24

35
namespace OrcaMDF.RawCore.Records
46
{
@@ -9,10 +11,18 @@ public class RawRecord
911
public bool HasNullBitmap { get; private set; }
1012
public bool HasVariableLengthColumns { get; private set; }
1113
public bool HasVersioningInformation { get; private set; }
14+
public short FixedLengthSize { get; protected set; }
15+
public ArrayDelimiter<byte> FixedLengthData { get; protected set; }
16+
public short NullBitmapColumnCount { get; protected set; }
17+
public BitArray NullBitmap { get; protected set; }
18+
public short? NumberOfVariableLengthOffsetArrayEntries { get; protected set; }
19+
public List<short> VariableLengthOffsetArray { get; protected set; }
20+
public List<ArrayDelimiter<byte>> VariableLengthOffsetValues { get; protected set; }
1221

1322
internal byte RawStatusByteA { get; private set; }
23+
internal ArrayDelimiter<byte> NullBitmapRawBytes { get; set; }
1424

15-
public RawRecord(ArrayDelimiter<byte> bytes)
25+
internal RawRecord(ArrayDelimiter<byte> bytes)
1626
{
1727
// Status byte A
1828
RawStatusByteA = bytes[0];

0 commit comments

Comments
 (0)