383 lines
16 KiB
C#
383 lines
16 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="ColumnValue.cs" company="Microsoft Corporation">
|
|
// Copyright (c) Microsoft Corporation.
|
|
// </copyright>
|
|
//-----------------------------------------------------------------------
|
|
|
|
namespace Microsoft.Isam.Esent.Interop
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
|
|
/// <summary>
|
|
/// Base class for objects that represent a column value to be set.
|
|
/// </summary>
|
|
public abstract partial class ColumnValue
|
|
{
|
|
/// <summary>
|
|
/// Internal grbit.
|
|
/// </summary>
|
|
private RetrieveColumnGrbit grbit;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the ColumnValue class.
|
|
/// </summary>
|
|
protected ColumnValue()
|
|
{
|
|
this.ItagSequence = 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the columnid to be set or retrieved.
|
|
/// </summary>
|
|
public JET_COLUMNID Columnid { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the last set or retrieved value of the column. The
|
|
/// value is returned as a generic object.
|
|
/// </summary>
|
|
public abstract object ValueAsObject { get; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets column update options.
|
|
/// </summary>
|
|
public SetColumnGrbit SetGrbit { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets column retrieval options.
|
|
/// </summary>
|
|
public RetrieveColumnGrbit RetrieveGrbit
|
|
{
|
|
get
|
|
{
|
|
return this.grbit;
|
|
}
|
|
|
|
set
|
|
{
|
|
this.ValidateRetrieveGrbit(value);
|
|
this.grbit = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the column itag sequence.
|
|
/// </summary>
|
|
public int ItagSequence { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the warning generated by retrieving or setting this column.
|
|
/// </summary>
|
|
public JET_wrn Error { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Gets the byte length of a column value, which is zero if column is null, otherwise
|
|
/// it matches the Size for fixed-size columns and represent the actual value byte
|
|
/// length for variable sized columns (i.e. binary and string). For strings the length
|
|
/// is determined in assumption two bytes per character.
|
|
/// </summary>
|
|
public abstract int Length { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the size of the value in the column. This returns 0 for
|
|
/// variable sized columns (i.e. binary and string).
|
|
/// </summary>
|
|
protected abstract int Size { get; }
|
|
|
|
/// <summary>
|
|
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="ColumnValue"/>.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A <see cref="T:System.String"/> that represents the current <see cref="ColumnValue"/>.
|
|
/// </returns>
|
|
public abstract override string ToString();
|
|
|
|
/// <summary>
|
|
/// Recursive RetrieveColumns method for data pinning. This should pin a buffer and
|
|
/// call the inherited RetrieveColumns method.
|
|
/// </summary>
|
|
/// <param name="sesid">The session to use.</param>
|
|
/// <param name="tableid">
|
|
/// The table to retrieve the columns from.
|
|
/// </param>
|
|
/// <param name="columnValues">
|
|
/// Column values to retrieve.
|
|
/// </param>
|
|
internal static void RetrieveColumns(JET_SESID sesid, JET_TABLEID tableid, ColumnValue[] columnValues)
|
|
{
|
|
const int MaxColumnValues = 1024;
|
|
if (columnValues.Length > MaxColumnValues)
|
|
{
|
|
throw new ArgumentOutOfRangeException("columnValues", columnValues.Length, "Too many column values");
|
|
}
|
|
|
|
unsafe
|
|
{
|
|
byte[] buffer = null;
|
|
NATIVE_RETRIEVECOLUMN* nativeRetrievecolumns = stackalloc NATIVE_RETRIEVECOLUMN[columnValues.Length];
|
|
|
|
try
|
|
{
|
|
buffer = Caches.ColumnCache.Allocate();
|
|
Debug.Assert(MaxColumnValues * 16 < buffer.Length, "Maximum size of fixed columns could exceed buffer size");
|
|
|
|
fixed (byte* pinnedBuffer = buffer)
|
|
{
|
|
byte* currentBuffer = pinnedBuffer;
|
|
int numVariableLengthColumns = columnValues.Length;
|
|
|
|
// First the fixed-size columns
|
|
for (int i = 0; i < columnValues.Length; ++i)
|
|
{
|
|
if (0 != columnValues[i].Size)
|
|
{
|
|
columnValues[i].MakeNativeRetrieveColumn(ref nativeRetrievecolumns[i]);
|
|
nativeRetrievecolumns[i].pvData = new IntPtr(currentBuffer);
|
|
nativeRetrievecolumns[i].cbData = checked((uint)columnValues[i].Size);
|
|
|
|
currentBuffer += nativeRetrievecolumns[i].cbData;
|
|
Debug.Assert(currentBuffer <= pinnedBuffer + buffer.Length, "Moved past end of pinned buffer");
|
|
|
|
numVariableLengthColumns--;
|
|
}
|
|
}
|
|
|
|
// Now the variable-length columns
|
|
if (numVariableLengthColumns > 0)
|
|
{
|
|
int bufferUsed = checked((int)(currentBuffer - pinnedBuffer));
|
|
int bufferRemaining = checked(buffer.Length - bufferUsed);
|
|
int bufferPerColumn = bufferRemaining / numVariableLengthColumns;
|
|
Debug.Assert(bufferPerColumn > 0, "Not enough buffer left to retrieve variable length columns");
|
|
|
|
// Now the variable-size columns
|
|
for (int i = 0; i < columnValues.Length; ++i)
|
|
{
|
|
if (0 == columnValues[i].Size)
|
|
{
|
|
columnValues[i].MakeNativeRetrieveColumn(ref nativeRetrievecolumns[i]);
|
|
nativeRetrievecolumns[i].pvData = new IntPtr(currentBuffer);
|
|
nativeRetrievecolumns[i].cbData = checked((uint)bufferPerColumn);
|
|
currentBuffer += nativeRetrievecolumns[i].cbData;
|
|
Debug.Assert(currentBuffer <= pinnedBuffer + buffer.Length, "Moved past end of pinned buffer");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve the columns
|
|
Api.Check(Api.Impl.JetRetrieveColumns(sesid, tableid, nativeRetrievecolumns, columnValues.Length));
|
|
|
|
// Propagate the warnings and output.
|
|
for (int i = 0; i < columnValues.Length; ++i)
|
|
{
|
|
columnValues[i].Error = (JET_wrn)nativeRetrievecolumns[i].err;
|
|
columnValues[i].ItagSequence = (int)nativeRetrievecolumns[i].itagSequence;
|
|
}
|
|
|
|
// Now parse out the columns that were retrieved successfully
|
|
for (int i = 0; i < columnValues.Length; ++i)
|
|
{
|
|
if (nativeRetrievecolumns[i].err != (int)JET_wrn.BufferTruncated)
|
|
{
|
|
byte* columnBuffer = (byte*)nativeRetrievecolumns[i].pvData;
|
|
int startIndex = checked((int)(columnBuffer - pinnedBuffer));
|
|
columnValues[i].GetValueFromBytes(
|
|
buffer,
|
|
startIndex,
|
|
checked((int)nativeRetrievecolumns[i].cbActual),
|
|
nativeRetrievecolumns[i].err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally retrieve the buffers where the columns weren't large enough.
|
|
RetrieveTruncatedBuffers(sesid, tableid, columnValues, nativeRetrievecolumns);
|
|
}
|
|
finally
|
|
{
|
|
if (buffer != null)
|
|
{
|
|
Caches.ColumnCache.Free(ref buffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recursive SetColumns method for data pinning. This should populate the buffer and
|
|
/// call the inherited SetColumns method.
|
|
/// </summary>
|
|
/// <param name="sesid">The session to use.</param>
|
|
/// <param name="tableid">
|
|
/// The table to set the columns in. An update should be prepared.
|
|
/// </param>
|
|
/// <param name="columnValues">
|
|
/// Column values to set.
|
|
/// </param>
|
|
/// <param name="nativeColumns">
|
|
/// Structures to put the pinned data in.
|
|
/// </param>
|
|
/// <param name="i">Offset of this object in the array.</param>
|
|
/// <returns>An error code.</returns>
|
|
internal abstract unsafe int SetColumns(JET_SESID sesid, JET_TABLEID tableid, ColumnValue[] columnValues, NATIVE_SETCOLUMN* nativeColumns, int i);
|
|
|
|
/// <summary>
|
|
/// Recursive SetColumns function used to pin data.
|
|
/// </summary>
|
|
/// <param name="sesid">The session to use.</param>
|
|
/// <param name="tableid">
|
|
/// The table to set the columns in. An update should be prepared.
|
|
/// </param>
|
|
/// <param name="columnValues">
|
|
/// Column values to set.
|
|
/// </param>
|
|
/// <param name="nativeColumns">
|
|
/// Structures to put the pinned data in.
|
|
/// </param>
|
|
/// <param name="i">Offset of this object in the array.</param>
|
|
/// <param name="buffer">The buffer for this object.</param>
|
|
/// <param name="bufferSize">Size of the buffer for ths object.</param>
|
|
/// <param name="hasValue">True if this object is non null.</param>
|
|
/// <returns>An error code.</returns>
|
|
/// <remarks>
|
|
/// This is marked as internal because it uses the NATIVE_SETCOLUMN type
|
|
/// which is also marked as internal. It should be treated as a protected
|
|
/// method though.
|
|
/// </remarks>
|
|
internal unsafe int SetColumns(
|
|
JET_SESID sesid,
|
|
JET_TABLEID tableid,
|
|
ColumnValue[] columnValues,
|
|
NATIVE_SETCOLUMN* nativeColumns,
|
|
int i,
|
|
void* buffer,
|
|
int bufferSize,
|
|
bool hasValue)
|
|
{
|
|
Debug.Assert(this == columnValues[i], "SetColumns should be called on the current object");
|
|
this.MakeNativeSetColumn(ref nativeColumns[i]);
|
|
|
|
if (hasValue)
|
|
{
|
|
nativeColumns[i].cbData = checked((uint)bufferSize);
|
|
nativeColumns[i].pvData = new IntPtr(buffer);
|
|
if (0 == bufferSize)
|
|
{
|
|
nativeColumns[i].grbit |= (uint)SetColumnGrbit.ZeroLength;
|
|
}
|
|
}
|
|
|
|
int err = i == columnValues.Length - 1
|
|
? Api.Impl.JetSetColumns(sesid, tableid, nativeColumns, columnValues.Length)
|
|
: columnValues[i + 1].SetColumns(sesid, tableid, columnValues, nativeColumns, i + 1);
|
|
|
|
this.Error = (JET_wrn)nativeColumns[i].err;
|
|
return err;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given data retrieved from ESENT, decode the data and set the value in the ColumnValue object.
|
|
/// </summary>
|
|
/// <param name="value">An array of bytes.</param>
|
|
/// <param name="startIndex">The starting position within the bytes.</param>
|
|
/// <param name="count">The number of bytes to decode.</param>
|
|
/// <param name="err">The error returned from ESENT.</param>
|
|
protected abstract void GetValueFromBytes(byte[] value, int startIndex, int count, int err);
|
|
|
|
/// <summary>
|
|
/// Validation for the requested retrieve options for the column.
|
|
/// </summary>
|
|
/// <param name="grbit">The retrieve options to validate.</param>
|
|
protected virtual void ValidateRetrieveGrbit(RetrieveColumnGrbit grbit)
|
|
{
|
|
// We cannot support this request when there is no way to indicate that a column reference is returned.
|
|
if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord
|
|
{
|
|
throw new EsentInvalidGrbitException();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve the value for columns whose buffers were truncated.
|
|
/// </summary>
|
|
/// <param name="sesid">The session to use.</param>
|
|
/// <param name="tableid">The table to use.</param>
|
|
/// <param name="columnValues">The column values.</param>
|
|
/// <param name="nativeRetrievecolumns">
|
|
/// The native retrieve columns that match the column values.
|
|
/// </param>
|
|
private static unsafe void RetrieveTruncatedBuffers(JET_SESID sesid, JET_TABLEID tableid, IList<ColumnValue> columnValues, NATIVE_RETRIEVECOLUMN* nativeRetrievecolumns)
|
|
{
|
|
for (int i = 0; i < columnValues.Count; ++i)
|
|
{
|
|
if (nativeRetrievecolumns[i].err == (int)JET_wrn.BufferTruncated)
|
|
{
|
|
var buffer = new byte[nativeRetrievecolumns[i].cbActual];
|
|
int actualSize;
|
|
int err;
|
|
var retinfo = new JET_RETINFO { itagSequence = columnValues[i].ItagSequence };
|
|
|
|
// Pin the buffer and retrieve the data
|
|
fixed (byte* pinnedBuffer = buffer)
|
|
{
|
|
err = Api.Impl.JetRetrieveColumn(
|
|
sesid,
|
|
tableid,
|
|
columnValues[i].Columnid,
|
|
new IntPtr(pinnedBuffer),
|
|
buffer.Length,
|
|
out actualSize,
|
|
columnValues[i].RetrieveGrbit,
|
|
retinfo);
|
|
}
|
|
|
|
if (JET_wrn.BufferTruncated == (JET_wrn)err)
|
|
{
|
|
string error = string.Format(
|
|
CultureInfo.CurrentCulture,
|
|
"Column size changed from {0} to {1}. The record was probably updated by another thread.",
|
|
buffer.Length,
|
|
actualSize);
|
|
Trace.TraceError(error);
|
|
throw new InvalidOperationException(error);
|
|
}
|
|
|
|
// Throw errors, but put warnings in the structure
|
|
Api.Check(err);
|
|
columnValues[i].Error = (JET_wrn)err;
|
|
|
|
// For BytesColumnValue this will copy the data to a new array.
|
|
// If this situation becomes common we should simply use the array.
|
|
columnValues[i].GetValueFromBytes(buffer, 0, actualSize, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a native SetColumn from this object.
|
|
/// </summary>
|
|
/// <param name="setcolumn">The native setcolumn structure to fill in.</param>
|
|
private void MakeNativeSetColumn(ref NATIVE_SETCOLUMN setcolumn)
|
|
{
|
|
setcolumn.columnid = this.Columnid.Value;
|
|
setcolumn.grbit = (uint)this.SetGrbit;
|
|
setcolumn.itagSequence = checked((uint)this.ItagSequence);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a native RetrieveColumn from this object.
|
|
/// </summary>
|
|
/// <param name="retrievecolumn">
|
|
/// The retrieve column structure to fill in.
|
|
/// </param>
|
|
private void MakeNativeRetrieveColumn(ref NATIVE_RETRIEVECOLUMN retrievecolumn)
|
|
{
|
|
retrievecolumn.columnid = this.Columnid.Value;
|
|
retrievecolumn.grbit = (uint)this.RetrieveGrbit;
|
|
retrievecolumn.itagSequence = checked((uint)this.ItagSequence);
|
|
}
|
|
}
|
|
}
|