1
0
mirror of https://github.com/akpaevj/OneSTools.FileDatabase.git synced 2026-04-24 19:13:53 +02:00
Files
OneSTools.FileDatabase/OneSTools.FileDatabase/HighLevel/TableRows.cs
T
2025-07-14 22:48:37 +03:00

335 lines
11 KiB
C#

using System.Collections.Generic;
using System.Text;
using System;
using OneSTools.FileDatabase.LowLevel;
using OneSTools.FileDatabase.LowLevel.Files;
using System.Buffers.Binary;
using System.Collections;
using System.Globalization;
namespace OneSTools.FileDatabase.HighLevel
{
internal class TableRows : IReadOnlyList<TableRow>
{
private readonly FileDatabaseStream _stream;
private readonly Table _table;
private DataFile _dataFile;
private UnlimitedLengthDataFile _unlimitedLengthDataFile;
internal TableRows(FileDatabaseStream stream, Table table)
{
_stream = stream;
_table = table;
}
public TableRow this[int index]
{
get
{
if (index >= Count)
throw new IndexOutOfRangeException();
else
{
return Get(index + 1);
}
}
}
public int Count
{
get
{
InitializeFiles();
return (int)(_dataFile?.RootPage.DataLength / Convert.ToUInt64(_table.MaxRowSize));
}
}
public IEnumerator<TableRow> GetEnumerator()
{
for (var i = 0; i < Count; i++)
{
yield return Get(i + 1);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private void InitializeFiles()
{
if (_dataFile == null)
{
if (_table.DataFilePage != 0)
{
_dataFile = new DataFile(_stream, _table.DataFilePage, _table.MaxRowSize, _table.Fields.Length > 0 && _table.Fields[0].Type != FieldType.RowVersion && _table.RecordLock);
if (_dataFile.HasData())
{
_dataFile.GoToDataStartingPosition();
_dataFile.GoToRow(1);
}
}
if (_table.UnlimitedLengthDataFilePage != 0)
{
_unlimitedLengthDataFile = new UnlimitedLengthDataFile(_stream, _table.UnlimitedLengthDataFilePage);
if (_unlimitedLengthDataFile.HasData())
_dataFile.GoToDataStartingPosition();
}
}
}
private TableRow Get(int rowNumber)
{
InitializeFiles();
_dataFile.GoToRow(rowNumber);
var values = new object[_table.Fields.Length];
var rawData = _dataFile.ReadRow();
if (rawData == null)
return null;
var currentOffset = 0;
for (var i = 0; i < _table.Fields.Length; i++)
{
var field = _table.Fields[i];
var fieldData = rawData[currentOffset..(currentOffset + field.MaxSize)];
currentOffset += field.MaxSize;
values[i] = field.Type switch
{
FieldType.Binary => ReadBinary(fieldData, field.Nullable),
FieldType.Logical => ReadLogical(fieldData, field.Nullable),
FieldType.Numeric => ReadNumericValue(fieldData, field.Precision, field.Nullable),
FieldType.NChar => ReadNChar(fieldData, field.Nullable),
FieldType.NVarChar => ReadNVarChar(fieldData, field.Nullable),
FieldType.RowVersion => ReadRowVersion(fieldData, field.Nullable),
FieldType.NText => ReadNText(fieldData, field.Nullable),
FieldType.Image => ReadImage(fieldData, field.Nullable),
FieldType.DateTime => ReadDateTime(fieldData, field.Nullable),
_ => throw new Exception($"Reading value for a field with type {field.Type} is not implemented")
};
}
return new TableRow(_table, values);
}
private byte[] ReadBinary(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
return valueData;
}
}
private bool? ReadLogical(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
return valueData[0] != 0;
}
}
private decimal? ReadNumericValue(byte[] data, int precision, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var doubleStr = new StringBuilder();
var negative = ReadTetrad(valueData[0]) == 0;
if (negative)
doubleStr.Append('-');
doubleStr.Append(ReadTetrad(valueData[0], true));
for (int i = 1; i < valueData.Length; i++)
{
doubleStr.Append(ReadTetrad(valueData[i]));
doubleStr.Append(ReadTetrad(valueData[i], true));
}
if (precision > 0)
doubleStr.Insert(doubleStr.Length - 1 - precision, '.');
// remove last zero, I don't know what is it
doubleStr.Remove(doubleStr.Length - 1, 1);
return decimal.Parse(doubleStr.ToString(), new NumberFormatInfo() { NumberDecimalSeparator = "." });
}
}
private void AddNumberToNumeric(StringBuilder doubleStr, int value)
{
// don't add leading zeros
if (value != 0)
doubleStr.Append(value);
else if (doubleStr.Length > 0)
doubleStr.Append(value);
}
private string ReadNChar(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
return Encoding.Unicode.GetString(valueData);
}
}
private string ReadNVarChar(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var length = BinaryPrimitives.ReadUInt16LittleEndian(valueData);
if (length == 0)
return "";
else
return Encoding.Unicode.GetString(valueData[2..(length * 2 + 2)]);
}
}
private DateTime? ReadDateTime(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var year1 = ReadTetrad(valueData[0]);
var year2 = ReadTetrad(valueData[0], true);
var year3 = ReadTetrad(valueData[1]);
var year4 = ReadTetrad(valueData[1], true);
var year = int.Parse($"{year1}{year2}{year3}{year4}");
var month1 = ReadTetrad(valueData[2]);
var month2 = ReadTetrad(valueData[2], true);
var month = int.Parse($"{month1}{month2}");
var day1 = ReadTetrad(valueData[3]);
var day2 = ReadTetrad(valueData[3], true);
var day = int.Parse($"{day1}{day2}");
var hour1 = ReadTetrad(valueData[4]);
var hour2 = ReadTetrad(valueData[4], true);
var hour = int.Parse($"{hour1}{hour2}");
var minute1 = ReadTetrad(valueData[5]);
var minute2 = ReadTetrad(valueData[5], true);
var minute = int.Parse($"{minute1}{minute2}");
var second1 = ReadTetrad(valueData[6]);
var second2 = ReadTetrad(valueData[6], true);
var second = int.Parse($"{second1}{second2}");
if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0)
return DateTime.MinValue;
else
return new DateTime(year, month, day, hour, minute, second);
}
}
private string ReadRowVersion(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var v1 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[..5]);
var v2 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[5..9]);
var v3 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[9..13]);
var v4 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[12..]);
return $"{v1}.{v2}.{v3}.{v4}";
}
}
private string ReadNText(byte[] data, bool nullable)
{
var valueData = ReadUnlimitedData(data, nullable);
if (valueData is null)
return null;
else
return Encoding.Unicode.GetString(valueData);
}
private byte[] ReadImage(byte[] data, bool nullable)
{
var valueData = ReadUnlimitedData(data, nullable);
if (valueData is null)
return null;
else
return valueData;
}
private byte[] ReadUnlimitedData(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
var valueData = GetValueData(data, nullable);
var blockNumber = BinaryPrimitives.ReadUInt32LittleEndian(valueData);
var dataLength = BinaryPrimitives.ReadUInt32LittleEndian(valueData[4..]);
_unlimitedLengthDataFile.GoToBlock(blockNumber);
var valueRawData = _unlimitedLengthDataFile.ReadBlockChain();
return valueRawData[..(int)dataLength];
}
private bool HasValue(byte[] data, bool nullable)
{
if (nullable)
return (bool)ReadLogical(data, false);
else
return true;
}
private static byte[] GetValueData(byte[] data, bool nullable)
=> nullable ? data[1..] : data;
private static int ReadTetrad(byte b, bool second = false)
{
if (second)
return b & 0b_0000_1111;
else
return b >> 4;
}
}
}