From: <fab...@us...> - 2008-08-15 21:41:40
|
Revision: 3706 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=3706&view=rev Author: fabiomaulo Date: 2008-08-15 21:41:48 +0000 (Fri, 15 Aug 2008) Log Message: ----------- Start port of enhanced Id generators. Possible breaking change: - IPersistentIdentifierGenerator Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Cfg/Configuration.cs trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs trunk/nhibernate/src/NHibernate/Dialect/MySQLDialect.cs trunk/nhibernate/src/NHibernate/Dialect/Oracle9Dialect.cs trunk/nhibernate/src/NHibernate/Dialect/PostgreSQL82Dialect.cs trunk/nhibernate/src/NHibernate/Dialect/PostgreSQLDialect.cs trunk/nhibernate/src/NHibernate/Dialect/SQLiteDialect.cs trunk/nhibernate/src/NHibernate/Id/Assigned.cs trunk/nhibernate/src/NHibernate/Id/ForeignGenerator.cs trunk/nhibernate/src/NHibernate/Id/IConfigurable.cs trunk/nhibernate/src/NHibernate/Id/IPersistentIdentifierGenerator.cs trunk/nhibernate/src/NHibernate/Id/IncrementGenerator.cs trunk/nhibernate/src/NHibernate/Id/SequenceGenerator.cs trunk/nhibernate/src/NHibernate/Id/TableGenerator.cs trunk/nhibernate/src/NHibernate/Mapping/Table.cs trunk/nhibernate/src/NHibernate/NHibernate-2.0.csproj trunk/nhibernate/src/NHibernate.Test/NHibernate.Test-2.0.csproj Added Paths: ----------- trunk/nhibernate/src/NHibernate/Engine/Transaction/ trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs trunk/nhibernate/src/NHibernate/Engine/Transaction/Isolater.cs trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs trunk/nhibernate/src/NHibernate/Id/Enhanced/ trunk/nhibernate/src/NHibernate/Id/Enhanced/IAccessCallback.cs trunk/nhibernate/src/NHibernate/Id/Enhanced/IDatabaseStructure.cs trunk/nhibernate/src/NHibernate/Id/Enhanced/IOptimizer.cs trunk/nhibernate/src/NHibernate/Id/Enhanced/OptimizerFactory.cs trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStructure.cs trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs trunk/nhibernate/src/NHibernate/Id/Enhanced/TableStructure.cs trunk/nhibernate/src/NHibernate.Test/IdGen/ trunk/nhibernate/src/NHibernate.Test/IdGen/Enhanced/ trunk/nhibernate/src/NHibernate.Test/IdGen/Enhanced/SequenceStyleConfigUnitFixture.cs Modified: trunk/nhibernate/src/NHibernate/Cfg/Configuration.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Cfg/Configuration.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Cfg/Configuration.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -701,9 +701,14 @@ IEnumerable<IPersistentIdentifierGenerator> pIDg = IterateGenerators(dialect); foreach (IPersistentIdentifierGenerator idGen in pIDg) { - string dropString = idGen.SqlDropString(dialect); - if (dropString != null) - script.Add(dropString); + string[] lines = idGen.SqlDropString(dialect); + if (lines != null) + { + foreach (string line in lines) + { + script.Add(line); + } + } } return script.ToArray(); Modified: trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -393,7 +393,7 @@ /// <summary> /// Completely optional cascading drop clause /// </summary> - protected virtual string CascadeConstraintsString + public virtual string CascadeConstraintsString { get { return String.Empty; } } @@ -669,7 +669,7 @@ /// <summary> /// Does the dialect support the syntax 'drop table if exists NAME' /// </summary> - protected virtual bool SupportsIfExistsBeforeTableName + public virtual bool SupportsIfExistsBeforeTableName { get { return false; } } @@ -677,7 +677,7 @@ /// <summary> /// Does the dialect support the syntax 'drop table NAME if exists' /// </summary> - protected virtual bool SupportsIfExistsAfterTableName + public virtual bool SupportsIfExistsAfterTableName { get { return false; } } Modified: trunk/nhibernate/src/NHibernate/Dialect/MySQLDialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/MySQLDialect.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Dialect/MySQLDialect.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -112,7 +112,7 @@ get { return '`'; } } - protected override bool SupportsIfExistsBeforeTableName + public override bool SupportsIfExistsBeforeTableName { get { return true; } } Modified: trunk/nhibernate/src/NHibernate/Dialect/Oracle9Dialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/Oracle9Dialect.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Dialect/Oracle9Dialect.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -162,7 +162,7 @@ } /// <summary></summary> - protected override string CascadeConstraintsString + public override string CascadeConstraintsString { get { return " cascade constraints"; } } Modified: trunk/nhibernate/src/NHibernate/Dialect/PostgreSQL82Dialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/PostgreSQL82Dialect.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Dialect/PostgreSQL82Dialect.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -18,7 +18,7 @@ RegisterColumnType(DbType.Guid, "uuid"); } - protected override bool SupportsIfExistsBeforeTableName + public override bool SupportsIfExistsBeforeTableName { get { return true; } } Modified: trunk/nhibernate/src/NHibernate/Dialect/PostgreSQLDialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/PostgreSQLDialect.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Dialect/PostgreSQLDialect.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -77,7 +77,7 @@ get { return false; } } - protected override string CascadeConstraintsString + public override string CascadeConstraintsString { get { return " cascade"; } } Modified: trunk/nhibernate/src/NHibernate/Dialect/SQLiteDialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/SQLiteDialect.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Dialect/SQLiteDialect.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -79,7 +79,7 @@ get { return true; } } - protected override bool SupportsIfExistsBeforeTableName + public override bool SupportsIfExistsBeforeTableName { get { return true; } } Added: trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,18 @@ +using System.Data; + +namespace NHibernate.Engine.Transaction +{ + /// <summary> + /// Represents work that needs to be performed in a manner + /// which isolates it from any current application unit of + /// work transaction. + /// </summary> + public interface IIsolatedWork + { + /// <summary> + /// Perform the actual work to be done. + /// </summary> + /// <param name="connection">The ADP cpnnection to use.</param> + void DoWork(IDbConnection connection); + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Engine/Transaction/Isolater.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Engine/Transaction/Isolater.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Engine/Transaction/Isolater.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,150 @@ +using System; +using System.Data; +using System.Data.Common; +using log4net; +using NHibernate.Exceptions; + +namespace NHibernate.Engine.Transaction +{ + /// <summary> + /// Class which provides the isolation semantics required by + /// an <see cref="IIsolatedWork"/>. + /// </summary> + /// <remarks> + /// <list type="bullet"> + /// <listheader> + /// <description>Processing comes in two flavors:</description> + /// </listheader> + /// <item> + /// <term><see cref="DoIsolatedWork"/> </term> + /// <description>makes sure the work to be done is performed in a seperate, distinct transaction</description> + /// </item> + /// <item> + /// <term><see cref="DoNonTransactedWork"/> </term> + /// <description>makes sure the work to be done is performed outside the scope of any transaction</description> + /// </item> + /// </list> + /// </remarks> + public class Isolater + { + private static readonly ILog log = LogManager.GetLogger(typeof(Isolater)); + + private static DoWork GetApropieateDelegate() + { + bool isAmbientTransation = System.Transactions.Transaction.Current != null; + if (isAmbientTransation) + { + return AmbientDelegateWork; + } + else + { + return AdoDelegateWork; + } + } + /// <summary> + /// Ensures that all processing actually performed by the given work will + /// occur on a seperate transaction. + /// </summary> + /// <param name="work">The work to be performed. </param> + /// <param name="session">The session from which this request is originating. </param> + public static void DoIsolatedWork(IIsolatedWork work, ISessionImplementor session) + { + DoWork worker = GetApropieateDelegate(); + worker(session, work, true); + } + + /// <summary> + /// Ensures that all processing actually performed by the given work will + /// occur outside of a transaction. + /// </summary> + /// <param name="work">The work to be performed. </param> + /// <param name="session">The session from which this request is originating. </param> + public static void DoNonTransactedWork(IIsolatedWork work, ISessionImplementor session) + { + DoWork worker = GetApropieateDelegate(); + worker(session, work, false); + } + + private delegate void DoWork(ISessionImplementor session, IIsolatedWork work, bool transacted); + + private static void AdoDelegateWork(ISessionImplementor session, IIsolatedWork work, bool transacted) + { + IDbConnection connection = null; + IDbTransaction trans = null; + // bool wasAutoCommit = false; + try + { + connection = session.Factory.ConnectionProvider.GetConnection(); + + if (transacted) + { + trans = connection.BeginTransaction(); + // TODO NH: a way to read the autocommit state is needed + //if (TransactionManager.GetAutoCommit(connection)) + //{ + // wasAutoCommit = true; + // TransactionManager.SetAutoCommit(connection, false); + //} + } + + work.DoWork(connection); + + if (transacted) + { + trans.Commit(); + //TransactionManager.Commit(connection); + } + } + catch (Exception t) + { + try + { + if (transacted && connection != null && !(connection.State == ConnectionState.Closed)) + { + trans.Rollback(); + // TransactionManager.RollBack(connection); + } + } + catch (Exception ignore) + { + log.Debug("unable to release connection on exception [" + ignore + "]"); + } + + if (t is HibernateException) + { + throw; + } + else if (t is DbException) + { + throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, "error performing isolated work"); + } + else + { + throw new HibernateException("error performing isolated work", t); + } + } + finally + { + //if (transacted && wasAutoCommit) + //{ + // try + // { + // // TODO NH: reset autocommit + // // TransactionManager.SetAutoCommit(connection, true); + // } + // catch (Exception) + // { + // log.Debug("was unable to reset connection back to auto-commit"); + // } + //} + session.Factory.ConnectionProvider.CloseConnection(connection); + } + } + + private static void AmbientDelegateWork(ISessionImplementor session, IIsolatedWork work, bool transacted) + { + throw new NotSupportedException("Not supported yet."); + } + + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,53 @@ +using System.Data; +using NHibernate.Engine.Transaction; +using NHibernate.Exceptions; + +namespace NHibernate.Engine +{ + /// <summary> + /// Allows work to be done outside the current transaction, by suspending it, + /// and performing work in a new transaction + /// </summary> + public abstract class TransactionHelper + { + public class Work : IIsolatedWork + { + private readonly ISessionImplementor session; + private readonly TransactionHelper owner; + internal object generatedValue; + + public Work(ISessionImplementor session, TransactionHelper owner) + { + this.session = session; + this.owner = owner; + } + + #region Implementation of IIsolatedWork + + public void DoWork(IDbConnection connection) + { + try + { + generatedValue = owner.DoWorkInCurrentTransaction(connection, null); + } + catch (System.Data.OleDb.OleDbException sqle) + { + throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle, "could not get or update next value", null); + } + } + + #endregion + } + + /// <summary> The work to be done</summary> + public abstract object DoWorkInCurrentTransaction(IDbConnection conn, string sql); + + /// <summary> Suspend the current transaction and perform work in a new transaction</summary> + public virtual object DoWorkInNewTransaction(ISessionImplementor session) + { + Work work = new Work(session, this); + Isolater.DoIsolatedWork(work, session); + return work.generatedValue; + } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Id/Assigned.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Assigned.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Id/Assigned.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -52,7 +52,7 @@ #region IConfigurable Members - public void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect d) + public void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect dialect) { parms.TryGetValue(IdGeneratorParmsNames.EntityName, out entityName); if (entityName == null) Added: trunk/nhibernate/src/NHibernate/Id/Enhanced/IAccessCallback.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Enhanced/IAccessCallback.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Id/Enhanced/IAccessCallback.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,12 @@ +namespace NHibernate.Id.Enhanced +{ + /// <summary> + /// Contract for providing callback access to a <see cref="IDatabaseStructure"/>, + /// typically from the <see cref="IOptimizer"/>. + /// </summary> + public interface IAccessCallback + { + /// <summary> Retrieve the next value from the underlying source. </summary> + long NextValue { get;} + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Id/Enhanced/IDatabaseStructure.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Enhanced/IDatabaseStructure.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Id/Enhanced/IDatabaseStructure.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,44 @@ +using NHibernate.Engine; + +namespace NHibernate.Id.Enhanced +{ + /// <summary> + /// Encapsulates definition of the underlying data structure backing a sequence-style generator. + /// </summary> + public interface IDatabaseStructure + { + /// <summary> The name of the database structure (table or sequence).</summary> + string Name { get; } + + /// <summary> How many times has this structure been accessed through this reference?</summary> + int TimesAccessed { get; } + + /// <summary> The configured increment size</summary> + int IncrementSize { get; } + + /// <summary> + /// A callback to be able to get the next value from the underlying + /// structure as needed. + /// </summary> + /// <param name="session">The session. </param> + /// <returns> The next value. </returns> + IAccessCallback BuildCallback(ISessionImplementor session); + + /// <summary> + /// Prepare this structure for use. Called sometime after instantiation, + /// but before first use. + /// </summary> + /// <param name="optimizer">The optimizer being applied to the generator. </param> + void Prepare(IOptimizer optimizer); + + /// <summary> Commands needed to create the underlying structures.</summary> + /// <param name="dialect">The database dialect being used. </param> + /// <returns> The creation commands. </returns> + string[] SqlCreateStrings(Dialect.Dialect dialect); + + /// <summary> Commands needed to drop the underlying structures.</summary> + /// <param name="dialect">The database dialect being used. </param> + /// <returns> The drop commands. </returns> + string[] SqlDropStrings(Dialect.Dialect dialect); + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Id/Enhanced/IOptimizer.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Enhanced/IOptimizer.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Id/Enhanced/IOptimizer.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,54 @@ +namespace NHibernate.Id.Enhanced +{ + /// <summary> + /// Performs optimization on an optimizable identifier generator. Typically + /// this optimization takes the form of trying to ensure we do not have to + /// hit the database on each and every request to get an identifier value. + /// </summary> + /// <remarks> + /// <para> + /// Optimizers work on constructor injection. They should provide + /// a constructor with the following arguments. + /// </para> + /// - <see cref="System.Type"/> The return type for the generated values. + /// - <langword>int</langword> The increment size. + /// </remarks> + public interface IOptimizer + { + /// <summary> + /// A common means to access the last value obtained from the underlying + /// source. This is intended for testing purposes, since accessing the + /// unerlying database source directly is much more difficult. + /// </summary> + /// <value> + /// The last value we obtained from the underlying source; + /// -1 indicates we have not yet consulted with the source. + /// </value> + long LastSourceValue{get;} + /// <summary> + /// Defined increment size. + /// </summary> + /// <value> The increment size. + /// </value> + int IncrementSize{get;} + + /// <summary> + /// Generate an identifier value accounting for this specific optimization. + /// </summary> + /// <param name="callback">Callback to access the underlying value source. </param> + /// <returns> The generated identifier value. </returns> + object Generate(IAccessCallback callback); + + /// <summary> + /// Are increments to be applied to the values stored in the underlying + /// value source? + /// </summary> + /// <returns> + /// True if the values in the source are to be incremented + /// according to the defined increment size; false otherwise, in which + /// case the increment is totally an in memory construct. + /// </returns> + bool ApplyIncrementSizeToSourceValues { get;} + + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Id/Enhanced/OptimizerFactory.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Enhanced/OptimizerFactory.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Id/Enhanced/OptimizerFactory.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,265 @@ +using System; +using System.Reflection; +using log4net; +using NHibernate.Util; + +namespace NHibernate.Id.Enhanced +{ + public class OptimizerFactory + { + public const string HiLo = "hilo"; + public const string None = "none"; + public const string Pool = "pooled"; + private static readonly System.Type[] CtorSignature = new System.Type[] {typeof (System.Type), typeof (int)}; + private static readonly ILog log = LogManager.GetLogger(typeof (OptimizerFactory)); + + public static IOptimizer BuildOptimizer(string type, System.Type returnClass, int incrementSize) + { + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentNullException("type"); + } + if (returnClass == null) + { + throw new ArgumentNullException("returnClass"); + } + string optimizerClassName; + switch (type) + { + case None: + optimizerClassName = typeof (NoopOptimizer).FullName; + break; + case HiLo: + optimizerClassName = typeof (HiLoOptimizer).FullName; + break; + case Pool: + optimizerClassName = typeof (PooledOptimizer).FullName; + break; + default: + optimizerClassName = type; + break; + } + + try + { + System.Type optimizerClass = ReflectHelper.ClassForName(optimizerClassName); + ConstructorInfo ctor = optimizerClass.GetConstructor(CtorSignature); + return (IOptimizer)ctor.Invoke(new object[] { returnClass, incrementSize }); + } + catch (Exception) + { + // intentionally empty + } + + // the default... + return new NoopOptimizer(returnClass, incrementSize); + } + + #region Nested type: HiLoOptimizer + + public class HiLoOptimizer : OptimizerSupport + { + private long hiValue; + private long lastSourceValue = -1; + private long value_Renamed; + + public HiLoOptimizer(System.Type returnClass, int incrementSize) : base(returnClass, incrementSize) + { + if (incrementSize < 1) + { + throw new HibernateException("increment size cannot be less than 1"); + } + if (log.IsDebugEnabled) + { + log.Debug("creating hilo optimizer with [incrementSize=" + incrementSize + "; returnClass=" + returnClass.FullName + + "]"); + } + } + + public override long LastSourceValue + { + get { return lastSourceValue; } + } + + public long LastValue + { + get { return value_Renamed - 1; } + } + + public long HiValue + { + get { return hiValue; } + } + + public override bool ApplyIncrementSizeToSourceValues + { + get { return false; } + } + + public override object Generate(IAccessCallback callback) + { + if (lastSourceValue < 0) + { + lastSourceValue = callback.NextValue; + while (lastSourceValue <= 0) + { + lastSourceValue = callback.NextValue; + } + hiValue = (lastSourceValue * incrementSize) + 1; + value_Renamed = hiValue - incrementSize; + } + else if (value_Renamed >= hiValue) + { + lastSourceValue = callback.NextValue; + hiValue = (lastSourceValue * incrementSize) + 1; + } + return Make(value_Renamed++); + } + } + + #endregion + + #region Nested type: NoopOptimizer + + public class NoopOptimizer : OptimizerSupport + { + private long lastSourceValue = -1; + + public NoopOptimizer(System.Type returnClass, int incrementSize) : base(returnClass, incrementSize) {} + + public override long LastSourceValue + { + get { return lastSourceValue; } + } + + public override bool ApplyIncrementSizeToSourceValues + { + get { return false; } + } + + public override object Generate(IAccessCallback callback) + { + if (lastSourceValue == -1) + { + while (lastSourceValue <= 0) + { + lastSourceValue = callback.NextValue; + } + } + else + { + lastSourceValue = callback.NextValue; + } + return Make(lastSourceValue); + } + } + + #endregion + + #region Nested type: OptimizerSupport + + public abstract class OptimizerSupport : IOptimizer + { + protected int incrementSize; + protected System.Type returnClass; + + protected OptimizerSupport(System.Type returnClass, int incrementSize) + { + if (returnClass == null) + { + throw new HibernateException("return class is required"); + } + this.returnClass = returnClass; + this.incrementSize = incrementSize; + } + + public System.Type ReturnClass + { + get { return returnClass; } + } + + #region IOptimizer Members + + public int IncrementSize + { + get { return incrementSize; } + } + + public abstract long LastSourceValue { get; } + + public abstract bool ApplyIncrementSizeToSourceValues { get; } + public abstract object Generate(IAccessCallback param); + + #endregion + + protected virtual object Make(long value) + { + return IdentifierGeneratorFactory.CreateNumber(value, returnClass); + } + } + + #endregion + + #region Nested type: PooledOptimizer + + public class PooledOptimizer : OptimizerSupport + { + private long hiValue = -1; + private long value_Renamed; + + public PooledOptimizer(System.Type returnClass, int incrementSize) : base(returnClass, incrementSize) + { + if (incrementSize < 1) + { + throw new HibernateException("increment size cannot be less than 1"); + } + if (log.IsDebugEnabled) + { + log.Debug("creating pooled optimizer with [incrementSize=" + incrementSize + "; returnClass=" + + returnClass.FullName + "]"); + } + } + + public override long LastSourceValue + { + get { return hiValue; } + } + + public long LastValue + { + get { return value_Renamed - 1; } + } + + public override bool ApplyIncrementSizeToSourceValues + { + get { return true; } + } + + public override object Generate(IAccessCallback callback) + { + if (hiValue < 0) + { + value_Renamed = callback.NextValue; + if (value_Renamed < 1) + { + // unfortunately not really safe to normalize this + // to 1 as an initial value like we do the others + // because we would not be able to control this if + // we are using a sequence... + log.Info("pooled optimizer source reported [" + value_Renamed + + "] as the initial value; use of 1 or greater highly recommended"); + } + hiValue = callback.NextValue; + } + else if (value_Renamed >= hiValue) + { + hiValue = callback.NextValue; + value_Renamed = hiValue - incrementSize; + } + return Make(value_Renamed++); + } + } + + #endregion + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStructure.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStructure.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStructure.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,140 @@ +using System; +using System.Data; +using System.Data.Common; +using log4net; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.SqlCommand; +using NHibernate.SqlTypes; + +namespace NHibernate.Id.Enhanced +{ + /// <summary> + /// Describes a sequence. + /// </summary> + public class SequenceStructure : IDatabaseStructure + { + private static readonly ILog log = LogManager.GetLogger(typeof (SequenceStructure)); + private readonly int incrementSize; + private readonly int initialValue; + private readonly string sequenceName; + private readonly SqlString sql; + private int accessCounter; + private bool applyIncrementSizeToSourceValues; + + public SequenceStructure(Dialect.Dialect dialect, string sequenceName, int initialValue, int incrementSize) + { + this.sequenceName = sequenceName; + this.initialValue = initialValue; + this.incrementSize = incrementSize; + sql = new SqlString(dialect.GetSequenceNextValString(sequenceName)); + } + + #region IDatabaseStructure Members + + public string Name + { + get { return sequenceName; } + } + + public int IncrementSize + { + get { return incrementSize; } + } + + public IAccessCallback BuildCallback(ISessionImplementor session) + { + return new SequenceAccessCallback(session, this); + } + + public void Prepare(IOptimizer optimizer) + { + applyIncrementSizeToSourceValues = optimizer.ApplyIncrementSizeToSourceValues; + } + + public string[] SqlCreateStrings(Dialect.Dialect dialect) + { + int sourceIncrementSize = applyIncrementSizeToSourceValues ? incrementSize : 1; + return dialect.GetCreateSequenceStrings(sequenceName, initialValue, sourceIncrementSize); + } + + public string[] SqlDropStrings(Dialect.Dialect dialect) + { + return dialect.GetDropSequenceStrings(sequenceName); + } + + public int TimesAccessed + { + get { return accessCounter; } + } + + #endregion + + #region Nested type: SequenceAccessCallback + + private class SequenceAccessCallback : IAccessCallback + { + private readonly SequenceStructure owner; + private readonly ISessionImplementor session; + + public SequenceAccessCallback(ISessionImplementor session, SequenceStructure owner) + { + this.session = session; + this.owner = owner; + } + + #region IAccessCallback Members + + public virtual long NextValue + { + get + { + owner.accessCounter++; + try + { + IDbCommand st = session.Batcher.PrepareCommand(CommandType.Text, owner.sql, new SqlType[] {SqlTypeFactory.Int64}); + IDataReader rs = null; + try + { + rs = session.Batcher.ExecuteReader(st); + try + { + rs.Read(); + long result = rs.GetInt64(0); + if (log.IsDebugEnabled) + { + log.Debug("Sequence identifier generated: " + result); + } + return result; + } + finally + { + try + { + rs.Close(); + } + catch (Exception ignore) + { + // intentionally empty + } + } + } + finally + { + session.Batcher.CloseCommand(st, rs); + } + } + catch (DbException sqle) + { + throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle, "could not get next sequence value", + owner.sql); + } + } + } + + #endregion + } + + #endregion + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Id/Enhanced/SequenceStyleGenerator.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,133 @@ +using System.Collections.Generic; +using log4net; +using NHibernate.Engine; +using NHibernate.Mapping; +using NHibernate.Type; +using NHibernate.Util; + +namespace NHibernate.Id.Enhanced +{ + public class SequenceStyleGenerator : IPersistentIdentifierGenerator, IConfigurable + { + private static readonly ILog log = LogManager.GetLogger(typeof(SequenceStyleGenerator)); + + #region General purpose parameters + + public const string SequenceParam = "sequence_name"; + public const string DefaultSequenceName = "hibernate_sequence"; + + public const string InitialParam = "initial_value"; + public const int DefaultInitialValue = 1; + + public const string IncrementParam= "increment_size"; + public const int DefaultIncrementSize = 1; + + public const string OptimizerParam= "optimizer"; + + public const string ForceTableParam = "force_table_use"; + + #endregion + + #region table-specific parameters + + public const string ValueColumnParam= "value_column"; + public const string DefaultValueColumnName = "next_val"; + + #endregion + + private IDatabaseStructure databaseStructure; + private IOptimizer optimizer; + private IType identifierType; + + public IDatabaseStructure DatabaseStructure + { + get { return databaseStructure; } + } + + public IOptimizer Optimizer + { + get { return optimizer; } + } + + public IType IdentifierType + { + get { return identifierType; } + } + + #region Implementation of IIdentifierGenerator + + public virtual object Generate(ISessionImplementor session, object obj) + { + return optimizer.Generate(databaseStructure.BuildCallback(session)); + } + + #endregion + + #region Implementation of IPersistentIdentifierGenerator + + public virtual string[] SqlCreateStrings(Dialect.Dialect dialect) + { + return databaseStructure.SqlCreateStrings(dialect); + } + + public virtual string[] SqlDropString(Dialect.Dialect dialect) + { + return databaseStructure.SqlDropStrings(dialect); + } + + public virtual string GeneratorKey() + { + return databaseStructure.Name; + } + + #endregion + + #region Implementation of IConfigurable + + public virtual void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect dialect) + { + identifierType = type; + bool forceTableUse = PropertiesHelper.GetBoolean(ForceTableParam, parms, false); + + string sequenceName = PropertiesHelper.GetString(SequenceParam, parms, DefaultSequenceName); + if (sequenceName.IndexOf('.') < 0) + { + string schemaName; + string catalogName; + parms.TryGetValue(PersistentIdGeneratorParmsNames.Schema, out schemaName); + parms.TryGetValue(PersistentIdGeneratorParmsNames.Catalog, out catalogName); + sequenceName = Table.Qualify(catalogName, schemaName, sequenceName); + } + int initialValue = PropertiesHelper.GetInt32(InitialParam, parms, DefaultInitialValue); + int incrementSize = PropertiesHelper.GetInt32(IncrementParam, parms, DefaultIncrementSize); + + string valueColumnName = PropertiesHelper.GetString(ValueColumnParam, parms, DefaultValueColumnName); + + string defOptStrategy = incrementSize <= 1 ? OptimizerFactory.None : OptimizerFactory.Pool; + string optimizationStrategy = PropertiesHelper.GetString(OptimizerParam, parms, defOptStrategy); + if (OptimizerFactory.None.Equals(optimizationStrategy) && incrementSize > 1) + { + log.Warn("config specified explicit optimizer of [" + OptimizerFactory.None + "], but [" + IncrementParam + "=" + incrementSize + "; honoring optimizer setting"); + incrementSize = 1; + } + if (dialect.SupportsSequences && !forceTableUse) + { + if (OptimizerFactory.Pool.Equals(optimizationStrategy) && !dialect.SupportsPooledSequences) + { + // TODO : may even be better to fall back to a pooled table strategy here so that the db stored values remain consistent... + optimizationStrategy = OptimizerFactory.HiLo; + } + databaseStructure = new SequenceStructure(dialect, sequenceName, initialValue, incrementSize); + } + else + { + databaseStructure = new TableStructure(dialect, sequenceName, valueColumnName, initialValue, incrementSize); + } + + optimizer = OptimizerFactory.BuildOptimizer(optimizationStrategy, identifierType.ReturnedClass, incrementSize); + databaseStructure.Prepare(optimizer); + } + + #endregion + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Id/Enhanced/TableStructure.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/Enhanced/TableStructure.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Id/Enhanced/TableStructure.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,198 @@ +using System; +using System.Data; +using System.Text; +using log4net; +using NHibernate.Engine; +using NHibernate.SqlCommand; +using NHibernate.SqlTypes; + +namespace NHibernate.Id.Enhanced +{ + /// <summary> + /// Describes a table used to mimic sequence behavior + /// </summary> + public class TableStructure : TransactionHelper, IDatabaseStructure + { + private static readonly ILog log = LogManager.GetLogger(typeof (IDatabaseStructure)); + private static readonly ILog SqlLog = LogManager.GetLogger("NHibernate.SQL"); + private readonly int incrementSize; + private readonly int initialValue; + private readonly string tableName; + private readonly string valueColumnName; + private int accessCounter; + private bool applyIncrementSizeToSourceValues; + private readonly SqlString select; + private readonly SqlString update; + + public TableStructure(Dialect.Dialect dialect, string tableName, string valueColumnName, int initialValue, + int incrementSize) + { + this.tableName = tableName; + this.valueColumnName = valueColumnName; + this.initialValue = initialValue; + this.incrementSize = incrementSize; + + SqlStringBuilder b = new SqlStringBuilder(); + b.Add("select ").Add(valueColumnName).Add(" id_val").Add(" from ").Add(dialect.AppendLockHint(LockMode.Upgrade, + tableName)).Add( + dialect.ForUpdateString); + select = b.ToSqlString(); + + b = new SqlStringBuilder(); + b.Add("update ").Add(tableName).Add(" set ").Add(valueColumnName).Add(" = ").Add(Parameter.Placeholder).Add(" where ") + .Add(valueColumnName).Add(" = ").Add(Parameter.Placeholder); + update = b.ToSqlString(); + } + + #region Implementation of IDatabaseStructure + + public string Name + { + get { return tableName; } + } + + public int TimesAccessed + { + get { return accessCounter; } + } + + public int IncrementSize + { + get { return incrementSize; } + } + + public virtual IAccessCallback BuildCallback(ISessionImplementor session) + { + return new TableAccessCallback(session, this); + } + + public virtual void Prepare(IOptimizer optimizer) + { + applyIncrementSizeToSourceValues = optimizer.ApplyIncrementSizeToSourceValues; + } + + public virtual string[] SqlCreateStrings(Dialect.Dialect dialect) + { + return new String[] + { + "create table " + tableName + " ( " + valueColumnName + " " + dialect.GetTypeName(SqlTypeFactory.Int64) + + " )", "insert into " + tableName + " values ( " + initialValue + " )" + }; + } + + public virtual string[] SqlDropStrings(Dialect.Dialect dialect) + { + StringBuilder sqlDropString = new StringBuilder().Append("drop table "); + if (dialect.SupportsIfExistsBeforeTableName) + { + sqlDropString.Append("if exists "); + } + sqlDropString.Append(tableName).Append(dialect.CascadeConstraintsString); + if (dialect.SupportsIfExistsAfterTableName) + { + sqlDropString.Append(" if exists"); + } + return new String[] {sqlDropString.ToString()}; + } + + #endregion + + #region Overrides of TransactionHelper + + public override object DoWorkInCurrentTransaction(IDbConnection conn, string sql) + { + long result; + int rows; + do + { + string query = select.ToString(); + SqlLog.Debug(query); + IDbCommand qps = conn.CreateCommand(); + IDataReader rs = null; + qps.CommandText = query; + qps.CommandType = CommandType.Text; + qps.Transaction = conn.BeginTransaction(); + try + { + rs = qps.ExecuteReader(); + if (!rs.Read()) + { + string err = "could not read a hi value - you need to populate the table: " + tableName; + log.Error(err); + throw new IdentifierGenerationException(err); + } + result = rs.GetInt64(0); + rs.Close(); + } + catch (Exception sqle) + { + log.Error("could not read a hi value", sqle); + throw; + } + finally + { + if (rs != null) rs.Close(); + qps.Dispose(); + } + + query = update.ToString(); + SqlLog.Debug(sql); + IDbCommand ups = conn.CreateCommand(); + ups.CommandType = CommandType.Text; + ups.CommandText = query; + ups.Connection = conn; + ups.Transaction = conn.BeginTransaction(); + try + { + int increment = applyIncrementSizeToSourceValues ? incrementSize : 1; + ((IDataParameter) ups.Parameters[0]).Value = result + increment; + ((IDataParameter)ups.Parameters[1]).Value = result; + rows = ups.ExecuteNonQuery(); + } + catch (Exception sqle) + { + log.Error("could not update hi value in: " + tableName, sqle); + throw; + } + finally + { + ups.Dispose(); + } + } + while (rows == 0); + + accessCounter++; + + return result; + } + + #endregion + + #region Nested type: TableAccessCallback + + private class TableAccessCallback : IAccessCallback + { + private readonly TableStructure owner; + private readonly ISessionImplementor session; + + public TableAccessCallback(ISessionImplementor session, TableStructure owner) + { + this.session = session; + this.owner = owner; + } + + #region IAccessCallback Members + + public virtual long NextValue + { + get { return Convert.ToInt64(owner.DoWorkInNewTransaction(session)); } + } + + #endregion + } + + #endregion + + + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Id/ForeignGenerator.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/ForeignGenerator.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Id/ForeignGenerator.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -82,11 +82,11 @@ /// </summary> /// <param name="type">The <see cref="IType"/> the identifier should be.</param> /// <param name="parms">An <see cref="IDictionary"/> of Param values that are keyed by parameter name.</param> - /// <param name="d">The <see cref="Dialect.Dialect"/> to help with Configuration.</param> + /// <param name="dialect">The <see cref="Dialect.Dialect"/> to help with Configuration.</param> /// <exception cref="MappingException"> /// Thrown if the key <c>property</c> is not found in the <c>parms</c> parameter. /// </exception> - public void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect d) + public void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect dialect) { parms.TryGetValue(IdGeneratorParmsNames.EntityName, out entityName); parms.TryGetValue("property", out propertyName); Modified: trunk/nhibernate/src/NHibernate/Id/IConfigurable.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/IConfigurable.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Id/IConfigurable.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -16,7 +16,7 @@ /// </summary> /// <param name="type">The <see cref="IType"/> the identifier should be.</param> /// <param name="parms">An <see cref="IDictionary"/> of Param values that are keyed by parameter name.</param> - /// <param name="d">The <see cref="Dialect.Dialect"/> to help with Configuration.</param> - void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect d); + /// <param name="dialect">The <see cref="Dialect.Dialect"/> to help with Configuration.</param> + void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect dialect); } } \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Id/IPersistentIdentifierGenerator.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/IPersistentIdentifierGenerator.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Id/IPersistentIdentifierGenerator.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -53,7 +53,7 @@ /// <returns> /// A <see cref="String"/> that will drop the database objects. /// </returns> - string SqlDropString(Dialect.Dialect dialect); + string[] SqlDropString(Dialect.Dialect dialect); /// <summary> /// Return a key unique to the underlying database objects. Modified: trunk/nhibernate/src/NHibernate/Id/IncrementGenerator.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/IncrementGenerator.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Id/IncrementGenerator.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -38,8 +38,8 @@ /// </summary> /// <param name="type"></param> /// <param name="parms"></param> - /// <param name="d"></param> - public void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect d) + /// <param name="dialect"></param> + public void Configure(IType type, IDictionary<string, string> parms, Dialect.Dialect dialect) { string tableList; string column; @@ -62,7 +62,7 @@ { buf.Append("select ").Append(column).Append(" from "); } - buf.Append(d.Qualify(catalog, schema, tables[i])); + buf.Append(dialect.Qualify(catalog, schema, tables[i])); if (i < tables.Length - 1) buf.Append(" union "); } Modified: trunk/nhibernate/src/NHibernate/Id/SequenceGenerator.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/SequenceGenerator.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Id/SequenceGenerator.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -4,7 +4,6 @@ using log4net; using NHibernate.Engine; using NHibernate.Exceptions; -using NHibernate.Mapping; using NHibernate.SqlCommand; using NHibernate.SqlTypes; using NHibernate.Type; @@ -157,9 +156,9 @@ /// <returns> /// A <see cref="String"/> that will drop the database objects for the SequenceGenerator. /// </returns> - public string SqlDropString(Dialect.Dialect dialect) + public string[] SqlDropString(Dialect.Dialect dialect) { - return dialect.GetDropSequenceString(sequenceName); + return new string[] { dialect.GetDropSequenceString(sequenceName) }; } /// <summary> Modified: trunk/nhibernate/src/NHibernate/Id/TableGenerator.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Id/TableGenerator.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Id/TableGenerator.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -286,9 +286,9 @@ /// <returns> /// A <see cref="string"/> that will drop the database objects for the TableGenerator. /// </returns> - public string SqlDropString(Dialect.Dialect dialect) + public string[] SqlDropString(Dialect.Dialect dialect) { - return dialect.GetDropTableString(tableName); + return new string[] { dialect.GetDropTableString(tableName) }; } /// <summary> Modified: trunk/nhibernate/src/NHibernate/Mapping/Table.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Mapping/Table.cs 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/Mapping/Table.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -1022,5 +1022,19 @@ { throw new NotSupportedException(); } + + public static string Qualify(string catalog, string schema, string table) + { + StringBuilder qualifiedName = new StringBuilder(100); + if (catalog != null) + { + qualifiedName.Append(catalog).Append('.'); + } + if (schema != null) + { + qualifiedName.Append(schema).Append('.'); + } + return qualifiedName.Append(table).ToString(); + } } } Modified: trunk/nhibernate/src/NHibernate/NHibernate-2.0.csproj =================================================================== --- trunk/nhibernate/src/NHibernate/NHibernate-2.0.csproj 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate/NHibernate-2.0.csproj 2008-08-15 21:41:48 UTC (rev 3706) @@ -631,6 +631,9 @@ <Compile Include="Engine\Query\Sql\NativeSQLQueryRootReturn.cs" /> <Compile Include="Engine\Query\Sql\NativeSQLQueryScalarReturn.cs" /> <Compile Include="Engine\StatefulPersistenceContext.cs" /> + <Compile Include="Engine\TransactionHelper.cs" /> + <Compile Include="Engine\Transaction\IIsolatedWork.cs" /> + <Compile Include="Engine\Transaction\Isolater.cs" /> <Compile Include="Engine\TwoPhaseLoad.cs" /> <Compile Include="Engine\ValueInclusion.cs" /> <Compile Include="Engine\VersionValue.cs" /> @@ -738,6 +741,13 @@ <Compile Include="Criterion\SqlFunctionProjection.cs" /> <Compile Include="FKUnmatchingColumnsException.cs" /> <Compile Include="Id\AbstractPostInsertGenerator.cs" /> + <Compile Include="Id\Enhanced\IAccessCallback.cs" /> + <Compile Include="Id\Enhanced\IDatabaseStructure.cs" /> + <Compile Include="Id\Enhanced\IOptimizer.cs" /> + <Compile Include="Id\Enhanced\OptimizerFactory.cs" /> + <Compile Include="Id\Enhanced\SequenceStructure.cs" /> + <Compile Include="Id\Enhanced\SequenceStyleGenerator.cs" /> + <Compile Include="Id\Enhanced\TableStructure.cs" /> <Compile Include="Id\Insert\AbstractReturningDelegate.cs"> <SubType>Code</SubType> </Compile> Added: trunk/nhibernate/src/NHibernate.Test/IdGen/Enhanced/SequenceStyleConfigUnitFixture.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/IdGen/Enhanced/SequenceStyleConfigUnitFixture.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/IdGen/Enhanced/SequenceStyleConfigUnitFixture.cs 2008-08-15 21:41:48 UTC (rev 3706) @@ -0,0 +1,172 @@ +using System.Collections.Generic; +using NHibernate.Id.Enhanced; +using NUnit.Framework; +using NUnit.Framework.SyntaxHelpers; + +namespace NHibernate.Test.IdGen.Enhanced +{ + /// <summary> + /// Tests that SequenceStyleGenerator configures itself as expected in various scenarios + /// </summary> + [TestFixture] + public class SequenceStyleConfigUnitFixture + { + private class TableDialect : Dialect.Dialect + { + public override bool SupportsSequences + { + get { return false; } + } + } + + private class SequenceDialect : Dialect.Dialect + { + public override bool SupportsSequences + { + get { return true; } + } + + public override bool SupportsPooledSequences + { + get { return false; } + } + + public override string GetSequenceNextValString(string sequenceName) + { + return string.Empty; + } + } + + private class PooledSequenceDialect : SequenceDialect + { + public override bool SupportsPooledSequences + { + get { return true; } + } + } + + // Test explicitly specifying both optimizer and increment + [Test] + public void ExplicitOptimizerWithExplicitIncrementSize() + { + // with sequence ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Dialect.Dialect dialect = new SequenceDialect(); + + // optimizer=none w/ increment > 1 => should honor optimizer + IDictionary<string, string> props = new Dictionary<string, string>(); + props[SequenceStyleGenerator.OptimizerParam] = OptimizerFactory.None; + props[SequenceStyleGenerator.IncrementParam] = "20"; + SequenceStyleGenerator generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (SequenceStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.NoopOptimizer))); + Assert.AreEqual(1, generator.Optimizer.IncrementSize); + Assert.AreEqual(1, generator.DatabaseStructure.IncrementSize); + + // optimizer=hilo w/ increment > 1 => hilo + props = new Dictionary<string, string>(); + props[SequenceStyleGenerator.OptimizerParam] = OptimizerFactory.HiLo; + props[SequenceStyleGenerator.IncrementParam] = "20"; + generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (SequenceStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.HiLoOptimizer))); + Assert.AreEqual(20, generator.Optimizer.IncrementSize); + Assert.AreEqual(20, generator.DatabaseStructure.IncrementSize); + + // optimizer=pooled w/ increment > 1 => hilo + props = new Dictionary<string, string>(); + props[SequenceStyleGenerator.OptimizerParam] = OptimizerFactory.Pool; + props[SequenceStyleGenerator.IncrementParam] = "20"; + generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (SequenceStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.HiLoOptimizer))); + Assert.AreEqual(20, generator.Optimizer.IncrementSize); + Assert.AreEqual(20, generator.DatabaseStructure.IncrementSize); + } + + // Test all params defaulted with a dialect supporting sequences + [Test] + public void DefaultedSequenceBackedConfiguration() + { + Dialect.Dialect dialect = new SequenceDialect(); + IDictionary<string, string> props = new Dictionary<string, string>(); + SequenceStyleGenerator generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (SequenceStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.NoopOptimizer))); + Assert.That(generator.DatabaseStructure.Name, Is.EqualTo(SequenceStyleGenerator.DefaultSequenceName)); + } + + // Test all params defaulted with a dialect which does not support sequences + [Test] + public void DefaultedTableBackedConfiguration() + { + Dialect.Dialect dialect = new TableDialect(); + IDictionary<string, string> props = new Dictionary<string, string>(); + SequenceStyleGenerator generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (TableStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.NoopOptimizer))); + Assert.That(generator.DatabaseStructure.Name, Is.EqualTo(SequenceStyleGenerator.DefaultSequenceName)); + } + + //Test default optimizer selection for sequence backed generators + //based on the configured increment size; both in the case of the + //dialect supporting pooled sequences (pooled) and not (hilo) + [Test] + public void DefaultOptimizerBasedOnIncrementBackedBySequence() + { + IDictionary<string, string> props = new Dictionary<string, string>(); + props[SequenceStyleGenerator.IncrementParam] = "10"; + + // for dialects which do not support pooled sequences, we default to hilo + Dialect.Dialect dialect = new SequenceDialect(); + SequenceStyleGenerator generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (SequenceStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.HiLoOptimizer))); + Assert.That(generator.DatabaseStructure.Name, Is.EqualTo(SequenceStyleGenerator.DefaultSequenceName)); + + // for dialects which do support pooled sequences, we default to pooled + dialect = new PooledSequenceDialect(); + generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (SequenceStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.PooledOptimizer))); + Assert.That(generator.DatabaseStructure.Name, Is.EqualTo(SequenceStyleGenerator.DefaultSequenceName)); + } + + // Test default optimizer selection for table backed generators + // based on the configured increment size. Here we always prefer pooled. + [Test] + public void DefaultOptimizerBasedOnIncrementBackedByTable() + { + IDictionary<string, string> props = new Dictionary<string, string>(); + props[SequenceStyleGenerator.IncrementParam] = "10"; + Dialect.Dialect dialect = new TableDialect(); + SequenceStyleGenerator generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (TableStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.PooledOptimizer))); + Assert.That(generator.DatabaseStructure.Name, Is.EqualTo(SequenceStyleGenerator.DefaultSequenceName)); + } + + // Test forcing of table as backing strucuture with dialect supporting sequences + [Test] + public void ForceTableUse() + { + Dialect.Dialect dialect = new SequenceDialect(); + IDictionary<string, string> props = new Dictionary<string, string>(); + props[SequenceStyleGenerator.ForceTableParam] = "true"; + SequenceStyleGenerator generator = new SequenceStyleGenerator(); + generator.Configure(NHibernateUtil.Int64, props, dialect); + Assert.That(generator.DatabaseStructure, Is.AssignableFrom(typeof (TableStructure))); + Assert.That(generator.Optimizer, Is.AssignableFrom(typeof (OptimizerFactory.NoopOptimizer))); + Assert.That(generator.DatabaseStructure.Name, Is.EqualTo(SequenceStyleGenerator.DefaultSequenceName)); + } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate.Test/NHibernate.Test-2.0.csproj =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHibernate.Test-2.0.csproj 2008-08-15 11:25:15 UTC (rev 3705) +++ trunk/nhibernate/src/NHibernate.Test/NHibernate.Test-2.0.csproj 2008-08-15 21:41:48 UTC (rev 3706) @@ -226,6 +226,7 @@ <Compile Include="HQLFunctionTest\SQLFunctionTemplateTest.cs" /> <Compile Include="BulkManipulation\NativeSQLBulkOperations.cs" /> <Compile Include="BulkManipulation\Vehicles.cs" /> + <Compile Include="IdGen\Enhanced\SequenceStyleConfigUnitFixture.cs" /> <Compile Include="IdTest\Car.cs" /> <Compile Include="IdTest\HiLoInt16Class.cs" /> <Compile Include="IdTest\HiLoInt32Class.cs" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |