Skip to content

NH-3499 - Allow custom QueryModelVisitorBase to be provided through the session factory #374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/NHibernate.Test/Linq/CustomQueryModelRewriterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using NHibernate.Linq.Visitors;
using NUnit.Framework;
using Remotion.Linq;
using Remotion.Linq.Clauses;
using Remotion.Linq.Parsing;

namespace NHibernate.Test.Linq
{
public class CustomQueryModelRewriterTests : LinqTestCase
{
protected override void Configure(Cfg.Configuration configuration)
{
configuration.Properties[Cfg.Environment.QueryModelRewriterFactory] = typeof(QueryModelRewriterFactory).AssemblyQualifiedName;
}

[Test]
public void RewriteNullComparison()
{
// This example shows how to use the query model rewriter to
// make radical changes to the query. In this case, we rewrite
// a null comparison (which would translate into a IS NULL)
// into a comparison to "Thomas Hardy" (which translates to a = "Thomas Hardy").

var contacts = (from c in db.Customers where c.ContactName == null select c).ToList();
Assert.Greater(contacts.Count, 0);
Assert.IsTrue(contacts.Select(customer => customer.ContactName).All(c => c == "Thomas Hardy"));
}

[Serializable]
public class QueryModelRewriterFactory : IQueryModelRewriterFactory
{
public QueryModelVisitorBase CreateVisitor(VisitorParameters parameters)
{
return new CustomVisitor();
}
}

public class CustomVisitor : QueryModelVisitorBase
{
public override void VisitWhereClause(WhereClause whereClause, QueryModel queryModel, int index)
{
whereClause.TransformExpressions(new Visitor().VisitExpression);
}

private class Visitor : ExpressionTreeVisitor
{
protected override Expression VisitBinaryExpression(BinaryExpression expression)
{
if (
expression.NodeType == ExpressionType.Equal ||
expression.NodeType == ExpressionType.NotEqual
)
{
var left = expression.Left;
var right = expression.Right;
bool reverse = false;

if (!(left is ConstantExpression) && right is ConstantExpression)
{
var tmp = left;
left = right;
right = tmp;
reverse = true;
}

var constant = left as ConstantExpression;

if (constant != null && constant.Value == null)
{
left = Expression.Constant("Thomas Hardy");

expression = Expression.MakeBinary(
expression.NodeType,
reverse ? right : left,
reverse ? left : right
);
}
}

return base.VisitBinaryExpression(expression);
}
}
}
}
}
5 changes: 5 additions & 0 deletions src/NHibernate.Test/NHibernate.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\net\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="Remotion.Linq, Version=1.13.171.1, Culture=neutral, PublicKeyToken=fee00910d6e5f53b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\net\Remotion.Linq.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core">
Expand Down Expand Up @@ -512,6 +516,7 @@
<Compile Include="Linq\ByMethod\GetValueOrDefaultTests.cs" />
<Compile Include="Linq\CasingTest.cs" />
<Compile Include="Linq\CharComparisonTests.cs" />
<Compile Include="Linq\CustomQueryModelRewriterTests.cs" />
<Compile Include="Linq\DateTimeTests.cs" />
<Compile Include="Linq\ExpressionSessionLeakTest.cs" />
<Compile Include="Linq\LoggingTests.cs" />
Expand Down
2 changes: 2 additions & 0 deletions src/NHibernate/Cfg/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ public static string Version
/// <summary> Enable ordering of insert statements for the purpose of more effecient batching.</summary>
public const string OrderInserts = "order_inserts";

public const string QueryModelRewriterFactory = "query.query_model_rewriter_factory";

/// <summary>
/// If this setting is set to false, exceptions in IInterceptor.BeforeTransactionCompletion bubble to the caller of ITransaction.Commit and abort the commit.
/// If this setting is set to true, exceptions in IInterceptor.BeforeTransactionCompletion are ignored and the commit is performed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NHibernate.Connection;
using NHibernate.Driver;
using NHibernate.Exceptions;
using NHibernate.Linq.Visitors;
using NHibernate.Transaction;

namespace NHibernate.Cfg.Loquacious
Expand Down Expand Up @@ -123,6 +124,11 @@ public SchemaAutoAction SchemaAction
set { configuration.SetProperty(Environment.Hbm2ddlAuto, value.ToString()); }
}

public void QueryModelRewriterFactory<TFactory>() where TFactory : IQueryModelRewriterFactory
{
configuration.SetProperty(Environment.QueryModelRewriterFactory, typeof(TFactory).AssemblyQualifiedName);
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NHibernate.Connection;
using NHibernate.Driver;
using NHibernate.Exceptions;
using NHibernate.Linq.Visitors;
using NHibernate.Transaction;

namespace NHibernate.Cfg.Loquacious
Expand Down Expand Up @@ -35,5 +36,7 @@ public interface IDbIntegrationConfigurationProperties
byte MaximumDepthOfOuterJoinFetching { set; }

SchemaAutoAction SchemaAction { set; }

void QueryModelRewriterFactory<TFactory>() where TFactory : IQueryModelRewriterFactory;
}
}
3 changes: 3 additions & 0 deletions src/NHibernate/Cfg/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using NHibernate.Exceptions;
using NHibernate.Hql;
using NHibernate.Linq.Functions;
using NHibernate.Linq.Visitors;
using NHibernate.Transaction;

namespace NHibernate.Cfg
Expand Down Expand Up @@ -129,6 +130,8 @@ public Settings()
[Obsolete("This setting is likely to be removed in a future version of NHibernate. The workaround is to catch all exceptions in the IInterceptor implementation.")]
public bool IsInterceptorsBeforeTransactionCompletionIgnoreExceptionsEnabled { get; internal set; }

public IQueryModelRewriterFactory QueryModelRewriterFactory { get; internal set; }

#endregion
}
}
24 changes: 24 additions & 0 deletions src/NHibernate/Cfg/SettingsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using NHibernate.Exceptions;
using NHibernate.Hql;
using NHibernate.Linq.Functions;
using NHibernate.Linq.Visitors;
using NHibernate.Transaction;
using NHibernate.Util;

Expand Down Expand Up @@ -288,6 +289,8 @@ public Settings BuildSettings(IDictionary<string, string> properties)
settings.IsMinimalPutsEnabled = useMinimalPuts;
// Not ported - JdbcBatchVersionedData

settings.QueryModelRewriterFactory = CreateQueryModelRewriterFactory(properties);

// NHibernate-specific:
settings.IsolationLevel = isolation;

Expand Down Expand Up @@ -379,5 +382,26 @@ private static ITransactionFactory CreateTransactionFactory(IDictionary<string,
throw new HibernateException("could not instantiate TransactionFactory: " + className, cnfe);
}
}

private static IQueryModelRewriterFactory CreateQueryModelRewriterFactory(IDictionary<string, string> properties)
{
string className = PropertiesHelper.GetString(Environment.QueryModelRewriterFactory, properties, null);

if (className == null)
return null;

log.Info("Query model rewriter factory factory: " + className);

try
{
return
(IQueryModelRewriterFactory)
Environment.BytecodeProvider.ObjectsFactory.CreateInstance(ReflectHelper.ClassForName(className));
}
catch (Exception cnfe)
{
throw new HibernateException("could not instantiate IQueryModelRewriterFactory: " + className, cnfe);
}
}
}
}
13 changes: 13 additions & 0 deletions src/NHibernate/Linq/Visitors/IQueryModelRewriterFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Remotion.Linq;

namespace NHibernate.Linq.Visitors
{
public interface IQueryModelRewriterFactory
{
QueryModelVisitorBase CreateVisitor(VisitorParameters parameters);
}
}
10 changes: 10 additions & 0 deletions src/NHibernate/Linq/Visitors/QueryModelVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ public static ExpressionToHqlTranslationResults GenerateHqlQuery(QueryModel quer
// Move OrderBy clauses to end
MoveOrderByToEndRewriter.ReWrite(queryModel);

// Give a rewriter provided by the session factory a chance to
// rewrite the query.
var rewriterFactory = parameters.SessionFactory.Settings.QueryModelRewriterFactory;
if (rewriterFactory != null)
{
var customVisitor = rewriterFactory.CreateVisitor(parameters);
if (customVisitor != null)
customVisitor.VisitQueryModel(queryModel);
}

// rewrite any operators that should be applied on the outer query
// by flattening out the sub-queries that they are located in
var result = ResultOperatorRewriter.Rewrite(queryModel);
Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/NHibernate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@
<Compile Include="Linq\NestedSelects\Tuple.cs" />
<Compile Include="Linq\NestedSelects\SelectClauseRewriter.cs" />
<Compile Include="Linq\NestedSelects\ExpressionHolder.cs" />
<Compile Include="Linq\Visitors\IQueryModelRewriterFactory.cs" />
<Compile Include="Linq\Visitors\LeftJoinRewriter.cs" />
<Compile Include="Linq\Functions\CompareGenerator.cs" />
<Compile Include="Linq\ExpressionTransformers\SimplifyCompareTransformer.cs" />
Expand Down