Skip to content

Commit

Permalink
Add support for a custom ColumnConverter (#55)
Browse files Browse the repository at this point in the history
* Add support for a custom ColumnConverter

* Fix PersistentDictionary constructors

* Fix dataLength for SetColumn

* Add checks for dataSize
  • Loading branch information
Denis12484 authored Apr 3, 2023
1 parent 2d8266f commit d490330
Show file tree
Hide file tree
Showing 8 changed files with 761 additions and 44 deletions.
40 changes: 11 additions & 29 deletions EsentCollections/ColumnConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace Microsoft.Isam.Esent.Collections.Generic
/// database.
/// </summary>
/// <typeparam name="TColumn">The type of the column.</typeparam>
internal class ColumnConverter<TColumn>
internal class ColumnConverter<TColumn> : IColumnConverter<TColumn>
{
/// <summary>
/// A mapping of types to RetrieveColumn function names.
Expand Down Expand Up @@ -97,12 +97,12 @@ internal class ColumnConverter<TColumn>
/// <summary>
/// The SetColumn delegate for this object.
/// </summary>
private readonly SetColumnDelegate columnSetter;
private readonly SetColumnDelegate<TColumn> columnSetter;

/// <summary>
/// The RetrieveColumn delegate for this object.
/// </summary>
private readonly RetrieveColumnDelegate columnRetriever;
private readonly RetrieveColumnDelegate<TColumn> columnRetriever;

/// <summary>
/// The column type for this object.
Expand Down Expand Up @@ -141,25 +141,7 @@ public ColumnConverter()
RuntimeHelpers.PrepareDelegate(this.columnSetter);
RuntimeHelpers.PrepareDelegate(this.columnRetriever);
}

/// <summary>
/// Represents a SetColumn operation.
/// </summary>
/// <param name="sesid">The session to use.</param>
/// <param name="tableid">The cursor to set the value in. An update should be prepared.</param>
/// <param name="columnid">The column to set.</param>
/// <param name="value">The value to set.</param>
public delegate void SetColumnDelegate(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, TColumn value);

/// <summary>
/// Represents a RetrieveColumn operation.
/// </summary>
/// <param name="sesid">The session to use.</param>
/// <param name="tableid">The cursor to retrieve the value from.</param>
/// <param name="columnid">The column to retrieve.</param>
/// <returns>The retrieved value.</returns>
public delegate TColumn RetrieveColumnDelegate(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid);


/// <summary>
/// Gets the type of database column the value should be stored in.
/// </summary>
Expand All @@ -175,7 +157,7 @@ public JET_coltyp Coltyp
/// Gets a delegate that can be used to set the Key column with an object of
/// type <see cref="Type"/>.
/// </summary>
public SetColumnDelegate ColumnSetter
public SetColumnDelegate<TColumn> ColumnSetter
{
get
{
Expand All @@ -187,7 +169,7 @@ public SetColumnDelegate ColumnSetter
/// Gets a delegate that can be used to retrieve the Key column, returning
/// type <see cref="Type"/>.
/// </summary>
public RetrieveColumnDelegate ColumnRetriever
public RetrieveColumnDelegate<TColumn> ColumnRetriever
{
get
{
Expand Down Expand Up @@ -787,7 +769,7 @@ private static bool SetColumnIfNull<T>(JET_SESID sesid, JET_TABLEID tableid, JET
/// Get the retrieve column delegate for the type.
/// </summary>
/// <returns>The retrieve column delegate for the type.</returns>
private static RetrieveColumnDelegate CreateRetrieveColumnDelegate()
private static RetrieveColumnDelegate<TColumn> CreateRetrieveColumnDelegate()
{
// Look for a method called "RetrieveColumnAs{Type}", which will return a
// nullable version of the type (except for strings, which are are ready
Expand All @@ -813,7 +795,7 @@ private static RetrieveColumnDelegate CreateRetrieveColumnDelegate()
null);

// Return the string/nullable type.
return (RetrieveColumnDelegate)Delegate.CreateDelegate(typeof(RetrieveColumnDelegate), retrieveColumnMethod);
return (RetrieveColumnDelegate<TColumn>)Delegate.CreateDelegate(typeof(RetrieveColumnDelegate<TColumn>), retrieveColumnMethod);
}
else
{
Expand All @@ -829,15 +811,15 @@ private static RetrieveColumnDelegate CreateRetrieveColumnDelegate()
retrieveColumnArguments,
null);

return (RetrieveColumnDelegate)Delegate.CreateDelegate(typeof(RetrieveColumnDelegate), retrieveNonNullableColumnMethod);
return (RetrieveColumnDelegate<TColumn>)Delegate.CreateDelegate(typeof(RetrieveColumnDelegate<TColumn>), retrieveNonNullableColumnMethod);
}
}

/// <summary>
/// Create the set column delegate.
/// </summary>
/// <returns>The set column delegate.</returns>
private static SetColumnDelegate CreateSetColumnDelegate()
private static SetColumnDelegate<TColumn> CreateSetColumnDelegate()
{
// Look for a method called "SetColumn", which takes a TColumn.
// First look for a private method in this class that takes the
Expand All @@ -855,7 +837,7 @@ private static SetColumnDelegate CreateSetColumnDelegate()
null,
setColumnArguments,
null);
return (SetColumnDelegate)Delegate.CreateDelegate(typeof(SetColumnDelegate), setColumnMethod);
return (SetColumnDelegate<TColumn>)Delegate.CreateDelegate(typeof(SetColumnDelegate<TColumn>), setColumnMethod);
}
}
}
52 changes: 52 additions & 0 deletions EsentCollections/IColumnConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IColumnConverter.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation.
// </copyright>
// <summary>
// Contains methods to set and get data from the ESENT database.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Isam.Esent.Collections.Generic
{
using Microsoft.Isam.Esent.Interop;

/// <summary>
/// Represents a SetColumn operation.
/// </summary>
/// <param name="sesid">The session to use.</param>
/// <param name="tableid">The cursor to set the value in. An update should be prepared.</param>
/// <param name="columnid">The column to set.</param>
/// <param name="value">The value to set.</param>
public delegate void SetColumnDelegate<TColumn>(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, TColumn value);

/// <summary>
/// Represents a RetrieveColumn operation.
/// </summary>
/// <param name="sesid">The session to use.</param>
/// <param name="tableid">The cursor to retrieve the value from.</param>
/// <param name="columnid">The column to retrieve.</param>
/// <returns>The retrieved value.</returns>
public delegate TColumn RetrieveColumnDelegate<TColumn>(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid);

/// <summary>
/// Contains methods to set and get data from the ESENT database.
/// </summary>
public interface IColumnConverter<TColumn>
{
/// <summary>
/// Gets a delegate that can be used to set the Key column with an object of
/// </summary>
SetColumnDelegate<TColumn> ColumnSetter { get; }

/// <summary>
/// Gets a delegate that can be used to retrieve the Key column, returning
/// </summary>
RetrieveColumnDelegate<TColumn> ColumnRetriever { get; }

/// <summary>
/// Gets the type of database column the value should be stored in.
/// </summary>
JET_coltyp Coltyp { get; }
}
}
40 changes: 32 additions & 8 deletions EsentCollections/PersistentDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public sealed partial class PersistentDictionary<TKey, TValue> : IDictionary<TKe
/// <param name="directory">
/// The directory in which to create the database.
/// </param>
public PersistentDictionary(string directory) : this(directory, null, null)
public PersistentDictionary(string directory) : this(directory, null, null, null)
{
if (null == directory)
{
Expand All @@ -127,7 +127,7 @@ public PersistentDictionary(string directory) : this(directory, null, null)
/// Initializes a new instance of the PersistentDictionary class.
/// </summary>
/// <param name="customConfig">The custom config to use for creating the PersistentDictionary.</param>
public PersistentDictionary(IConfigSet customConfig) : this(null, customConfig, null)
public PersistentDictionary(IConfigSet customConfig) : this(null, customConfig, null, null)
{
if (null == customConfig)
{
Expand All @@ -140,13 +140,32 @@ public PersistentDictionary(IConfigSet customConfig) : this(null, customConfig,
/// </summary>
/// <param name="directory">The directory in which to create the database.</param>
/// <param name="customConfig">The custom config to use for creating the PersistentDictionary.</param>
public PersistentDictionary(string directory, IConfigSet customConfig) : this(directory, customConfig, null)
public PersistentDictionary(string directory, IConfigSet customConfig) : this(directory, customConfig, null, null)
{
if (directory == null && customConfig == null)
{
throw new ArgumentException("Must specify a valid directory or customConfig");
}
}

/// <summary>
/// Initializes a new instance of the PersistentDictionary class.
/// </summary>
/// <param name="directory">The directory in which to create the database.</param>
/// <param name="customConfig">The custom config to use for creating the PersistentDictionary.</param>
/// <param name="valueColumnConverter">The custom converter for database value column.</param>
public PersistentDictionary(string directory, IConfigSet customConfig, IColumnConverter<TValue> valueColumnConverter) : this(directory, customConfig, null, valueColumnConverter)
{
if (directory == null && customConfig == null)
{
throw new ArgumentException("Must specify a valid directory or customConfig");
}

if (valueColumnConverter == null)
{
throw new ArgumentNullException("valueColumnConverter");
}
}

/// <summary>
/// Initializes a new instance of the PersistentDictionary class.
Expand All @@ -157,7 +176,7 @@ public PersistentDictionary(string directory, IConfigSet customConfig) : this(di
/// <param name="directory">
/// The directory in which to create the database.
/// </param>
public PersistentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> dictionary, string directory) : this(directory, null, dictionary)
public PersistentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> dictionary, string directory) : this(directory, null, dictionary, null)
{
if (null == directory)
{
Expand All @@ -176,7 +195,7 @@ public PersistentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> dictionary,
/// </summary>
/// <param name="dictionary">The IDictionary whose contents are copied to the new dictionary.</param>
/// <param name="customConfig">The custom config to use for creating the PersistentDictionary.</param>
public PersistentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> dictionary, IConfigSet customConfig) : this(null, customConfig, dictionary)
public PersistentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> dictionary, IConfigSet customConfig) : this(null, customConfig, dictionary, null)
{
if (null == customConfig)
{
Expand All @@ -200,7 +219,7 @@ public PersistentDictionary(
IEnumerable<KeyValuePair<TKey, TValue>> dictionary,
string directory,
IConfigSet customConfig)
: this(directory, customConfig, dictionary)
: this(directory, customConfig, dictionary, null)
{
if (directory == null && customConfig == null)
{
Expand All @@ -220,19 +239,24 @@ public PersistentDictionary(
/// <param name="directory">The directory to create the database in.</param>
/// <param name="customConfig">The custom config to use for creating the PersistentDictionary.</param>
/// <param name="dictionary">The IDictionary whose contents are copied to the new dictionary.</param>
/// <param name="valueColumnConverter">The custom converter for database value column.</param>
/// <remarks>The constructor can either intialize PersistentDictionary from a directory string, or a full custom config set. But not both.</remarks>
private PersistentDictionary(
string directory,
IConfigSet customConfig,
IEnumerable<KeyValuePair<TKey, TValue>> dictionary)
IEnumerable<KeyValuePair<TKey, TValue>> dictionary,
IColumnConverter<TValue> valueColumnConverter)
{
Contract.Requires(directory != null || customConfig != null); // At least 1 of the two arguments should be set
if (directory == null && customConfig == null)
{
return; // The calling constructor will throw an error
}

this.converters = new PersistentDictionaryConverters<TKey, TValue>();
this.converters = valueColumnConverter == null ?
new PersistentDictionaryConverters<TKey, TValue>() :
new PersistentDictionaryConverters<TKey, TValue>(valueColumnConverter);

this.schema = new PersistentDictionaryConfig();
var defaultConfig = PersistentDictionaryDefaultConfig.GetDefaultDatabaseConfig();
var databaseConfig = new DatabaseConfig();
Expand Down
25 changes: 20 additions & 5 deletions EsentCollections/PersistentDictionaryConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;

namespace Microsoft.Isam.Esent.Collections.Generic
{
using Microsoft.Isam.Esent.Interop;
Expand All @@ -29,7 +31,20 @@ internal class PersistentDictionaryConverters<TKey, TValue>
/// <summary>
/// Column converter for the value column.
/// </summary>
private readonly ColumnConverter<TValue> valueColumnConverter = new ColumnConverter<TValue>();
private readonly IColumnConverter<TValue> valueColumnConverter;

public PersistentDictionaryConverters() : this(new ColumnConverter<TValue>())
{
}

public PersistentDictionaryConverters(IColumnConverter<TValue> valueColumnConverter)
{
if (valueColumnConverter == null)
{
throw new ArgumentNullException("valueColumnConverter");
}
this.valueColumnConverter = valueColumnConverter;
}

/// <summary>
/// Gets a delegate that can be used to call JetMakeKey with an object of
Expand All @@ -47,7 +62,7 @@ public KeyColumnConverter<TKey>.MakeKeyDelegate MakeKey
/// Gets a delegate that can be used to set the Key column with an object of
/// type <typeparamref name="TKey"/>.
/// </summary>
public ColumnConverter<TKey>.SetColumnDelegate SetKeyColumn
public SetColumnDelegate<TKey> SetKeyColumn
{
get
{
Expand All @@ -59,7 +74,7 @@ public ColumnConverter<TKey>.SetColumnDelegate SetKeyColumn
/// Gets a delegate that can be used to set the Value column with an object of
/// type <typeparamref name="TValue"/>.
/// </summary>
public ColumnConverter<TValue>.SetColumnDelegate SetValueColumn
public SetColumnDelegate<TValue> SetValueColumn
{
get
{
Expand All @@ -71,7 +86,7 @@ public ColumnConverter<TValue>.SetColumnDelegate SetValueColumn
/// Gets a delegate that can be used to retrieve the Key column, returning
/// an object of type <typeparamref name="TKey"/>.
/// </summary>
public ColumnConverter<TKey>.RetrieveColumnDelegate RetrieveKeyColumn
public RetrieveColumnDelegate<TKey> RetrieveKeyColumn
{
get
{
Expand All @@ -83,7 +98,7 @@ public ColumnConverter<TKey>.RetrieveColumnDelegate RetrieveKeyColumn
/// Gets a delegate that can be used to retrieve the Value column, returning
/// an object of type <typeparamref name="TValue"/>.
/// </summary>
public ColumnConverter<TValue>.RetrieveColumnDelegate RetrieveValueColumn
public RetrieveColumnDelegate<TValue> RetrieveValueColumn
{
get
{
Expand Down
Loading

0 comments on commit d490330

Please sign in to comment.