using System;
using System.IO;
using System.Collections;
namespace LumiSoft.Data.lsDB
{
/// <summary>
/// lsDB database record.
/// </summary>
public class LDB_Record
{
private DbFile m_pOwnerDb = null;
private DataPage m_pDataPage = null;
private int[] m_ColumnValueSize = null;
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="ownerDb">Table that owns this row.</param>
/// <param name="rowStartDataPage">Data page on what row starts.</param>
internal LDB_Record(DbFile ownerDb,DataPage rowStartDataPage)
{
m_pOwnerDb = ownerDb;
m_pDataPage = rowStartDataPage;
ParseRowInfo();
}
#region static method CreateRecord
/// <summary>
/// Creates record. Contains record info + record values.
/// </summary>
/// <param name="ownerDb">Roecord owner table.</param>
/// <param name="rowValues">Row values what to store to record.</param>
/// <returns></returns>
internal static byte[] CreateRecord(DbFile ownerDb,object[] rowValues)
{
if(ownerDb.Columns.Count != rowValues.Length){
throw new Exception("LDB_Record.CreateRecord m_pOwnerDb.Columns.Count != rowValues.Length !");
}
// Convert values to internal store format
ArrayList rowByteValues = new ArrayList();
for(int i=0;i<rowValues.Length;i++){
rowByteValues.Add(ConvertToInternalData(ownerDb.Columns[i],rowValues[i]));
}
/* RowInfo structure
(4 bytes) * columnCount - holds column data data length
xx bytes columns values
*/
MemoryStream msRecord = new MemoryStream();
// Write values sizes
for(int i=0;i<rowByteValues.Count;i++){
msRecord.Write(ldb_Utils.IntToByte(((byte[])rowByteValues[i]).Length),0,4);
}
// Write values
for(int i=0;i<rowByteValues.Count;i++){
byte[] val = (byte[])rowByteValues[i];
msRecord.Write(val,0,val.Length);
}
return msRecord.ToArray();
}
#endregion
#region method ParseRowInfo
/// <summary>
/// Parse row info.
/// </summary>
private void ParseRowInfo()
{
/* RowInfo structure
(4 bytes) * columnCount - holds column data data length
xx bytes columns values
*/
m_ColumnValueSize = new int[m_pOwnerDb.Columns.Count];
byte[] columnValueSizes = m_pDataPage.ReadData(0,4 * m_pOwnerDb.Columns.Count);
for(int i=0;i<m_pOwnerDb.Columns.Count;i++){
m_ColumnValueSize[i] = ldb_Utils.ByteToInt(columnValueSizes,i * 4);
}
}
#endregion
#region method GetColumnData
/// <summary>
/// Gets specified column data.
/// </summary>
/// <param name="columnIndex">Column index.</param>
/// <returns></returns>
private object GetColumnData(int columnIndex)
{
// Get column data start offset
int columnStartOffset = 4 * m_pOwnerDb.Columns.Count;
for(int i=0;i<columnIndex;i++){
columnStartOffset += m_ColumnValueSize[i];
}
int dataLength = m_ColumnValueSize[columnIndex];
int startDataPage = (int)Math.Floor(columnStartOffset / (double)m_pOwnerDb.DataPageDataAreaSize);
int offsetInStartDataPage = columnStartOffset - (startDataPage * m_pOwnerDb.DataPageDataAreaSize);
int dataOffset = 0;
int currentDataPageIndex = 0;
byte[] columnData = new byte[dataLength];
DataPage currentDataPage = this.DataPage;
while(dataOffset < dataLength){
// We haven't reached to data page on what data starts, just go to next continuing data page
if(currentDataPageIndex < startDataPage){
// Get next continuing data page
currentDataPage = new DataPage(m_pOwnerDb.DataPageDataAreaSize,m_pOwnerDb,currentDataPage.NextDataPagePointer);
currentDataPageIndex++;
}
// We need all this data page data + addtitional data pages data
else if((dataLength - dataOffset + offsetInStartDataPage) > currentDataPage.StoredDataLength){
currentDataPage.ReadData(columnData,dataOffset,m_pOwnerDb.DataPageDataAreaSize - offsetInStartDataPage,offsetInStartDataPage);
dataOffset += m_pOwnerDb.DataPageDataAreaSize - offsetInStartDataPage;
// Get next continuing data page
currentDataPage = new DataPage(m_pOwnerDb.DataPageDataAreaSize,m_pOwnerDb,currentDataPage.NextDataPagePointer);
currentDataPageIndex++;
offsetInStartDataPage = 0;
}
// This data page has all data we need
else{
currentDataPage.ReadData(columnData,dataOffset,dataLength - (int)dataOffset,offsetInStartDataPage);
dataOffset += dataLength - (int)dataOffset;
offsetInStartDataPage = 0;
}
}
// Convert to column data type
return ConvertFromInternalData(m_pOwnerDb.Columns[columnIndex],columnData);
}
#endregion
#region method UpdateRecord
/// <summary>
/// Updates this record values.
/// </summary>
/// <param name="rowValues">Row new values.</param>
private void UpdateRecord(object[] rowValues)
{
bool unlock = true;
// Table is already locked, don't lock it
if(m_pOwnerDb.TableLocked){
unlock = false;
}
else{
m_pOwnerDb.LockTable(15);
}
// Create new record
byte[] rowData = LDB_Record.CreateRecord(m_pOwnerDb,rowValues);
DataPage[] dataPages = this.DataPages;
// Clear old data ?? do we need that
// for(int i=0;i<dataPages.Length;i++){
// dataPages[i].Data = new byte[1000];
// }
// We haven't enough room to store row, get needed data pages
if((int)Math.Ceiling(rowData.Length / (double)m_pOwnerDb.DataPageDataAreaSize) > dataPages.Length){
int dataPagesNeeded = (int)Math.Ceiling(rowData.Length / (double)m_pOwnerDb.DataPageDataAreaSize) - dataPages.Length;
DataPage[] additionalDataPages = m_pOwnerDb.GetDataPages(dataPages[dataPages.Length - 1].Pointer,dataPagesNeeded);
// Append new data pages to existing data pages chain
dataPages[dataPages.Length - 1].NextDataPagePointer = additionalDataPages[0].Pointer;
DataPage[] newVal = new DataPage[dataPages.Length + additionalDataPages.Length];
Array.Copy(dataPages,0,newVal,0,dataPages.Length);
Array.Copy(additionalDataPages,0,newVal,dataPages.Length,additionalDataPages.Length);
dataPages = newVal;
}
// Store new record
DbFile.StoreDataToDataPages(m_pOwnerDb.DataPageDataAreaSize,rowData,dataPages);
// Update row info
ParseRowInfo();
if(unlock){
m_pOwnerDb.UnlockTable();
}
}
#endregion
#region static method ConvertToInternalData
/// <summary>
/// Converts data to specied column internal store data.
/// </summary>
/// <param name="coulmn">Column where to store data.</param>
/// <param name="val">Data to convert.</param>
/// <returns></returns>
internal static byte[] ConvertToInternalData(LDB_DataColumn coulmn,object val)
{
if(val == null){
throw new Exception("Null values aren't supported !");
}
if(coulmn.DataType == LDB_DataType.Bool){
if(val.GetType() != typeof(bool)){
throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of bool, but value contains '" +val.GetType().ToString() + "' !");
}
return new byte[]{Convert.ToByte((bool)val)};
}
else if(coulmn.DataType == LDB_DataType.DateTime){
if(val.GetType() != typeof(DateTime)){
throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of DateTime, but value contains '" + val.GetType().ToString() + "' !");
}
/* Data structure
1 byte day
1 byte month
4 byte year (int)
1 byte hour
1 byte minute
1 byte second
*/
DateTime d = (DateTime)val;
byte[] dateBytes = new byte[13];
// day
dateBytes[0] = (byte)d.Day;
// month
dateBytes[1] = (byte)d.Month;
// year
Array.Copy(ldb_Utils.IntToByte(d.Year),0,dateBytes,2,4);
// hour
dateBytes[6] = (byte)d.Hour;
// minute
dateBytes[7] = (byte)d.Minute;
// second
dateBytes[8] = (byte)d.Second;
return dateBytes;
}
else if(coulmn.DataType == LDB_DataType.Long){
if(val.GetType() != typeof(long)){
throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of Long, but value contains '" + val.GetType().ToString() + "' !");
}
return ldb_Utils.LongToByte((long)val);
}
else if(coulmn.DataType == LDB_DataType.Int){
if(val.GetType() != typeof(int)){
throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of Int, but value contains '" + val.GetType().ToString() + "' !");
}
return ldb_Utils.IntToByte((int)val);
}
else if(coulmn.DataType == LDB_DataType.String){
if(val.GetType() != typeof(string)){
throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of String, but value contains '" + val.GetType().ToString() + "' !");
}
return System.Text.Encoding.UTF8.GetBytes(val.ToString());
}
else{
throw new Exception("Invalid column data type, never must reach here !");
}
}
#endregion
#region static method ConvertFromInternalData
/// <summary>
/// Converts internal data to .NET data type.
/// </summary>
/// <param name="coulmn">Column what data it is.</param>
/// <param name="val">Internal data value.</param>
/// <returns></returns>
internal static object ConvertFromInternalData(LDB_DataColumn coulmn,byte[] val)
{
if(coulmn.DataType == LDB_DataType.Bool){
return Convert.ToBoolean(val[0]);
}
else if(coulmn.DataType == LDB_DataType.DateTime){
/* Data structure
1 byte day
1 byte month
4 byte year (int)
1 byte hour
1 byte minute
1 byte second
*/
byte[] dateBytes = new byte[13];
// day
int day = val[0];
// month
int month = val[1];
// year
int year = ldb_Utils.ByteToInt(val,2);
// hour
int hour = val[6];
// minute
int minute = val[7];
// second
int second = val[8];
return new DateTime(year,month,day,hour,minute,second);
}
else if(coulmn.DataType == LDB_DataType.Long){
return ldb_Utils.ByteToLong(val,0);
}
else if(coulmn.DataType == LDB_DataType.Int){
return ldb_Utils.ByteToInt(val,0);
}
else if(coulmn.DataType == LDB_DataType.String){
return System.Text.Encoding.UTF8.GetString(val);
}
else{
throw new Exception("Invalid column data type, never must reach here !");
}
}
#endregion
#region Properties Implementation
/// <summary>
/// Gets or set all data column values.
/// </summary>
public object[] Values
{
get{
object[] retVal = new object[m_pOwnerDb.Columns.Count];
for(int i=0;i<m_pOwnerDb.Columns.Count;i++){
retVal[i] = this[i];
}
return retVal;
}
set{
UpdateRecord(value);
}
}
/// <summary>
/// Gets or sets specified data column value.
/// </summary>
public object this[int columnIndex]
{
get{
if(columnIndex < 0){
throw new Exception("The columnIndex can't be negative value !");
}
if(columnIndex > m_pOwnerDb.Columns.Count){
throw new Exception("The columnIndex out of columns count !");
}
return GetColumnData(columnIndex);
}
set{
if(columnIndex < 0){
throw new Exception("The columnIndex can't be negative value !");
}
if(columnIndex > m_pOwnerDb.Columns.Count){
throw new Exception("The columnIndex out of columns count !");
}
// Get current row values
object[] rowValues = this.Values;
// Update specified column value
rowValues[columnIndex] = value;
// Update record
this.UpdateRecord(rowValues);
}
}
/// <summary>
/// Gets or sets specified data column value.
/// </summary>
public object this[string columnName]
{
get{
int index = m_pOwnerDb.Columns.IndexOf(columnName);
if(index == -1){
throw new Exception("Table doesn't contain column '" + columnName + "' !");
}
return this[index];
}
set{
int index = m_pOwnerDb.Columns.IndexOf(columnName);
if(index == -1){
throw new Exception("Table doesn't contain column '" + columnName + "' !");
}
this[index] = value;
}
}
/// <summary>
/// Gets or sets specified data column value.
/// </summary>
public object this[LDB_DataColumn column]
{
get{
int index = m_pOwnerDb.Columns.IndexOf(column);
if(index == -1){
throw new Exception("Table doesn't contain column '" + column.ColumnName + "' !");
}
return this[index];
}
set{
int index = m_pOwnerDb.Columns.IndexOf(column);
if(index == -1){
throw new Exception("Table doesn't contain column '" + column.ColumnName + "' !");
}
this[index] = value;
}
}
/// <summary>
/// Gets data page on what row starts.
/// </summary>
internal DataPage DataPage
{
get{ return m_pDataPage; }
}
/// <summary>
/// Gets data pages held by this row.
/// </summary>
internal DataPage[] DataPages
{
get{
ArrayList dataPages = new ArrayList();
DataPage page = m_pDataPage;
dataPages.Add(page);
while(page.NextDataPagePointer > 0){
page = new DataPage(m_pOwnerDb.DataPageDataAreaSize,m_pOwnerDb,page.NextDataPagePointer);
dataPages.Add(page);
}
DataPage[] retVal = new DataPage[dataPages.Count];
dataPages.CopyTo(retVal);
return retVal;
}
}
#endregion
}
}