private void ParseRowContents(byte[] bytes, int rowOffset) { //normalize the col count bool onlyVarCols; bytes = NormalizeBytes(bytes, rowOffset, out onlyVarCols); //first byte is status //second byte is skipped //third and fourth is the amount of bytes for fixed width cols // (+4 for the 2 here and the 2 above) int posOffset = BitUtil.ToInt16(bytes, 2, true); //number of cols is two bytes at the end of this int colCount = 1; byte[] nullability = new byte[(int)Math.Ceiling(colCount / 8d)]; int fixedLengthColumnOffset = 4; int varLengthColumnCount = -1; int varLengthColumnCountBegin = -1; int varLengthColumnCurrentCount = -1; int varLengthColumnOffset = -1; if (posOffset != 0 && bytes.Length > posOffset) { //colCount colCount = BitUtil.ToInt16(bytes, posOffset, true); if (colCount != _Table.Columns.Count) { throw new Exception("Expected " + _Table.Columns.Count + " columns, but received " + colCount + " columns"); } //nullability is 1 bit per column posOffset += 2; if (bytes.Length > posOffset) { Array.Copy(bytes, posOffset, nullability, 0, nullability.Length); posOffset += nullability.Length; if (bytes.Length > posOffset) { //may not have var length columns (and may show 1-byte 0 or just not be there) varLengthColumnCount = bytes.Length <= posOffset + 1 ? -1 : BitUtil.ToInt16(bytes, posOffset, true); varLengthColumnCountBegin = posOffset + 2; varLengthColumnCurrentCount = 0; varLengthColumnOffset = posOffset + 2 + (varLengthColumnCount * 2); } } } BitArray nullabilityArray = new BitArray(nullability); //load up the columns foreach (ColumnInfo col in _Table.Columns) { if (col.IsFixedWidth && !onlyVarCols && bytes.Length > fixedLengthColumnOffset) { if (nullabilityArray[col.OrdinalPosition - 1]) { _ColumnValues[col] = null; } else { if (!col.DataTypeLength.HasValue) { Console.WriteLine(); } int length = col.DataTypeLength.Value; //sometimes, the last column is trimmed to a certain size (e.g. positive bigint can be uint24) if (fixedLengthColumnOffset + length >= bytes.Length) { length = bytes.Length - fixedLengthColumnOffset; } _ColumnValues[col] = col.FromBytes(bytes, fixedLengthColumnOffset, length); } fixedLengthColumnOffset += col.DataTypeLength.Value; } else if (!col.IsFixedWidth && nullabilityArray[col.OrdinalPosition - 1]) { _ColumnValues[col] = null; varLengthColumnCurrentCount++; } else if (varLengthColumnCount > 0 && bytes.Length > varLengthColumnOffset) { int end = BitUtil.ToInt16(bytes, varLengthColumnCountBegin + (varLengthColumnCurrentCount * 2), true); //sometimes, the last column is trimmed to a certain size if (end >= bytes.Length) { end = bytes.Length; } _ColumnValues[col] = col.FromBytes(bytes, varLengthColumnOffset, end - varLengthColumnOffset); varLengthColumnOffset = end; varLengthColumnCurrentCount++; } } }
internal object FromBytes(byte[] bytes, int offset, int length) { switch (_DataType) { case SqlDbType.BigInt: switch (length) { case 1: return((long)bytes[offset]); case 2: return((long)BitUtil.ToUInt16(bytes, offset, true)); case 3: return((long)BitUtil.ToUInt24(bytes, offset, true)); case 4: return((long)BitUtil.ToUInt32(bytes, offset, true)); case 8: return(BitUtil.ToInt64(bytes, offset, true)); default: throw new Exception("Unrecognized length for bigint: " + length); } case SqlDbType.Binary: byte[] binaryBytes = new byte[DataTypeLength.Value]; Array.Copy(bytes, offset, binaryBytes, 0, length); return(binaryBytes); case SqlDbType.VarBinary: byte[] varBinaryBytes = new byte[length]; Array.Copy(bytes, offset, varBinaryBytes, 0, length); return(varBinaryBytes); case SqlDbType.Bit: return((bytes[offset] & 1) != 0); case SqlDbType.Char: byte[] charBytes = new byte[DataTypeLength.Value]; Array.Copy(bytes, offset, charBytes, 0, length); //go ahead and set each extra 32's (spaces) if length is shorted for (int i = length; i < DataTypeLength.Value; i++) { charBytes[i] = 32; } return(Encoding.UTF8.GetString(charBytes)); case SqlDbType.VarChar: byte[] varCharBytes = new byte[length]; Array.Copy(bytes, offset, varCharBytes, 0, length); return(Encoding.UTF8.GetString(varCharBytes)); case SqlDbType.Date: return(new DateTime().AddDays(BitUtil.ToInt32(bytes, offset + 4, true))); case SqlDbType.DateTime: if (length < 8) { byte[] dateBytes = new byte[8]; for (int i = 0; i < 8; i++) { if (i < length) { dateBytes[i] = bytes[offset + i]; } else { dateBytes[i] = 0; } } return(new DateTime(1900, 1, 1).AddDays(BitUtil.ToInt32(dateBytes, 4, true)). AddMilliseconds(THREE_AND_ONE_THIRD * BitUtil.ToInt32(dateBytes, 0, true))); } else { return(new DateTime(1900, 1, 1).AddDays(BitUtil.ToInt32(bytes, offset + 4, true)). AddMilliseconds(THREE_AND_ONE_THIRD * BitUtil.ToInt32(bytes, offset, true))); } case SqlDbType.DateTime2: return(new DateTime().AddDays(BitUtil.ToInt32(bytes, offset + 8, true)). AddMilliseconds(1000d / Math.Pow(10, Scale.Value) * BitUtil.ToInt64(bytes, offset, true))); case SqlDbType.DateTimeOffset: return("DateTimeOffset not supported"); case SqlDbType.Decimal: //when data type length is less than the data type length, we must pad with zeroes if (length < DataTypeLength.Value) { byte[] decimalBytes = new byte[offset + DataTypeLength.Value]; Array.Copy(bytes, offset, decimalBytes, offset, length); bytes = decimalBytes; length = DataTypeLength.Value; } int[] bits = new int[4]; bits[0] = BitUtil.ToInt32(bytes, offset + 1, true); bits[1] = length >= 9 ? BitUtil.ToInt32(bytes, offset + 5, true) : 0; bits[2] = length >= 13 ? BitUtil.ToInt32(bytes, offset + 9, true) : 0; bits[3] = length >= 17 ? BitUtil.ToInt32(bytes, offset + 13, true) : 0; //sometimes this is bigger than C#'s max precision/scale of 28 so just return SqlDecimal if (_Precision > 28 || _Scale > 28) { return(new SqlDecimal(_Precision.Value, (byte)_Scale, bytes[offset] == 1, bits)); } else { return(new SqlDecimal(_Precision.Value, (byte)_Scale, bytes[offset] == 1, bits).Value); } case SqlDbType.Float: switch (length) { case 7: return(BitUtil.ToDouble(new byte[] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3], bytes[offset + 4], bytes[offset + 5], bytes[offset + 6], 64 }, 0, true)); case 8: return(BitUtil.ToDouble(bytes, offset, true)); default: throw new Exception("Unrecognized float length: " + length); } case SqlDbType.Image: return("Image not supported"); case SqlDbType.Int: switch (length) { case 1: return((int)bytes[offset]); case 2: return((int)BitUtil.ToUInt16(bytes, offset, true)); case 3: return((int)BitUtil.ToUInt24(bytes, offset, true)); case 4: return(BitUtil.ToInt32(bytes, offset, true)); default: throw new Exception("Unrecognized int length : " + length); } case SqlDbType.Money: if (length < 8) { byte[] moneyBytes = new byte[8]; for (int i = 0; i < 8; i++) { if (i < length) { moneyBytes[i] = bytes[offset + i]; } else { moneyBytes[i] = (byte)(i == 2 ? 1 : 0); } } return(BitUtil.ToInt64(moneyBytes, 0, true) / 10000d); } return(BitUtil.ToInt64(bytes, offset, true) / 10000d); case SqlDbType.NChar: byte[] ncharBytes = new byte[DataTypeLength.Value]; Array.Copy(bytes, offset, ncharBytes, 0, length); //go ahead and set each extra 32's (spaces) if length is shorted for (int i = length; i < DataTypeLength.Value; i++) { ncharBytes[i] = (byte)(i % 2 == 1 ? 0 : 32); } return(Encoding.Unicode.GetString(ncharBytes)); case SqlDbType.NVarChar: //if it's an odd number, we have to add a zero to the end int nvarCharLength = length + (length % 2); byte[] nvarCharBytes = new byte[nvarCharLength]; Array.Copy(bytes, offset, nvarCharBytes, 0, length); return(Encoding.Unicode.GetString(nvarCharBytes)); case SqlDbType.NText: return("NText not supported"); case SqlDbType.Real: return(BitUtil.ToSingle(bytes, offset, true)); case SqlDbType.SmallDateTime: return(new DateTime(1900, 1, 1).AddDays(BitUtil.ToUInt16(bytes, offset + 2, true)). AddMinutes(BitUtil.ToUInt16(bytes, offset, true))); case SqlDbType.SmallInt: return(length == 1 ? (short)bytes[offset] : BitUtil.ToInt16(bytes, offset, true)); case SqlDbType.SmallMoney: switch (length) { case 3: return(BitUtil.ToInt32(new byte[] { bytes[offset], bytes[offset + 1], bytes[offset + 2], 0 }, 0, true) / 10000d); case 4: return(BitUtil.ToInt32(bytes, offset, true) / 10000d); default: throw new Exception("Unrecognized length for small money: " + length); } case SqlDbType.Structured: return("Structured not supported"); case SqlDbType.Text: return("Text not supported"); case SqlDbType.Time: return(TimeSpan.FromMilliseconds(1000d / Math.Pow(10, Scale.Value) * BitUtil.ToInt64(bytes, offset, true))); case SqlDbType.Timestamp: return("Timestamp not supported"); case SqlDbType.TinyInt: return(bytes[offset]); case SqlDbType.Udt: return("UDT's not supported"); case SqlDbType.UniqueIdentifier: byte[] guid = new byte[16]; Array.Copy(bytes, offset, guid, 0, 16); return(new Guid(guid)); case SqlDbType.Variant: return("Variant not supported"); case SqlDbType.Xml: return("Xml not supported"); default: throw new NotImplementedException(_DataType + " not implemented"); } }