Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
...
  • 5 commits
  • 47 files changed
  • 0 commit comments
  • 1 contributor
Showing with 968 additions and 222 deletions.
  1. +1 −0 .gitignore
  2. +18 −0 Lucene.Net.Linq.Tests/App.config
  3. +8 −1 Lucene.Net.Linq.Tests/Integration/IntegrationTestBase.cs
  4. +0 −1 Lucene.Net.Linq.Tests/Integration/SampleProgram.cs
  5. +28 −1 Lucene.Net.Linq.Tests/Integration/SelectTests.cs
  6. +38 −0 Lucene.Net.Linq.Tests/Integration/SessionTests.cs
  7. +9 −2 Lucene.Net.Linq.Tests/Lucene.Net.Linq.Tests.csproj
  8. +104 −34 Lucene.Net.Linq.Tests/LuceneSessionTests.cs
  9. +34 −0 Lucene.Net.Linq.Tests/Mapping/ReflectionDocumentMapperTests.cs
  10. +101 −0 ...Linq.Tests/Transformation/TreeVisitors/BooleanBinaryToQueryPredicateExpressionTreeVisitorTests.cs
  11. +4 −4 ...ryExpressionTreeVisitorTests.cs → CompareCallToLuceneQueryPredicateExpressionTreeVisitorTests.cs}
  12. +17 −1 Lucene.Net.Linq.Tests/Transformation/TreeVisitors/FlagToBinaryConditionTreeVisitorTests.cs
  13. +3 −3 ...aryExpressionTreeVisitorTests.cs → MethodCallToLuceneQueryPredicateExpressionTreeVisitorTests.cs}
  14. +54 −0 Lucene.Net.Linq.Tests/Transformation/TreeVisitors/NoOpConvertExpressionRemovingVisitorTests.cs
  15. +10 −1 Lucene.Net.Linq.Tests/Translation/QueryModelTranslatorTests.cs
  16. +1 −0 Lucene.Net.Linq.Tests/packages.config
  17. +9 −22 Lucene.Net.Linq/Clauses/BoostClause.cs
  18. +4 −3 Lucene.Net.Linq/Clauses/ExpressionNodes/BoostExpressionNode.cs
  19. +34 −0 Lucene.Net.Linq/Clauses/ExpressionNodes/TrackRetrievedDocumentsExpressionNode.cs
  20. +34 −0 Lucene.Net.Linq/Clauses/ExtensionClause.cs
  21. +29 −0 Lucene.Net.Linq/Clauses/TrackRetrievedDocumentsClause.cs
  22. +1 −0 Lucene.Net.Linq/ILuceneQueryModelVisitor.cs
  23. +7 −0 Lucene.Net.Linq/IRetrievedDocumentTracker.cs
  24. +3 −1 Lucene.Net.Linq/ISession.cs
  25. +13 −1 Lucene.Net.Linq/Lucene.Net.Linq.csproj
  26. +5 −1 Lucene.Net.Linq/Lucene.Net.Linq.nuspec
  27. +20 −5 Lucene.Net.Linq/LuceneDataProvider.cs
  28. +7 −0 Lucene.Net.Linq/LuceneMethods.cs
  29. +25 −43 Lucene.Net.Linq/LuceneQueryExecutor.cs
  30. +6 −4 Lucene.Net.Linq/LuceneQueryModel.cs
  31. +123 −30 Lucene.Net.Linq/LuceneSession.cs
  32. +14 −0 Lucene.Net.Linq/Mapping/ReflectionDocumentMapper.cs
  33. +45 −0 Lucene.Net.Linq/ScalarResultHandlers/ScalarResultHandler.cs
  34. +33 −0 Lucene.Net.Linq/ScalarResultHandlers/ScalarResultHandlerRegistry.cs
  35. +12 −7 Lucene.Net.Linq/Transformation/QueryModelTransformer.cs
  36. +35 −0 Lucene.Net.Linq/Transformation/TreeVisitors/BooleanBinaryToQueryPredicateExpressionTreeVisitor.cs
  37. +2 −2 Lucene.Net.Linq/Transformation/TreeVisitors/CompareCallToBinaryExpressionTreeVisitor.cs
  38. +13 −11 Lucene.Net.Linq/Transformation/TreeVisitors/FlagToBinaryConditionTreeVisitor.cs
  39. +2 −2 Lucene.Net.Linq/Transformation/TreeVisitors/MethodCallToBinaryExpressionTreeVisitor.cs
  40. +46 −0 Lucene.Net.Linq/Transformation/TreeVisitors/NoOpConvertExpressionRemovingVisitor.cs
  41. +2 −2 Lucene.Net.Linq/Transformation/TreeVisitors/NullSafetyConditionRemovingTreeVisitor.cs
  42. +5 −0 Lucene.Net.Linq/Translation/QueryModelTranslator.cs
  43. 0 ...anslation/ResultOperatorHandlers/{ResultOperatorHandlers.cs → AggregateResultOperatorHandlers.cs}
  44. +3 −12 Lucene.Net.Linq/Util/ExpressionExtensions.cs
  45. +2 −23 Lucene.Net.Linq/Util/Log.cs
  46. +1 −0 Lucene.Net.Linq/packages.config
  47. +3 −5 README.markdown
View
1 .gitignore
@@ -7,3 +7,4 @@ obj
build/
*.cache
Release/
+packages/
View
18 Lucene.Net.Linq.Tests/App.config
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <configSections>
+ <sectionGroup name="common">
+ <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
+ </sectionGroup>
+ </configSections>
+
+ <common>
+ <logging>
+ <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
+ <arg key="level" value="OFF" />
+ <arg key="showLogName" value="true" />
+ <arg key="showDataTime" value="false" />
+ </factoryAdapter>
+ </logging>
+ </common>
+</configuration>
View
9 Lucene.Net.Linq.Tests/Integration/IntegrationTestBase.cs
@@ -31,11 +31,18 @@ protected virtual Analyzer GetAnalyzer(Version version)
public class SampleDocument
{
+ private static int count;
+ public SampleDocument()
+ {
+ Key = (count++).ToString();
+ }
+
public string Name { get; set; }
[Field(IndexMode.NotAnalyzed)]
public string Id { get; set; }
+ [Field(Key = true)]
public string Key { get; set; }
public int Scalar { get; set; }
@@ -53,7 +60,7 @@ public class SampleDocument
public string Alias { get; set; }
}
- protected void AddDocument<T>(T document)
+ protected void AddDocument<T>(T document) where T : new()
{
using (var session = provider.OpenSession<T>())
{
View
1 Lucene.Net.Linq.Tests/Integration/SampleProgram.cs
@@ -21,7 +21,6 @@ public static void Main()
using (var session = provider.OpenSession<Article>())
{
session.Add(new Article { Author = "John Doe", BodyText = "some body text", PublishDate = DateTimeOffset.UtcNow });
- session.Commit();
}
var articles = provider.AsQueryable<Article>();
View
29 Lucene.Net.Linq.Tests/Integration/SelectTests.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using Lucene.Net.Analysis;
using Lucene.Net.Linq.Mapping;
using NUnit.Framework;
@@ -283,6 +284,32 @@ public void Where_StartsWith()
}
[Test]
+ public void Where_StartsWith_Negate()
+ {
+ AddDocument(new SampleDocument { Name = "Other Document" });
+ AddDocument(new SampleDocument { Name = "My Document", NullableScalar = 12 });
+
+ var documents = provider.AsQueryable<SampleDocument>();
+
+ var result = from doc in documents where !doc.Name.StartsWith("other") select doc;
+
+ Assert.That(result.Single().Name, Is.EqualTo("My Document"));
+ }
+
+ [Test]
+ public void Where_StartsWith_Negate_NullSafe()
+ {
+ AddDocument(new SampleDocument { Name = "Other Document" });
+ AddDocument(new SampleDocument { Name = "My Document", NullableScalar = 12 });
+
+ var documents = provider.AsQueryable<SampleDocument>();
+
+ var result = from doc in documents where (bool)(!((bool?)doc.Name.StartsWith("other"))) select doc;
+
+ Assert.That(result.Single().Name, Is.EqualTo("My Document"));
+ }
+
+ [Test]
public void Where_Compare()
{
AddDocument(new SampleDocument { Name = "Other Document", Id = "b" });
View
38 Lucene.Net.Linq.Tests/Integration/SessionTests.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq;
+using NUnit.Framework;
+
+namespace Lucene.Net.Linq.Tests.Integration
+{
+ [TestFixture]
+ public class SessionTests : IntegrationTestBase
+ {
+ protected override Analysis.Analyzer GetAnalyzer(Net.Util.Version version)
+ {
+ return new LowercaseKeywordAnalyzer();
+ }
+
+ [SetUp]
+ public void AddDocuments()
+ {
+ AddDocument(new SampleDocument { Name = "c", Scalar = 3, Flag = true, Version = new Version(100, 0, 0) });
+ AddDocument(new SampleDocument { Name = "a", Scalar = 1, Version = new Version(20, 0, 0) });
+ AddDocument(new SampleDocument { Name = "b", Scalar = 2, Flag = true, Version = new Version(3, 0, 0) });
+ }
+
+ [Test]
+ public void Query()
+ {
+ var session = provider.OpenSession<SampleDocument>();
+
+ using (session)
+ {
+ var item = (from d in session.Query() where d.Name == "a" select d).Single();
+ item.Scalar = 4;
+ }
+
+ var result = (from d in session.Query() where d.Name == "a" select d).Single();
+ Assert.That(result.Scalar, Is.EqualTo(4));
+ }
+ }
+}
View
11 Lucene.Net.Linq.Tests/Lucene.Net.Linq.Tests.csproj
@@ -31,6 +31,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="Common.Logging, Version=2.1.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
+ <HintPath>..\packages\Common.Logging.2.1.1\lib\net40\Common.Logging.dll</HintPath>
+ </Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
@@ -70,6 +73,7 @@
<Compile Include="Integration\SelectCollectionTests.cs" />
<Compile Include="Integration\CustomWhereTests.cs" />
<Compile Include="Integration\SelectTests.cs" />
+ <Compile Include="Integration\SessionTests.cs" />
<Compile Include="Integration\SingleResultTests.cs" />
<Compile Include="Integration\SkipTakeTests.cs" />
<Compile Include="LowercaseKeywordAnalyzer.cs" />
@@ -86,11 +90,13 @@
<Compile Include="Mapping\ReflectionDocumentMapperTests.cs" />
<Compile Include="Record.cs" />
<Compile Include="Transformation\TreeVisitors\BoostMethodCallTreeVisitorTests.cs" />
- <Compile Include="Transformation\TreeVisitors\CompareCallToBinaryExpressionTreeVisitorTests.cs" />
+ <Compile Include="Transformation\TreeVisitors\CompareCallToLuceneQueryPredicateExpressionTreeVisitorTests.cs" />
+ <Compile Include="Transformation\TreeVisitors\BooleanBinaryToQueryPredicateExpressionTreeVisitorTests.cs" />
<Compile Include="Transformation\TreeVisitors\LuceneExtensionMethodCallTreeVisitorTests.cs" />
- <Compile Include="Transformation\TreeVisitors\MethodCallToBinaryExpressionTreeVisitorTests.cs" />
+ <Compile Include="Transformation\TreeVisitors\MethodCallToLuceneQueryPredicateExpressionTreeVisitorTests.cs" />
<Compile Include="Transformation\TreeVisitors\MethodInfoMatchingTreeVisitorTests.cs" />
<Compile Include="Transformation\TreeVisitors\NoOpConditionRemovingTreeVisitorTests.cs" />
+ <Compile Include="Transformation\TreeVisitors\NoOpConvertExpressionRemovingVisitorTests.cs" />
<Compile Include="Transformation\TreeVisitors\NoOpMethodCallRemovingTreeVisitorTests.cs" />
<Compile Include="VersionConverter.cs" />
<Compile Include="Mapping\FieldMappingInfoBuilderCollectionComplexTypeTests.cs" />
@@ -106,6 +112,7 @@
<Compile Include="Transformation\TreeVisitors\QuerySourceReferencePropertyTransformingTreeVisitorTests.cs" />
</ItemGroup>
<ItemGroup>
+ <None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
View
138 Lucene.Net.Linq.Tests/LuceneSessionTests.cs
@@ -1,15 +1,16 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Linq.Expressions;
using Lucene.Net.Analysis;
-using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Linq.Abstractions;
using Lucene.Net.Linq.Mapping;
using Lucene.Net.Search;
-using Lucene.Net.Util;
using NUnit.Framework;
using Rhino.Mocks;
+using Version = Lucene.Net.Util.Version;
namespace Lucene.Net.Linq.Tests
{
@@ -27,11 +28,11 @@ public void SetUp()
{
mapper = MockRepository.GenerateStrictMock<IDocumentMapper<Record>>();
writer = MockRepository.GenerateStrictMock<IIndexWriter>();
- analyzer = new StandardAnalyzer(Version.LUCENE_29);
+ analyzer = new LowercaseKeywordAnalyzer();
context = MockRepository.GenerateStub<Context>(null, analyzer, Version.LUCENE_29, writer, new object());
- session = new LuceneSession<Record>(mapper, context);
+ session = new LuceneSession<Record>(mapper, context, null);
mapper.Expect(m => m.ToKey(Arg<Record>.Is.NotNull))
.WhenCalled(mi => mi.ReturnValue = new DocumentKey(new Dictionary<IFieldMappingInfo, object> { { new FakeFieldMappingInfo { FieldName = "Id"}, ((Record)mi.Arguments[0]).Id } }))
@@ -44,14 +45,14 @@ public void AddWithSameKeyReplaces()
var r1 = new Record { Id = "11", Name = "A" };
var r2 = new Record { Id = "11", Name = "B" };
- mapper.Expect(m => m.ToDocument(Arg<Record>.Is.Same(r1), Arg<Document>.Is.NotNull));
mapper.Expect(m => m.ToDocument(Arg<Record>.Is.Same(r2), Arg<Document>.Is.NotNull));
session.Add(r1, r2);
+ var pendingAdditions = session.ConvertPendingAdditions();
Verify();
-
- Assert.That(session.Additions.Count(), Is.EqualTo(1));
+
+ Assert.That(pendingAdditions.Count(), Is.EqualTo(1));
}
[Test]
@@ -94,51 +95,55 @@ public void Commit_Delete()
}
[Test]
- public void Commit_Add()
+ public void Commit_Add_DeletesKey()
{
- var doc1 = new Document();
- var doc2 = new Document();
+ var key = new DocumentKey(new Dictionary<IFieldMappingInfo, object> { { new FakeFieldMappingInfo { FieldName = "Id" }, 1 } });
- session.Add(new DocumentKey(), doc1);
- session.Add(new DocumentKey(), doc2);
+ var record = new Record {Id = "1"};
- writer.Expect(w => w.AddDocument(doc1));
- writer.Expect(w => w.AddDocument(doc2));
+ session.Add(record);
+
+ mapper.Expect(m => m.ToDocument(Arg<Record>.Is.Same(record), Arg<Document>.Is.NotNull));
+ writer.Expect(w => w.DeleteDocuments(new[] {key.ToQuery(context.Analyzer, context.Version)}));
+ writer.Expect(w => w.AddDocument(Arg<Document>.Is.NotNull));
writer.Expect(w => w.Commit());
session.Commit();
Verify();
- Assert.That(session.Additions, Is.Empty, "Commit should clear pending deletions.");
+ Assert.That(session.ConvertPendingAdditions, Is.Empty, "Commit should clear pending deletions.");
}
[Test]
- public void Commit_Add_DeletesKey()
+ public void Commit_Add_ConvertsDocumentAndKeyLate()
{
- var doc1 = new Document();
- var key = new DocumentKey(new Dictionary<IFieldMappingInfo, object> { { new FakeFieldMappingInfo { FieldName = "Id" }, 1 } });
-
- session.Add(key, doc1);
+ var record = new Record();
+ var key = new DocumentKey(new Dictionary<IFieldMappingInfo, object> { { new FakeFieldMappingInfo { FieldName = "Id" }, "biully" } });
+ var deleteQuery = key.ToQuery(context.Analyzer, Version.LUCENE_29);
- writer.Expect(w => w.DeleteDocuments(new[] {key.ToQuery(context.Analyzer, context.Version)}));
- writer.Expect(w => w.AddDocument(doc1));
+ mapper.Expect(m => m.ToDocument(Arg<Record>.Is.Same(record), Arg<Document>.Is.NotNull));
+ writer.Expect(w => w.DeleteDocuments(new[] { deleteQuery }));
+ writer.Expect(w => w.AddDocument(Arg<Document>.Is.NotNull));//Matches(doc => doc.GetValues("Name")[0] == "a name")));
writer.Expect(w => w.Commit());
+
+ session.Add(record);
+
+ record.Id = "biully";
+ record.Name = "a name";
session.Commit();
Verify();
- Assert.That(session.Additions, Is.Empty, "Commit should clear pending deletions.");
+ Assert.That(session.ConvertPendingAdditions, Is.Empty, "Commit should clear pending deletions.");
}
+
[Test]
public void Commit_ReloadsSearcher()
{
- var doc1 = new Document();
-
- session.Add(new DocumentKey(), doc1);
-
- writer.Expect(w => w.AddDocument(doc1));
+ session.DeleteAll();
+ writer.Expect(w => w.DeleteAll());
writer.Expect(w => w.Commit());
session.Commit();
@@ -168,12 +173,11 @@ public void DeleteAll()
public void DeleteAllClearsPendingAdditions()
{
var r1 = new Record();
- mapper.Expect(m => m.ToDocument(Arg<Record>.Is.Same(r1), Arg<Document>.Is.NotNull));
session.Add(r1);
session.DeleteAll();
-
- Assert.That(session.Additions, Is.Empty, "Additions");
+
+ Assert.That(session.ConvertPendingAdditions, Is.Empty, "Additions");
Verify();
}
@@ -189,6 +193,17 @@ public void Delete()
}
[Test]
+ public void Delete_RemovesFromPendingAdditions()
+ {
+ var r1 = new Record { Id = "12" };
+ session.Add(r1);
+ session.Delete(r1);
+
+ Assert.That(session.Additions, Is.Empty);
+ Assert.That(session.Deletions.Single().ToString(), Is.EqualTo("+Id:12"));
+ }
+
+ [Test]
public void Delete_SetsPendingChangesFlag()
{
var r1 = new Record { Id = "12" };
@@ -197,8 +212,7 @@ public void Delete_SetsPendingChangesFlag()
Assert.That(session.PendingChanges, Is.True, "PendingChanges");
}
-
-
+
[Test]
public void Delete_ThrowsOnEmptyKey()
{
@@ -212,6 +226,62 @@ public void Delete_ThrowsOnEmptyKey()
Assert.That(call, Throws.InvalidOperationException);
}
+ [Test]
+ public void Query_Attaches()
+ {
+ var records = new Record[0].AsQueryable();
+ var provider = MockRepository.GenerateStrictMock<IQueryProvider>();
+ var queryable = MockRepository.GenerateStrictMock<IQueryable<Record>>();
+ queryable.Expect(q => q.Provider).Return(provider);
+ queryable.Expect(q => q.Expression).Return(Expression.Constant(records));
+ provider.Expect(p => p.CreateQuery<Record>(Arg<Expression>.Is.NotNull)).Return(records);
+ session = new LuceneSession<Record>(mapper, context, queryable);
+
+ session.Query();
+
+ queryable.VerifyAllExpectations();
+ provider.VerifyAllExpectations();
+ }
+
+ [Test]
+ public void PendingChanges_DirtyDocuments()
+ {
+ var record = new Record();
+ var copy = new Record();
+ mapper.Expect(m => m.Equals(record, copy)).Return(false);
+ mapper.Expect(m => m.ToDocument(Arg<Record>.Is.Same(record), Arg<Document>.Is.NotNull));
+ session.DocumentTracker.TrackDocument(record, copy);
+ record.Id = "1";
+
+ session.StageModifiedDocuments();
+
+ Assert.That(session.PendingChanges, Is.True, "Should detect modified document.");
+ }
+
+ [Test]
+ public void Dispose_Commits()
+ {
+ writer.Expect(w => w.DeleteAll());
+ writer.Expect(w => w.Commit());
+
+ session.DeleteAll();
+ session.Dispose();
+ }
+
+ [Test]
+ public void Commit_RollbackException_ThrowsAggregateException()
+ {
+ var ex1 = new Exception("ex1");
+ var ex2 = new Exception("ex2");
+ writer.Expect(w => w.DeleteAll()).Throw(ex1);
+ writer.Expect(w => w.Rollback()).Throw(ex2);
+
+ session.DeleteAll();
+
+ var thrown = Assert.Throws<AggregateException>(session.Commit);
+ Assert.That(thrown.InnerExceptions, Is.EquivalentTo(new[] {ex1, ex2}));
+ }
+
private void Verify()
{
mapper.VerifyAllExpectations();
View
34 Lucene.Net.Linq.Tests/Mapping/ReflectionDocumentMapperTests.cs
@@ -8,6 +8,9 @@ namespace Lucene.Net.Linq.Tests.Mapping
[TestFixture]
public class ReflectionDocumentMapperTests
{
+ private ReflectedDocument item1 = new ReflectedDocument { Id = "1", Version = new Version("1.2.3.4"), Location = "New York", Name = "Fun things", Number = 12 };
+ private ReflectedDocument item2 = new ReflectedDocument { Id = "1", Version = new Version("1.2.3.4"), Location = "New York", Name = "Fun things", Number = 12 };
+
[Test]
public void CtrFindsKeyFields()
{
@@ -50,6 +53,34 @@ public void ToKey_NotEqual()
Assert.That(key1, Is.Not.EqualTo(key2));
}
+ [Test]
+ public void Documents_Equal()
+ {
+ var mapper = new ReflectionDocumentMapper<ReflectedDocument>();
+
+ Assert.That(mapper.Equals(item1, item2), Is.True);
+ }
+
+ [Test]
+ public void Documents_Equal_IgnoredField()
+ {
+ var mapper = new ReflectionDocumentMapper<ReflectedDocument>();
+
+ item1.IgnoreMe = "different";
+
+ Assert.That(mapper.Equals(item1, item2), Is.True);
+ }
+
+ [Test]
+ public void Documents_Equal_Not()
+ {
+ var mapper = new ReflectionDocumentMapper<ReflectedDocument>();
+
+ item1.Version = new Version("5.6.7.8");
+
+ Assert.That(mapper.Equals(item1, item2), Is.False);
+ }
+
public class ReflectedDocument
{
[Field(Key = true)]
@@ -65,6 +96,9 @@ public class ReflectedDocument
[NumericField(Key = true)]
public int Number { get; set; }
+
+ [IgnoreField]
+ public string IgnoreMe { get; set; }
}
}
View
101 ...ts/Transformation/TreeVisitors/BooleanBinaryToQueryPredicateExpressionTreeVisitorTests.cs
@@ -0,0 +1,101 @@
+using System.Linq.Expressions;
+using Lucene.Net.Linq.Clauses.Expressions;
+using Lucene.Net.Linq.Search;
+using Lucene.Net.Linq.Transformation.TreeVisitors;
+using Lucene.Net.Search;
+using NUnit.Framework;
+
+namespace Lucene.Net.Linq.Tests.Transformation.TreeVisitors
+{
+ [TestFixture]
+ public class BooleanBinaryToQueryPredicateExpressionTreeVisitorTests
+ {
+ private BooleanBinaryToQueryPredicateExpressionTreeVisitor visitor;
+
+ // Query(Name:foo*)
+ private static readonly LuceneQueryPredicateExpression predicate = new LuceneQueryPredicateExpression(
+ new LuceneQueryFieldExpression(typeof (string), "Name"),
+ Expression.Constant("foo"),
+ BooleanClause.Occur.MUST,
+ QueryType.Prefix);
+
+ [SetUp]
+ public void SetUp()
+ {
+ visitor = new BooleanBinaryToQueryPredicateExpressionTreeVisitor();
+ }
+
+ [Test]
+ public void EqualFalse()
+ {
+ var call = CreateBinaryExpression(ExpressionType.Equal, false);
+
+ var result = visitor.VisitExpression(call) as LuceneQueryPredicateExpression;
+
+ AssertResult(result, BooleanClause.Occur.MUST_NOT);
+ }
+
+ [Test]
+ public void NotEqualTrue()
+ {
+ var call = CreateBinaryExpression(ExpressionType.NotEqual, true);
+
+ var result = visitor.VisitExpression(call) as LuceneQueryPredicateExpression;
+
+ AssertResult(result, BooleanClause.Occur.MUST_NOT);
+ }
+
+ [Test]
+ public void EqualTrue()
+ {
+ var call = CreateBinaryExpression(ExpressionType.Equal, true);
+
+ var result = visitor.VisitExpression(call);
+
+ Assert.That(result, Is.SameAs(predicate));
+ }
+
+ [Test]
+ public void NotEqualFalse()
+ {
+ var call = CreateBinaryExpression(ExpressionType.NotEqual, false);
+
+ var result = visitor.VisitExpression(call);
+
+ Assert.That(result, Is.SameAs(predicate));
+ }
+ [Test]
+ public void RetainsBoostAndAllowSpecialCharacters()
+ {
+ var call = CreateBinaryExpression(ExpressionType.Equal, false);
+
+
+ predicate.AllowSpecialCharacters = true;
+ predicate.Boost = 1234f;
+
+ var result = visitor.VisitExpression(call) as LuceneQueryPredicateExpression;
+
+ AssertResult(result, BooleanClause.Occur.MUST_NOT);
+ }
+
+ private static void AssertResult(LuceneQueryPredicateExpression result, BooleanClause.Occur expectedOccur)
+ {
+ Assert.That(result, Is.Not.Null, "Expected LuceneQueryPredicateExpression to be returned.");
+ Assert.That(result, Is.Not.SameAs(predicate));
+ Assert.That(result.QueryField, Is.SameAs(predicate.QueryField));
+ Assert.That(result.QueryPattern, Is.SameAs(predicate.QueryPattern));
+ Assert.That(result.QueryType, Is.EqualTo(predicate.QueryType));
+ Assert.That(result.Occur, Is.EqualTo(expectedOccur));
+ Assert.That(result.Boost, Is.EqualTo(predicate.Boost));
+ Assert.That(result.AllowSpecialCharacters, Is.EqualTo(predicate.AllowSpecialCharacters));
+ }
+
+ private static BinaryExpression CreateBinaryExpression(ExpressionType expressionType, bool value)
+ {
+ return Expression.MakeBinary(
+ expressionType,
+ predicate,
+ Expression.Constant(value));
+ }
+ }
+}
View
8 ...CallToBinaryExpressionTreeVisitorTests.cs → ...eryPredicateExpressionTreeVisitorTests.cs
@@ -8,18 +8,18 @@
namespace Lucene.Net.Linq.Tests.Transformation.TreeVisitors
{
[TestFixture]
- public class CompareCallToBinaryExpressionTreeVisitorTests
+ public class CompareCallToLuceneQueryPredicateExpressionTreeVisitorTests
{
- private CompareCallToBinaryExpressionTreeVisitor visitor;
- private readonly MethodInfo methodInfo = typeof(CompareCallToBinaryExpressionTreeVisitorTests).GetMethod("Compare", BindingFlags.Static | BindingFlags.Public);
+ private CompareCallToLuceneQueryPredicateExpressionTreeVisitor visitor;
+ private readonly MethodInfo methodInfo = typeof(CompareCallToLuceneQueryPredicateExpressionTreeVisitorTests).GetMethod("Compare", BindingFlags.Static | BindingFlags.Public);
private readonly Expression field = new LuceneQueryFieldExpression(typeof(string), "Name");
private readonly Expression constant = Expression.Constant("John");
[SetUp]
public void SetUp()
{
- visitor = new CompareCallToBinaryExpressionTreeVisitor();
+ visitor = new CompareCallToLuceneQueryPredicateExpressionTreeVisitor();
}
[Test]
View
18 Lucene.Net.Linq.Tests/Transformation/TreeVisitors/FlagToBinaryConditionTreeVisitorTests.cs
@@ -81,7 +81,7 @@ public void Inverse()
{
// "where !doc.SomeFlag"
var flag = new LuceneQueryFieldExpression(typeof(bool), "SomeFlag");
- var expression = Expression.MakeUnary(ExpressionType.Not, flag, typeof (bool));
+ var expression = Expression.MakeUnary(ExpressionType.Not, flag, typeof(bool));
var result = visitor.VisitExpression(expression) as BinaryExpression;
Assert.That(result, Is.Not.Null, "Expected BinaryExpression to be returned.");
@@ -91,6 +91,22 @@ public void Inverse()
}
[Test]
+ public void ConvertNestedMethodCall()
+ {
+ // where !doc.Name.StartsWith("foo")
+ var field = new LuceneQueryFieldExpression(typeof(string), "Name");
+ var startsWith = Expression.Call(field, "StartsWith", null, Expression.Constant("foo"));
+ var expression = Expression.MakeUnary(ExpressionType.Not, startsWith, typeof(bool));
+ var result = visitor.VisitExpression(expression) as BinaryExpression;
+
+ Assert.That(result, Is.Not.Null, "Expected BinaryExpression to be returned.");
+ Assert.That(result.Left, Is.SameAs(startsWith));
+ Assert.That(result.Right, Is.InstanceOf<ConstantExpression>());
+ Assert.That(result.NodeType, Is.EqualTo(ExpressionType.Equal));
+ Assert.That(((ConstantExpression)result.Right).Value, Is.EqualTo(false));
+ }
+
+ [Test]
public void DoubleInverse()
{
// "where !(!doc.SomeFlag)"
View
6 ...CallToBinaryExpressionTreeVisitorTests.cs → ...eryPredicateExpressionTreeVisitorTests.cs
@@ -7,16 +7,16 @@
namespace Lucene.Net.Linq.Tests.Transformation.TreeVisitors
{
[TestFixture]
- public class MethodCallToBinaryExpressionTreeVisitorTests
+ public class MethodCallToLuceneQueryPredicateExpressionTreeVisitorTests
{
- private MethodCallToBinaryExpressionTreeVisitor visitor;
+ private MethodCallToLuceneQueryPredicateExpressionTreeVisitor visitor;
private Expression field;
private ConstantExpression foo;
[SetUp]
public void SetUp()
{
- visitor = new MethodCallToBinaryExpressionTreeVisitor();
+ visitor = new MethodCallToLuceneQueryPredicateExpressionTreeVisitor();
field = new LuceneQueryFieldExpression(typeof (string), "firstName");
foo = Expression.Constant("foo");
}
View
54 ...e.Net.Linq.Tests/Transformation/TreeVisitors/NoOpConvertExpressionRemovingVisitorTests.cs
@@ -0,0 +1,54 @@
+using System.Linq.Expressions;
+using Lucene.Net.Linq.Transformation.TreeVisitors;
+using NUnit.Framework;
+
+namespace Lucene.Net.Linq.Tests.Transformation.TreeVisitors
+{
+ [TestFixture]
+ public class NoOpConvertExpressionRemovingVisitorTests
+ {
+ private NoOpConvertExpressionRemovingVisitor visitor;
+
+ [SetUp]
+ public void SetUp()
+ {
+ visitor = new NoOpConvertExpressionRemovingVisitor();
+ }
+
+ [Test]
+ public void ConvertNestedBinary()
+ {
+ var inner = Expression.MakeBinary(ExpressionType.Equal,
+ Expression.Convert(Expression.Constant((bool?)false), typeof(bool)),
+ Expression.Convert(Expression.Constant((bool?)false), typeof(bool)));
+
+ var outer = Expression.MakeBinary(ExpressionType.AndAlso,
+ Expression.Convert(Expression.Constant((bool?)false), typeof (bool)), inner);
+
+ var result = (BinaryExpression)visitor.VisitExpression(outer);
+
+ Assert.That(result.Left, Is.InstanceOf<ConstantExpression>());
+
+ var innerResult = (BinaryExpression)result.Right;
+
+ Assert.That(innerResult.Left, Is.InstanceOf<ConstantExpression>());
+ Assert.That(innerResult.Right, Is.InstanceOf<ConstantExpression>());
+ }
+
+ [Test]
+ public void ChangeTypeOfConstant()
+ {
+ var call = Expression.Call(Expression.Constant("hello"), "StartsWith", null, Expression.Constant("foo"));
+ var expression = Expression.MakeBinary(ExpressionType.Equal,
+ Expression.Convert(call, typeof(bool?)),
+ Expression.Convert(Expression.Constant(false), typeof(bool?)));
+
+ var result = (BinaryExpression)visitor.VisitExpression(expression);
+
+ Assert.That(result.Left, Is.SameAs(call));
+ Assert.That(result.Right, Is.InstanceOf<ConstantExpression>());
+ Assert.That(result.Right.Type, Is.EqualTo(typeof(bool)));
+ Assert.That(((ConstantExpression)result.Right).Value, Is.False);
+ }
+ }
+}
View
11 Lucene.Net.Linq.Tests/Translation/QueryModelTranslatorTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq.Expressions;
using Lucene.Net.Analysis;
+using Lucene.Net.Linq.Clauses;
using Lucene.Net.Linq.Clauses.Expressions;
using Lucene.Net.Linq.Mapping;
using Lucene.Net.Linq.Search;
@@ -150,7 +151,6 @@ public void ConvertsToSort_MultipleClauses()
mappingInfo.Expect(m => m.GetMappingInfo("Id")).Return(numericMappingInfo);
nonNumericMappingInfo.Stub(i => i.FieldName).Return("Name");
numericMappingInfo.Stub(i => i.FieldName).Return("Id");
-
var orderByClause = new OrderByClause();
orderByClause.Orderings.Add(new Ordering(new LuceneQueryFieldExpression(typeof(string), "Name"), OrderingDirection.Asc));
@@ -167,6 +167,15 @@ public void ConvertsToSort_MultipleClauses()
AssertSortFieldEquals(transformer.Model.Sort.GetSort()[1], "Id", OrderingDirection.Desc, SortField.LONG);
}
+ [Test]
+ public void SetsDocumentTracker()
+ {
+ var expr = Expression.Constant(this);
+ transformer.VisitTrackRetrievedDocumentsClause(new TrackRetrievedDocumentsClause(expr), queryModel, 0);
+
+ Assert.That(transformer.Model.DocumentTracker, Is.SameAs(expr.Value));
+ }
+
private void AssertSortFieldEquals(SortField sortField, string expectedFieldName, OrderingDirection expectedDirection, int expectedType)
{
Assert.That(sortField.GetField(), Is.EqualTo(expectedFieldName));
View
1 Lucene.Net.Linq.Tests/packages.config
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
+ <package id="Common.Logging" version="2.1.1" targetFramework="net40" />
<package id="Lucene.Net" version="2.9.4.1" />
<package id="NUnit" version="2.6.0.12054" />
<package id="RhinoMocks" version="3.6.1" />
View
31 Lucene.Net.Linq/Clauses/BoostClause.cs
@@ -1,46 +1,33 @@
-using System;
-using System.Linq.Expressions;
-using Lucene.Net.Linq.Translation;
+using System.Linq.Expressions;
using Remotion.Linq;
using Remotion.Linq.Clauses;
namespace Lucene.Net.Linq.Clauses
{
- internal class BoostClause : IBodyClause
+ internal class BoostClause : ExtensionClause<LambdaExpression>
{
- private LambdaExpression boostFunction;
-
- internal BoostClause(LambdaExpression boostFunction)
+ public BoostClause(LambdaExpression expression) : base(expression)
{
- this.boostFunction = boostFunction;
}
public LambdaExpression BoostFunction
{
- get { return boostFunction; }
+ get { return expression; }
}
- public void TransformExpressions(Func<Expression, Expression> transformation)
+ protected override void Accept(ILuceneQueryModelVisitor visitor, QueryModel queryModel, int index)
{
- boostFunction = transformation(boostFunction) as LambdaExpression;
- }
-
- public void Accept(IQueryModelVisitor visitor, QueryModel queryModel, int index)
- {
- var customVisitor = visitor as ILuceneQueryModelVisitor;
- if (customVisitor == null) return;
-
- customVisitor.VisitBoostClause(this, queryModel, index);
+ visitor.VisitBoostClause(this, queryModel, index);
}
- public IBodyClause Clone(CloneContext cloneContext)
+ public override IBodyClause Clone(CloneContext cloneContext)
{
- return new BoostClause(boostFunction);
+ return new BoostClause(expression);
}
public override string ToString()
{
- return "boost " + boostFunction;
+ return "boost " + expression;
}
}
}
View
7 Lucene.Net.Linq/Clauses/ExpressionNodes/BoostExpressionNode.cs
@@ -8,9 +8,10 @@ namespace Lucene.Net.Linq.Clauses.ExpressionNodes
internal class BoostExpressionNode : MethodCallExpressionNodeBase
{
public static readonly MethodInfo[] SupportedMethods = new[]
- {
- GetSupportedMethod (() => LuceneMethods.BoostInternal<object> (null, null))
- };
+ {
+ GetSupportedMethod (() => LuceneMethods.BoostInternal<object> (null, null))
+ };
+
private readonly LambdaExpression boostFunction;
public BoostExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression boostFunction) : base(parseInfo)
View
34 Lucene.Net.Linq/Clauses/ExpressionNodes/TrackRetrievedDocumentsExpressionNode.cs
@@ -0,0 +1,34 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using Remotion.Linq;
+using Remotion.Linq.Parsing.Structure.IntermediateModel;
+
+namespace Lucene.Net.Linq.Clauses.ExpressionNodes
+{
+ internal class TrackRetrievedDocumentsExpressionNode : MethodCallExpressionNodeBase
+ {
+ public static readonly MethodInfo[] SupportedMethods = new[]
+ {
+ GetSupportedMethod (() => LuceneMethods.TrackRetrievedDocuments<object>(null, null))
+ };
+
+ private readonly ConstantExpression tracker;
+
+ public TrackRetrievedDocumentsExpressionNode(MethodCallExpressionParseInfo parseInfo, ConstantExpression tracker)
+ : base(parseInfo)
+ {
+ this.tracker = tracker;
+ }
+
+ public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, ClauseGenerationContext clauseGenerationContext)
+ {
+ return Source.Resolve(inputParameter, expressionToBeResolved, clauseGenerationContext);
+ }
+
+ protected override QueryModel ApplyNodeSpecificSemantics(QueryModel queryModel, ClauseGenerationContext clauseGenerationContext)
+ {
+ queryModel.BodyClauses.Add(new TrackRetrievedDocumentsClause(tracker));
+ return queryModel;
+ }
+ }
+}
View
34 Lucene.Net.Linq/Clauses/ExtensionClause.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq.Expressions;
+using Remotion.Linq;
+using Remotion.Linq.Clauses;
+
+namespace Lucene.Net.Linq.Clauses
+{
+ internal abstract class ExtensionClause<T> : IBodyClause where T : Expression
+ {
+ protected T expression;
+
+ internal ExtensionClause(T expression)
+ {
+ this.expression = expression;
+ }
+
+ public void TransformExpressions(Func<Expression, Expression> transformation)
+ {
+ expression = transformation(expression) as T;
+ }
+
+ public void Accept(IQueryModelVisitor visitor, QueryModel queryModel, int index)
+ {
+ var customVisitor = visitor as ILuceneQueryModelVisitor;
+ if (customVisitor == null) return;
+
+ Accept(customVisitor, queryModel, index);
+ }
+
+ public abstract IBodyClause Clone(CloneContext cloneContext);
+
+ protected abstract void Accept(ILuceneQueryModelVisitor visitor, QueryModel queryModel, int index);
+ }
+}
View
29 Lucene.Net.Linq/Clauses/TrackRetrievedDocumentsClause.cs
@@ -0,0 +1,29 @@
+using System.Linq.Expressions;
+using Remotion.Linq;
+using Remotion.Linq.Clauses;
+
+namespace Lucene.Net.Linq.Clauses
+{
+ internal class TrackRetrievedDocumentsClause : ExtensionClause<ConstantExpression>
+ {
+ public TrackRetrievedDocumentsClause(ConstantExpression expression)
+ : base(expression)
+ {
+ }
+
+ public ConstantExpression Tracker
+ {
+ get { return expression; }
+ }
+
+ protected override void Accept(ILuceneQueryModelVisitor visitor, QueryModel queryModel, int index)
+ {
+ visitor.VisitTrackRetrievedDocumentsClause(this, queryModel, index);
+ }
+
+ public override IBodyClause Clone(CloneContext cloneContext)
+ {
+ return new TrackRetrievedDocumentsClause(expression);
+ }
+ }
+}
View
1 Lucene.Net.Linq/ILuceneQueryModelVisitor.cs
@@ -6,5 +6,6 @@ namespace Lucene.Net.Linq
internal interface ILuceneQueryModelVisitor : IQueryModelVisitor
{
void VisitBoostClause(BoostClause boostClause, QueryModel queryModel, int index);
+ void VisitTrackRetrievedDocumentsClause(TrackRetrievedDocumentsClause trackRetrievedDocumentsClause, QueryModel queryModel, int index);
}
}
View
7 Lucene.Net.Linq/IRetrievedDocumentTracker.cs
@@ -0,0 +1,7 @@
+namespace Lucene.Net.Linq
+{
+ internal interface IRetrievedDocumentTracker<in T>
+ {
+ void TrackDocument(T item, T hiddenCopy);
+ }
+}
View
4 Lucene.Net.Linq/ISession.cs
@@ -1,10 +1,12 @@
using System;
+using System.Linq;
using Lucene.Net.Search;
namespace Lucene.Net.Linq
{
- public interface ISession<in T> : IDisposable
+ public interface ISession<T> : IDisposable
{
+ IQueryable<T> Query();
void Add(params T[] items);
void Delete(params T[] items);
void Delete(params Query[] items);
View
14 Lucene.Net.Linq/Lucene.Net.Linq.csproj
@@ -34,6 +34,10 @@
<DocumentationFile>bin\Release\Lucene.Net.Linq.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="Common.Logging, Version=2.1.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Common.Logging.2.1.1\lib\net40\Common.Logging.dll</HintPath>
+ </Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
@@ -59,6 +63,7 @@
<Compile Include="Abstractions\IIndexWriter.cs" />
<Compile Include="Clauses\ExpressionNodes\BoostExpressionNode.cs" />
<Compile Include="Clauses\BoostClause.cs" />
+ <Compile Include="Clauses\ExpressionNodes\TrackRetrievedDocumentsExpressionNode.cs" />
<Compile Include="Clauses\Expressions\AllowSpecialCharactersExpression.cs" />
<Compile Include="Clauses\Expressions\BoostBinaryExpression.cs" />
<Compile Include="Clauses\Expressions\LuceneCompositeOrderingExpression.cs" />
@@ -68,9 +73,14 @@
<Compile Include="Clauses\Expressions\LuceneQueryExpression.cs" />
<Compile Include="Clauses\Expressions\LuceneQueryFieldExpression.cs" />
<Compile Include="Clauses\Expressions\LuceneQueryPredicateExpression.cs" />
+ <Compile Include="Clauses\ExtensionClause.cs" />
+ <Compile Include="Clauses\TrackRetrievedDocumentsClause.cs" />
<Compile Include="Clauses\TreeVisitors\LuceneExpressionTreeVisitor.cs" />
+ <Compile Include="IRetrievedDocumentTracker.cs" />
<Compile Include="Mapping\DocumentKey.cs" />
<Compile Include="RelinqQueryParserFactory.cs" />
+ <Compile Include="ScalarResultHandlers\ScalarResultHandler.cs" />
+ <Compile Include="ScalarResultHandlers\ScalarResultHandlerRegistry.cs" />
<Compile Include="Transformation\AllowSpecialCharactersExpressionTransformer.cs" />
<Compile Include="Context.cs" />
<Compile Include="Converters\DateTimeConverter.cs" />
@@ -96,17 +106,19 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Search\Function\DelegatingCustomScoreQuery.cs" />
<Compile Include="Transformation\TreeVisitors\AllowSpecialCharactersMethodExpressionTreeVisitor.cs" />
+ <Compile Include="Transformation\TreeVisitors\BooleanBinaryToQueryPredicateExpressionTreeVisitor.cs" />
<Compile Include="Transformation\TreeVisitors\BoostMethodCallTreeVisitor.cs" />
<Compile Include="Transformation\TreeVisitors\ExternallyProvidedQueryExpressionTreeVisitor.cs" />
<Compile Include="Transformation\TreeVisitors\LuceneExtensionMethodCallTreeVisitor.cs" />
<Compile Include="Transformation\TreeVisitors\MethodInfoMatchingTreeVisitor.cs" />
+ <Compile Include="Transformation\TreeVisitors\NoOpConvertExpressionRemovingVisitor.cs" />
<Compile Include="Transformation\TreeVisitors\SubQueryContainsTreeVisitor.cs" />
<Compile Include="ILuceneQueryModelVisitor.cs" />
<Compile Include="LuceneQueryModel.cs" />
<Compile Include="Translation\ResultOperatorHandlers\ResultOperatorHandler.cs" />
<Compile Include="Translation\ResultOperatorHandlers\ResultOperatorRegistry.cs" />
- <Compile Include="Translation\ResultOperatorHandlers\ResultOperatorHandlers.cs" />
<Compile Include="Util\AnalyzerExtensions.cs" />
+ <Compile Include="Translation\ResultOperatorHandlers\AggregateResultOperatorHandlers.cs" />
<Compile Include="Util\ExpressionExtensions.cs" />
<Compile Include="Transformation\TreeVisitors\NoOpConditionRemovingTreeVisitor.cs" />
<Compile Include="Translation\TreeVisitors\QueryBuildingExpressionTreeVisitor.cs" />
View
6 Lucene.Net.Linq/Lucene.Net.Linq.nuspec
@@ -14,6 +14,10 @@
<tags>lucene.net lucene linq odata search nosql</tags>
<summary>Provides LINQ IQueryable interface over a Lucene.Net index.</summary>
<description>Execute LINQ queries on Lucene.Net complete with object to Document mapping.</description>
- <releaseNotes>Fixes thread safety and reuse issues.</releaseNotes>
+ <releaseNotes>ISession implements unit-of-work pattern, automatically flushing modified documents.</releaseNotes>
+ <dependencies>
+ <dependency id="Remotion.Linq" version="[1.13.111, 2.0)" />
+ <dependency id="Common.Logging" version="[2.1.1, 3.0)" />
+ </dependencies>
</metadata>
</package>
View
25 Lucene.Net.Linq/LuceneDataProvider.cs
@@ -84,23 +84,38 @@ public IQueryable<T> AsQueryable<T>() where T : new()
/// <param name="factory">Factory method to instantiate new instances of T.</param>
public IQueryable<T> AsQueryable<T>(Func<T> factory)
{
- var executor = new LuceneQueryExecutor<T>(context, factory, new ReflectionDocumentMapper<T>());
- return new LuceneQueryable<T>(queryParser, executor);
+ return CreateQueryable(factory, new ReflectionDocumentMapper<T>());
+ }
+
+ /// <summary>
+ /// Opens a session for staging changes and then committing them atomically.
+ /// </summary>
+ /// <typeparam name="T">The type of object that will be mapped to <c cref="Document"/>.</typeparam>
+ public ISession<T> OpenSession<T>() where T : new()
+ {
+ return OpenSession(() => new T());
}
/// <summary>
/// Opens a session for staging changes and then committing them atomically.
/// </summary>
+ /// <param name="factory">Factory delegate that creates new instances of <typeparamref name="T"/></param>
/// <typeparam name="T">The type of object that will be mapped to <c cref="Document"/>.</typeparam>
- public ISession<T> OpenSession<T>()
+ public ISession<T> OpenSession<T>(Func<T> factory)
{
if (context.IsReadOnly)
{
throw new InvalidOperationException("This data provider is read-only. To enable writes, construct " + typeof(LuceneDataProvider) + " with an IndexWriter.");
}
var mapper = new ReflectionDocumentMapper<T>();
- return new LuceneSession<T>(mapper, context);
+ return new LuceneSession<T>(mapper, context, CreateQueryable(factory, mapper));
+ }
+
+ private LuceneQueryable<T> CreateQueryable<T>(Func<T> factory, IDocumentMapper<T> mapper)
+ {
+ var executor = new LuceneQueryExecutor<T>(context, factory, mapper);
+ return new LuceneQueryable<T>(queryParser, executor);
}
}
-}
+}
View
7 Lucene.Net.Linq/LuceneMethods.cs
@@ -53,6 +53,13 @@ internal static IQueryable<T> BoostInternal<T>(this IQueryable<T> source, Expres
source.Expression, boostFunction));
}
+ internal static IQueryable<T> TrackRetrievedDocuments<T>(this IQueryable<T> source, IRetrievedDocumentTracker<T> tracker)
+ {
+ return source.Provider.CreateQuery<T>(
+ Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(T)),
+ source.Expression, Expression.Constant(tracker)));
+ }
+
/// <summary>
/// Applies the provided Query. Enables queries to be constructed from outside of
/// LINQ to be executed as part of a LINQ query.
View
68 Lucene.Net.Linq/LuceneQueryExecutor.cs
@@ -2,17 +2,16 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
+using Common.Logging;
using Lucene.Net.Documents;
using Lucene.Net.Linq.Mapping;
+using Lucene.Net.Linq.ScalarResultHandlers;
using Lucene.Net.Linq.Search.Function;
using Lucene.Net.Linq.Transformation;
using Lucene.Net.Linq.Translation;
-using Lucene.Net.Linq.Util;
-using Lucene.Net.Search;
using Remotion.Linq;
using Remotion.Linq.Clauses;
using Remotion.Linq.Clauses.ExpressionTreeVisitors;
-using Remotion.Linq.Clauses.ResultOperators;
namespace Lucene.Net.Linq
{
@@ -55,6 +54,8 @@ protected override bool EnableScoreTracking
internal abstract class LuceneQueryExecutorBase<TDocument> : IQueryExecutor, IFieldMappingInfoProvider
{
+ private readonly ILog Log = LogManager.GetCurrentClassLogger();
+
private readonly Context context;
protected LuceneQueryExecutorBase(Context context)
@@ -76,10 +77,9 @@ public T ExecuteScalar<T>(QueryModel queryModel)
var hits = searcher.Search(luceneQueryModel.Query, null, maxResults, luceneQueryModel.Sort);
- var projection = GetScalarProjector<T>(luceneQueryModel.ResultSetOperator, hits);
- var projector = projection.Compile();
+ var handler = ScalarResultHandlerRegistry.Instance.GetItem(luceneQueryModel.ResultSetOperator.GetType());
- return projector(hits);
+ return handler.Execute<T>(hits);
}
}
@@ -103,7 +103,6 @@ public IEnumerable<T> ExecuteCollection<T>(QueryModel queryModel)
var luceneQueryModel = PrepareQuery(queryModel);
- // TODO: move this into QueryModelTransformer?
var mapping = new QuerySourceMapping();
mapping.AddMapping(queryModel.MainFromClause, currentItemExpression);
queryModel.TransformExpressions(e => ReferenceReplacingExpressionTreeVisitor.ReplaceClauseReferences(e, mapping, true));
@@ -139,9 +138,22 @@ public IEnumerable<T> ExecuteCollection<T>(QueryModel queryModel)
if (skipResults < 0) yield break;
}
+ var tracker = luceneQueryModel.DocumentTracker as IRetrievedDocumentTracker<TDocument>;
+
for (var i = skipResults; i < hits.ScoreDocs.Length; i++)
{
- itemHolder.Current = ConvertDocument(searcher.Doc(hits.ScoreDocs[i].doc), hits.ScoreDocs[i].score);
+ var doc = hits.ScoreDocs[i].doc;
+ var score = hits.ScoreDocs[i].score;
+
+ var item = ConvertDocument(searcher.Doc(doc), score);
+
+ if (tracker != null)
+ {
+ var copy = ConvertDocument(searcher.Doc(doc), score);
+ tracker.TrackDocument(item, copy);
+ }
+
+ itemHolder.Current = item;
yield return projector(itemHolder.Current);
}
}
@@ -154,50 +166,20 @@ private LuceneQueryModel PrepareQuery(QueryModel queryModel)
var builder = new QueryModelTranslator(context, this);
builder.Build(queryModel);
- Log.Trace(() => "Lucene query: " + builder.Model);
+ Log.Debug(m => m("Lucene query: {0}", builder.Model));
return builder.Model;
}
- public abstract IFieldMappingInfo GetMappingInfo(string propertyName);
- public abstract IEnumerable<string> AllFields { get; }
-
- protected abstract TDocument ConvertDocument(Document doc, float score);
- protected abstract bool EnableScoreTracking { get; }
-
protected virtual Expression<Func<TDocument, T>> GetProjector<T>(QueryModel queryModel)
{
return Expression.Lambda<Func<TDocument, T>>(queryModel.SelectClause.Selector, Expression.Parameter(typeof(TDocument)));
}
- protected virtual Expression<Func<TopFieldDocs, T>> GetScalarProjector<T>(ResultOperatorBase op, TopFieldDocs docs)
- {
- Expression call = Expression.Call(Expression.Constant(this), GetType().GetMethod("DoCount"), Expression.Constant(docs));
- if (op is LongCountResultOperator)
- {
- call = Expression.Convert(call, typeof(long));
- }
- else if (op is AnyResultOperator)
- {
- call = Expression.Call(Expression.Constant(this), GetType().GetMethod("DoAny"), Expression.Constant(docs));
- }
- else if (!(op is CountResultOperator))
- {
- //TODO: resultOperator.ExecuteInMemory() on unsupported ones.
- throw new NotSupportedException("The result operator type " + op.GetType() + " is not supported.");
- }
-
- return Expression.Lambda<Func<TopFieldDocs, T>>(call, Expression.Parameter(typeof(TopFieldDocs)));
- }
-
- public bool DoAny(TopFieldDocs d)
- {
- return d.TotalHits != 0;
- }
+ public abstract IFieldMappingInfo GetMappingInfo(string propertyName);
+ public abstract IEnumerable<string> AllFields { get; }
- public int DoCount(TopFieldDocs d)
- {
- return d.ScoreDocs.Length;
- }
+ protected abstract TDocument ConvertDocument(Document doc, float score);
+ protected abstract bool EnableScoreTracking { get; }
}
}
View
10 Lucene.Net.Linq/LuceneQueryModel.cs
@@ -43,7 +43,9 @@ public Sort Sort
public Expression SelectClause { get; set; }
public StreamedSequenceInfo OutputDataInfo { get; set; }
public ResultOperatorBase ResultSetOperator { get; private set; }
-
+
+ public object DocumentTracker { get; set; }
+
public void AddQuery(Query additionalQuery)
{
if (query == null)
@@ -161,17 +163,17 @@ public void AddBoostFunction(LambdaExpression expression)
{
if (customScoreFunction == null) return null;
- var invocationList = customScoreFunction.GetInvocationList();
+ var invocationList = customScoreFunction.GetInvocationList().Cast<Func<TDocument, float>>().ToArray();
if (invocationList.Length == 1)
{
- return (Func<TDocument, float>)customScoreFunction;
+ return invocationList[0];
}
return delegate(TDocument document)
{
var score = 1.0f;
- foreach (Func<TDocument, float> func in invocationList)
+ foreach (var func in invocationList)
{
score = score * func(document);
}
View
153 Lucene.Net.Linq/LuceneSession.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Common.Logging;
using Lucene.Net.Documents;
using Lucene.Net.Linq.Mapping;
using Lucene.Net.Linq.Util;
@@ -10,40 +11,44 @@ namespace Lucene.Net.Linq
{
internal class LuceneSession<T> : ISession<T>
{
+ private readonly ILog Log = LogManager.GetCurrentClassLogger();
+
private readonly object sessionLock = new object();
private readonly IDocumentMapper<T> mapper;
private readonly Context context;
-
- private readonly IDictionary<DocumentKey, Document> additions = new Dictionary<DocumentKey, Document>();
+ private readonly IQueryable<T> queryable;
+
+ private readonly List<T> additions = new List<T>();
private readonly List<Query> deleteQueries = new List<Query>();
private readonly ISet<DocumentKey> deleteKeys = new HashSet<DocumentKey>();
- public LuceneSession(IDocumentMapper<T> mapper, Context context)
+ private readonly SessionDocumentTracker documentTracker;
+
+ public LuceneSession(IDocumentMapper<T> mapper, Context context, IQueryable<T> queryable)
{
this.mapper = mapper;
this.context = context;
+ this.queryable = queryable;
+ documentTracker = new SessionDocumentTracker(mapper);
}
- public void Add(params T[] items)
+ public IQueryable<T> Query()
{
- lock (sessionLock)
- {
- foreach (var item in items)
- {
- var key = mapper.ToKey(item);
- var doc = new Document();
+ return queryable.TrackRetrievedDocuments(documentTracker);
+ }
- mapper.ToDocument(item, doc);
-
- additions[key] = doc;
- }
- }
+ public void Add(params T[] items)
+ {
+ Add((IEnumerable<T>) items);
}
- internal void Add(DocumentKey key, Document doc)
+ public void Add(IEnumerable<T> items)
{
- additions[key] = doc;
+ lock (sessionLock)
+ {
+ additions.AddRange(items);
+ }
}
public void Delete(params T[] items)
@@ -52,6 +57,7 @@ public void Delete(params T[] items)
{
foreach (var item in items)
{
+ additions.Remove(item);
var key = mapper.ToKey(item);
if (key.Empty)
{
@@ -77,10 +83,17 @@ public void DeleteAll()
}
}
+ public void Dispose()
+ {
+ Commit();
+ }
+
public void Commit()
{
lock (sessionLock)
{
+ StageModifiedDocuments();
+
if (!PendingChanges)
{
return;
@@ -92,24 +105,40 @@ public void Commit()
{
CommitInternal();
}
- catch (OutOfMemoryException)
+ catch (OutOfMemoryException ex)
{
- context.IndexWriter.Dispose();
+ Log.Error(m => m("OutOfMemoryException while writing/committing to Lucene index. Closing writer."), ex);
+ try
+ {
+ context.IndexWriter.Dispose();
+ }
+ catch (Exception ex2)
+ {
+ Log.Error(m => m("Exception in IndexWriter.Dispose"), ex2);
+ throw new AggregateException(ex, ex2);
+ }
+
throw;
}
- catch (Exception)
+ catch (Exception ex)
{
- context.IndexWriter.Rollback();
+ Log.Error(m => m("Exception in commit"), ex);
+ try
+ {
+ context.IndexWriter.Rollback();
+ }
+ catch (Exception ex2)
+ {
+ Log.Error(m => m("Exception in rollback"), ex2);
+ throw new AggregateException(ex, ex2);
+ }
+
throw;
}
}
}
}
- public void Dispose()
- {
- }
-
private void CommitInternal()
{
var writer = context.IndexWriter;
@@ -124,16 +153,18 @@ private void CommitInternal()
deletes = Deletions;
}
- deletes = deletes.Union(additions.Keys.Where(k => !k.Empty).Select(k => k.ToQuery(context.Analyzer, context.Version)));
+ var additionMap = ConvertPendingAdditions();
+
+ deletes = deletes.Union(additionMap.Keys.Where(k => !k.Empty).Select(k => k.ToQuery(context.Analyzer, context.Version)));
if (deletes.Any())
{
writer.DeleteDocuments(deletes.Distinct().ToArray());
}
- if (additions.Count > 0)
+ if (additionMap.Count > 0)
{
- additions.Values.Apply(writer.AddDocument);
+ additionMap.Values.Apply(writer.AddDocument);
}
writer.Commit();
@@ -143,6 +174,16 @@ private void CommitInternal()
context.Reload();
}
+ internal void StageModifiedDocuments()
+ {
+ var docs = documentTracker.FindModifiedDocuments();
+ foreach (var doc in docs)
+ {
+ Log.Debug(m => m("Flushing modified document " + doc));
+ Add(doc);
+ }
+ }
+
private void ClearPendingChanges()
{
DeleteAllFlag = false;
@@ -156,9 +197,61 @@ internal bool PendingChanges
get { return DeleteAllFlag || additions.Count > 0 || deleteQueries.Count > 0 || deleteKeys.Count > 0; }
}
- internal bool DeleteAllFlag { get; private set; }
+ internal IRetrievedDocumentTracker<T> DocumentTracker { get { return documentTracker; } }
- internal IEnumerable<Document> Additions { get { return new List<Document>(additions.Values); } }
+ internal bool DeleteAllFlag { get; private set; }
+
internal IEnumerable<Query> Deletions { get { return deleteQueries.Union(deleteKeys.Select(k => k.ToQuery(context.Analyzer, context.Version))); } }
+
+ internal List<T> Additions
+ {
+ get { return additions; }
+ }
+
+ internal IDictionary<DocumentKey, Document> ConvertPendingAdditions()
+ {
+ var map = new Dictionary<DocumentKey, Document>();
+ var reverse = new List<T>(additions);
+ reverse.Reverse();
+
+ foreach (var item in reverse)
+ {
+ var key = mapper.ToKey(item);
+ if (!map.ContainsKey(key))
+ {
+ map[key] = ToDocument(item);
+ }
+ }
+
+ return map;
+ }
+
+ private Document ToDocument(T i)
+ {
+ var doc = new Document();
+ mapper.ToDocument(i, doc);
+ return doc;
+ }
+
+ internal class SessionDocumentTracker : IRetrievedDocumentTracker<T>
+ {
+ private readonly IDocumentMapper<T> mapper;
+ private readonly IList<Tuple<T, T>> items = new List<Tuple<T, T>>();
+
+ public SessionDocumentTracker(IDocumentMapper<T> mapper)
+ {
+ this.mapper = mapper;
+ }
+
+ public void TrackDocument(T item, T hiddenCopy)
+ {
+ items.Add(new Tuple<T, T>(item, hiddenCopy));
+ }
+
+ public IEnumerable<T> FindModifiedDocuments()
+ {
+ return items.Where(t => !mapper.Equals(t.Item1, t.Item2)).Select(t => t.Item1);
+ }
+ }
}
}
View
14 Lucene.Net.Linq/Mapping/ReflectionDocumentMapper.cs
@@ -18,6 +18,7 @@ internal interface IDocumentMapper<in T> : IFieldMappingInfoProvider
void ToObject(Document source, float score, T target);
void ToDocument(T source, Document target);
DocumentKey ToKey(T source);
+ bool Equals(T item1, T item2);
bool EnableScoreTracking { get; }
}
@@ -94,5 +95,18 @@ public List<IFieldMapper<T>> KeyFields
{
get { return new List<IFieldMapper<T>>(keyFields); }
}
+
+ public bool Equals(T item1, T item2)
+ {
+ foreach (var field in fieldMap.Values)
+ {
+ var val1 = field.PropertyInfo.GetValue(item1, null);
+ var val2 = field.PropertyInfo.GetValue(item2, null);
+
+ if (!Equals(val1, val2)) return false;
+ }
+
+ return true;
+ }
}
}
View
45 Lucene.Net.Linq/ScalarResultHandlers/ScalarResultHandler.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using Lucene.Net.Search;
+using Remotion.Linq.Clauses.ResultOperators;
+
+namespace Lucene.Net.Linq.ScalarResultHandlers
+{
+ internal abstract class ScalarResultHandler
+ {
+ public abstract IEnumerable<Type> SupportedTypes { get; }
+
+ public T Execute<T>(TopFieldDocs hits)
+ {
+ return (T) Convert.ChangeType(Execute(hits), typeof (T));
+ }
+
+ protected abstract object Execute(TopFieldDocs hits);
+ }
+
+ internal class CountResultHandler : ScalarResultHandler
+ {
+ public override IEnumerable<Type> SupportedTypes
+ {
+ get { return new[] {typeof (CountResultOperator), typeof (LongCountResultOperator)}; }
+ }
+
+ protected override object Execute(TopFieldDocs hits)
+ {
+ return hits.ScoreDocs.Length;
+ }
+ }
+
+ internal class AnyResultHandler : ScalarResultHandler
+ {
+ public override IEnumerable<Type> SupportedTypes
+ {
+ get { return new[] { typeof(AnyResultOperator) }; }
+ }
+
+ protected override object Execute(TopFieldDocs hits)
+ {
+ return hits.ScoreDocs.Length > 0;
+ }
+ }
+}
View
33 Lucene.Net.Linq/ScalarResultHandlers/ScalarResultHandlerRegistry.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using Lucene.Net.Linq.Util;
+using Remotion.Linq.Utilities;
+
+namespace Lucene.Net.Linq.ScalarResultHandlers
+{
+ internal class ScalarResultHandlerRegistry : RegistryBase<ScalarResultHandlerRegistry, Type, ScalarResultHandler>
+ {
+ private static readonly ScalarResultHandlerRegistry instance = ScalarResultHandlerRegistry.CreateDefault();
+
+ public static ScalarResultHandlerRegistry Instance
+ {
+ get { return instance; }
+ }
+
+ protected override void RegisterForTypes(IEnumerable<Type> itemTypes)
+ {
+ itemTypes.Apply(RegisterForType);
+ }
+
+ private void RegisterForType(Type type)
+ {
+ var handler = (ScalarResultHandler)Activator.CreateInstance(type);
+ Register(handler.SupportedTypes, handler);
+ }
+
+ public override ScalarResultHandler GetItem(Type key)
+ {
+ return GetItemExact(key);
+ }
+ }
+}
View
19 Lucene.Net.Linq/Transformation/QueryModelTransformer.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Common.Logging;
using Lucene.Net.Linq.Clauses.Expressions;
using Lucene.Net.Linq.Transformation.TreeVisitors;
using Lucene.Net.Linq.Util;
@@ -13,6 +14,8 @@ namespace Lucene.Net.Linq.Transformation
/// </summary>
internal class QueryModelTransformer : QueryModelVisitorBase
{
+ private static readonly ILog Log = LogManager.GetLogger<QueryModelTransformer>();
+
private readonly IEnumerable<ExpressionTreeVisitor> whereSelectClauseVisitors;
private readonly IEnumerable<ExpressionTreeVisitor> orderingVisitors;
@@ -24,12 +27,14 @@ internal QueryModelTransformer()
new ExternallyProvidedQueryExpressionTreeVisitor(),
new QuerySourceReferencePropertyTransformingTreeVisitor(),
new BoostMethodCallTreeVisitor(0),
- new FlagToBinaryConditionTreeVisitor(),
new NoOpMethodCallRemovingTreeVisitor(),
new NoOpConditionRemovingTreeVisitor(),
- new MethodCallToBinaryExpressionTreeVisitor(),
+ new MethodCallToLuceneQueryPredicateExpressionTreeVisitor(),
new NullSafetyConditionRemovingTreeVisitor(),
- new CompareCallToBinaryExpressionTreeVisitor(),
+ new CompareCallToLuceneQueryPredicateExpressionTreeVisitor(),
+ new NoOpConvertExpressionRemovingVisitor(),
+ new FlagToBinaryConditionTreeVisitor(),
+ new BooleanBinaryToQueryPredicateExpressionTreeVisitor(),
new BinaryToQueryExpressionTreeVisitor(),
new AllowSpecialCharactersMethodExpressionTreeVisitor(),
new BoostMethodCallTreeVisitor(1)
@@ -62,25 +67,25 @@ public static void TransformQueryModel(QueryModel queryModel)
public override void VisitWhereClause(WhereClause whereClause, QueryModel queryModel, int index)
{
- Log.Trace(() => "Original QueryModel: " + queryModel);
+ Log.Trace(m => m("Original QueryModel: {0}", queryModel));
foreach (var visitor in whereSelectClauseVisitors)
{
whereClause.TransformExpressions(visitor.VisitExpression);
- Log.Trace(() => "Transformed QueryModel after " + visitor.GetType().Name + ": " + queryModel);
+ Log.Trace(m => m("Transformed QueryModel after {0}: {1}", visitor.GetType().Name, queryModel));
}
base.VisitWhereClause(whereClause, queryModel, index);
}
public override void VisitOrderByClause(OrderByClause orderByClause, QueryModel queryModel, int index)
{
- Log.Trace(() => "Original QueryModel: " + queryModel);
+ Log.Trace(m => m("Original QueryModel: {0}", queryModel));
foreach (var visitor in orderingVisitors)
{
orderByClause.TransformExpressions(visitor.VisitExpression);
- Log.Trace(() => "Transformed QueryModel after " + visitor.GetType().Name + ": " + queryModel);
+ Log.Trace(m => m("Transformed QueryModel after {0}: {1}", visitor.GetType().Name, queryModel));
}
ExpandCompositeOrderings(orderByClause);
View
35 ...et.Linq/Transformation/TreeVisitors/BooleanBinaryToQueryPredicateExpressionTreeVisitor.cs
@@ -0,0 +1,35 @@
+using System.Linq.Expressions;
+using Lucene.Net.Linq.Clauses.Expressions;
+using Lucene.Net.Linq.Util;
+using Lucene.Net.Search;
+using Remotion.Linq.Parsing;
+
+namespace Lucene.Net.Linq.Transformation.TreeVisitors
+{
+ /// <summary>
+ /// Replaces boolean binary expressions like <c>[LuceneQueryPredicateExpression](+field:query) == false</c> to <c>[LuceneQueryPredicateExpression](-field:query)</c>
+ /// </summary>
+ internal class BooleanBinaryToQueryPredicateExpressionTreeVisitor : ExpressionTreeVisitor
+ {
+ protected override Expression VisitBinaryExpression(BinaryExpression expression)
+ {
+ var predicate = expression.Left as LuceneQueryPredicateExpression;
+
+ var constant = expression.Right.IsTrueConstant();
+
+ if (predicate == null || !(constant || expression.Right.IsFalseConstant()))
+ {
+ return base.VisitBinaryExpression(expression);
+ }
+
+ if ((expression.NodeType == ExpressionType.Equal && constant) ||
+ (expression.NodeType == ExpressionType.NotEqual && !constant))
+ {
+ return predicate;
+ }
+
+ return new LuceneQueryPredicateExpression(predicate.QueryField, predicate.QueryPattern, BooleanClause.Occur.MUST_NOT, predicate.QueryType)
+ { Boost = predicate.Boost, AllowSpecialCharacters = predicate.AllowSpecialCharacters };
+ }
+ }
+}
View
4 Lucene.Net.Linq/Transformation/TreeVisitors/CompareCallToBinaryExpressionTreeVisitor.cs
@@ -9,9 +9,9 @@
namespace Lucene.Net.Linq.Transformation.TreeVisitors
{
/// <summary>
- /// Replaces supported method calls like Compare([LuceneQueryFieldExpression], "abc") to LuceneQueryPredicateExpression
+ /// Replaces supported method calls like <c>string.Compare([LuceneQueryFieldExpression], "abc") > 0</c> to LuceneQueryPredicateExpression
/// </summary>
- internal class CompareCallToBinaryExpressionTreeVisitor : ExpressionTreeVisitor
+ internal class CompareCallToLuceneQueryPredicateExpressionTreeVisitor : ExpressionTreeVisitor
{
private static readonly ISet<ExpressionType> compareTypes =
new HashSet<ExpressionType>
View
24 Lucene.Net.Linq/Transformation/TreeVisitors/FlagToBinaryConditionTreeVisitor.cs
@@ -1,11 +1,10 @@
using System.Linq.Expressions;
using Lucene.Net.Linq.Clauses.Expressions;
-using Remotion.Linq.Clauses.Expressions;
-using Remotion.Linq.Parsing;
+using Lucene.Net.Linq.Clauses.TreeVisitors;
namespace Lucene.Net.Linq.Transformation.TreeVisitors
{
- internal class FlagToBinaryConditionTreeVisitor : ExpressionTreeVisitor
+ internal class FlagToBinaryConditionTreeVisitor : LuceneExpressionTreeVisitor
{
private Expression parent;
private bool negate;
@@ -35,23 +34,26 @@ protected override Expression VisitUnaryExpression(UnaryExpression expression)
negate = !negate;
- var result = VisitExpression(expression.Operand);
+ var operand = VisitExpression(expression.Operand);
negate = !negate;
- return result;
+ if (Equals(operand, expression.Operand))
+ {
+ return Expression.MakeBinary(ExpressionType.Equal, operand, Expression.Constant(negate));
+ }
+
+ return operand;
}
- protected override Expression VisitExtensionExpression(ExtensionExpression expression)
+ protected override Expression VisitLuceneQueryFieldExpression(LuceneQueryFieldExpression expression)
{
- var field = expression as LuceneQueryFieldExpression;
-
- if (field == null || field.Type != typeof(bool) || IsAlreadyInEqualityExpression())
+ if (expression.Type != typeof(bool) || IsAlreadyInEqualityExpression())
{
- return base.VisitExtensionExpression(expression);
+ return base.VisitLuceneQueryFieldExpression(expression);
}
- return Expression.MakeBinary(ExpressionType.Equal, field, Expression.Constant(!negate));
+ return Expression.MakeBinary(ExpressionType.Equal, expression, Expression.Constant(!negate));
}
private bool IsAlreadyInEqualityExpression()