From 7ee7bb8aa3c94a4def34923f13a4e8324dde81fd Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Tue, 14 Apr 2015 21:50:40 -0600 Subject: [PATCH 1/4] First attempt at fixing arbitrary member access issue --- .../Integration/MultiObjectTests.cs | 15 ++++++ .../AnonymousTypeDatumConverterFactory.cs | 2 +- rethinkdb-net/Expressions/BaseExpression.cs | 47 +++++++++++++++++-- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/rethinkdb-net-test/Integration/MultiObjectTests.cs b/rethinkdb-net-test/Integration/MultiObjectTests.cs index a1cf4fa..d9739fc 100644 --- a/rethinkdb-net-test/Integration/MultiObjectTests.cs +++ b/rethinkdb-net-test/Integration/MultiObjectTests.cs @@ -1133,5 +1133,20 @@ public void FilterByRegexpMatches() } count.Should().Be(1); } + + [Test] + public void FilterByRegexpGroup() + { + int count = 0; + foreach (var row in connection.Run(testTable.Filter(o => + o.Name.Match("^([3])$") != null && + o.Name.Match("^([3])$").Groups[0].MatchedString == "3" + ))) + { + row.Name.Should().Be("3"); + count += 1; + } + count.Should().Be(1); + } } } diff --git a/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs b/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs index bdec881..c275176 100644 --- a/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs +++ b/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs @@ -30,7 +30,7 @@ public override bool TryGet(IDatumConverterFactory rootDatumConverterFactory, public bool IsTypeSupported(Type t) { - if (t.IsClass && t.Name.Contains("Anon") && t.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length == 1) + if (t.IsClass && t.Name.Contains("Anon") && t.Name.StartsWith("<>") && t.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length == 1) return true; return false; } diff --git a/rethinkdb-net/Expressions/BaseExpression.cs b/rethinkdb-net/Expressions/BaseExpression.cs index 961532c..4c0fb2f 100644 --- a/rethinkdb-net/Expressions/BaseExpression.cs +++ b/rethinkdb-net/Expressions/BaseExpression.cs @@ -167,8 +167,45 @@ protected Term SimpleMap(IDatumConverterFactory datumConverterFactory, Expressio DefaultExpressionConverterFactory.ExpressionMappingDelegate memberAccessMapping; if (expressionConverterFactory.TryGetMemberAccessMapping(member, out memberAccessMapping)) return memberAccessMapping(memberExpression, RecursiveMap, datumConverterFactory, expressionConverterFactory); - else - return AttemptClientSideConversion(datumConverterFactory, expr); + + // Check if this is a member access on an object, rather than a static member access. If so, + // see if we can use the IObjectDatumConverter interface to access a field on it. + if (memberExpression.Expression != null) + { + // If we even can get a datum converter for this type... + IDatumConverter datumConverter; + if (datumConverterFactory.TryGet(memberExpression.Expression.Type, out datumConverter)) + { + // And if it implements IObjectDatumConverter + var fieldConverter = datumConverter as IObjectDatumConverter; + if (fieldConverter != null) + { + var datumFieldName = fieldConverter.GetDatumFieldName(memberExpression.Member); + if (string.IsNullOrEmpty(datumFieldName)) + throw new NotSupportedException(String.Format("Member {0} on type {1} could not be mapped to a datum field", memberExpression.Member.Name, memberExpression.Type)); + + Console.WriteLine("Expr being mapped to object access: {0}", expr); + + var getAttrTerm = new Term() + { + type = Term.TermType.GET_FIELD + }; + getAttrTerm.args.Add(RecursiveMap(memberExpression.Expression)); + getAttrTerm.args.Add(new Term() + { + type = Term.TermType.DATUM, + datum = new Datum() + { + type = Datum.DatumType.R_STR, + r_str = datumFieldName + } + }); + return getAttrTerm; + } + } + } + + return AttemptClientSideConversion(datumConverterFactory, expr); } case ExpressionType.Conditional: @@ -205,7 +242,11 @@ private Term AttemptClientSideConversion(IDatumConverterFactory datumConverterFa } catch (InvalidOperationException ex) { - throw new InvalidOperationException("Failed to perform client-side evaluation of expression tree node; often this is caused by refering to a server-side variable in a node that is only supported w/ client-side evaluation", ex); + throw new InvalidOperationException( + String.Format( + "Failed to perform client-side evaluation of expression tree node '{0}'; this is caused by refering to a server-side variable in an expression tree that isn't convertible to ReQL logic", + expr), + ex); } } From bb63398eb5de35804cd8b272fc22890e8eac07b4 Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Wed, 15 Apr 2015 07:34:25 -0600 Subject: [PATCH 2/4] Don't use server-side field access on an ExpressionType.Constant Removes the hacky fix in AnonymousTypeDatumConverterFactory and also works for the Newtonsoft converter equally well. --- .../AnonymousTypeDatumConverterFactory.cs | 2 +- rethinkdb-net/Expressions/BaseExpression.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs b/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs index c275176..bdec881 100644 --- a/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs +++ b/rethinkdb-net/DatumConverters/AnonymousTypeDatumConverterFactory.cs @@ -30,7 +30,7 @@ public override bool TryGet(IDatumConverterFactory rootDatumConverterFactory, public bool IsTypeSupported(Type t) { - if (t.IsClass && t.Name.Contains("Anon") && t.Name.StartsWith("<>") && t.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length == 1) + if (t.IsClass && t.Name.Contains("Anon") && t.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length == 1) return true; return false; } diff --git a/rethinkdb-net/Expressions/BaseExpression.cs b/rethinkdb-net/Expressions/BaseExpression.cs index 4c0fb2f..3453ff3 100644 --- a/rethinkdb-net/Expressions/BaseExpression.cs +++ b/rethinkdb-net/Expressions/BaseExpression.cs @@ -168,9 +168,12 @@ protected Term SimpleMap(IDatumConverterFactory datumConverterFactory, Expressio if (expressionConverterFactory.TryGetMemberAccessMapping(member, out memberAccessMapping)) return memberAccessMapping(memberExpression, RecursiveMap, datumConverterFactory, expressionConverterFactory); - // Check if this is a member access on an object, rather than a static member access. If so, - // see if we can use the IObjectDatumConverter interface to access a field on it. - if (memberExpression.Expression != null) + // Check if this is a member access on an object, rather than a static member access. It also has to be + // a member access on something other than a constant; if it's accessing a member on a constant, than client-side + // evaluation is actually more efficient since we won't need to transfer it to the server to evaluate something + // that we can do locally. + // If it matches those conditions, see if we can use the IObjectDatumConverter interface to access a field on it. + if (memberExpression.Expression != null && memberExpression.Expression.NodeType != ExpressionType.Constant) { // If we even can get a datum converter for this type... IDatumConverter datumConverter; @@ -183,8 +186,6 @@ protected Term SimpleMap(IDatumConverterFactory datumConverterFactory, Expressio var datumFieldName = fieldConverter.GetDatumFieldName(memberExpression.Member); if (string.IsNullOrEmpty(datumFieldName)) throw new NotSupportedException(String.Format("Member {0} on type {1} could not be mapped to a datum field", memberExpression.Member.Name, memberExpression.Type)); - - Console.WriteLine("Expr being mapped to object access: {0}", expr); var getAttrTerm = new Term() { From ee974ea01b7e8e8627a5fc2c82048e9d43222d4b Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Wed, 15 Apr 2015 08:11:12 -0600 Subject: [PATCH 3/4] Cleanup duplicate MemberAccess code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that BaseExpression supports MemberAccess on any type, the single/two parameter lambda converters don’t have to have duplicate code doing the same thing. They still need a special case to handle Convert nodes on the parameter object. GenericTypeConstraintExpressionTests required a change; accessing a member on the interface here doesn’t seem right because we don’t know how to treat that interface. So, I supplied the correct datum converter factory for that interface. Code makes more sense now, and tests pass. For the original reported issue #169 it should be functionally equivalent as that issue was using the newtonsoft converter which presumably would work on the interface as well. --- .../GenericTypeConstraintExpressionTests.cs | 40 +++++- rethinkdb-net/Expressions/BaseExpression.cs | 86 +++++++------ .../Expressions/SingleParameterLambda.cs | 87 +++---------- .../Expressions/TwoParameterLambda.cs | 121 +++--------------- 4 files changed, 124 insertions(+), 210 deletions(-) diff --git a/rethinkdb-net-test/Expressions/GenericTypeConstraintExpressionTests.cs b/rethinkdb-net-test/Expressions/GenericTypeConstraintExpressionTests.cs index 8052b62..fc833c9 100644 --- a/rethinkdb-net-test/Expressions/GenericTypeConstraintExpressionTests.cs +++ b/rethinkdb-net-test/Expressions/GenericTypeConstraintExpressionTests.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System; +using System.Runtime.Serialization; using NUnit.Framework; using FluentAssertions; using RethinkDb.DatumConverters; @@ -28,12 +29,47 @@ public void TestFixtureSetUp() { datumConverterFactory = new AggregateDatumConverterFactory( PrimitiveDatumConverterFactory.Instance, - DataContractDatumConverterFactory.Instance + DataContractDatumConverterFactory.Instance, + new TestInterfaceDatumConverterFactory() ); expressionConverterFactory = new RethinkDb.Expressions.DefaultExpressionConverterFactory(); queryConverter = new QueryConverter(datumConverterFactory, expressionConverterFactory); } + private class TestInterfaceDatumConverterFactory : AbstractDatumConverterFactory + { + public override bool TryGet(IDatumConverterFactory rootDatumConverterFactory, out IDatumConverter datumConverter) + { + datumConverter = null; + if (rootDatumConverterFactory == null) + throw new ArgumentNullException("rootDatumConverterFactory"); + + if (typeof(T) != typeof(ITestInterface)) + return false; + + datumConverter = (IDatumConverter)new TestInterfaceDatumConverter(); + return true; + } + } + + private class TestInterfaceDatumConverter : AbstractReferenceTypeDatumConverter, IObjectDatumConverter + { + public override ITestInterface ConvertDatum(Datum datum) + { + throw new System.NotImplementedException(); + } + + public override Datum ConvertObject(ITestInterface value) + { + throw new System.NotImplementedException(); + } + + public string GetDatumFieldName(System.Reflection.MemberInfo memberInfo) + { + return "number"; + } + } + private static void AssertFunctionIsGetFieldSomeNumberSingleParameter(Term expr) { var funcTerm = diff --git a/rethinkdb-net/Expressions/BaseExpression.cs b/rethinkdb-net/Expressions/BaseExpression.cs index 3453ff3..39f8aef 100644 --- a/rethinkdb-net/Expressions/BaseExpression.cs +++ b/rethinkdb-net/Expressions/BaseExpression.cs @@ -168,43 +168,9 @@ protected Term SimpleMap(IDatumConverterFactory datumConverterFactory, Expressio if (expressionConverterFactory.TryGetMemberAccessMapping(member, out memberAccessMapping)) return memberAccessMapping(memberExpression, RecursiveMap, datumConverterFactory, expressionConverterFactory); - // Check if this is a member access on an object, rather than a static member access. It also has to be - // a member access on something other than a constant; if it's accessing a member on a constant, than client-side - // evaluation is actually more efficient since we won't need to transfer it to the server to evaluate something - // that we can do locally. - // If it matches those conditions, see if we can use the IObjectDatumConverter interface to access a field on it. - if (memberExpression.Expression != null && memberExpression.Expression.NodeType != ExpressionType.Constant) - { - // If we even can get a datum converter for this type... - IDatumConverter datumConverter; - if (datumConverterFactory.TryGet(memberExpression.Expression.Type, out datumConverter)) - { - // And if it implements IObjectDatumConverter - var fieldConverter = datumConverter as IObjectDatumConverter; - if (fieldConverter != null) - { - var datumFieldName = fieldConverter.GetDatumFieldName(memberExpression.Member); - if (string.IsNullOrEmpty(datumFieldName)) - throw new NotSupportedException(String.Format("Member {0} on type {1} could not be mapped to a datum field", memberExpression.Member.Name, memberExpression.Type)); - - var getAttrTerm = new Term() - { - type = Term.TermType.GET_FIELD - }; - getAttrTerm.args.Add(RecursiveMap(memberExpression.Expression)); - getAttrTerm.args.Add(new Term() - { - type = Term.TermType.DATUM, - datum = new Datum() - { - type = Datum.DatumType.R_STR, - r_str = datumFieldName - } - }); - return getAttrTerm; - } - } - } + Term serverSideTerm; + if (ServerSideMemberAccess(datumConverterFactory, memberExpression, out serverSideTerm)) + return serverSideTerm; return AttemptClientSideConversion(datumConverterFactory, expr); } @@ -230,6 +196,52 @@ protected Term SimpleMap(IDatumConverterFactory datumConverterFactory, Expressio } } + private bool ServerSideMemberAccess(IDatumConverterFactory datumConverterFactory, MemberExpression memberExpression, out Term term) + { + term = null; + + if (memberExpression.Expression == null) + // static member access; can't do that server-side, wouldn't want to either. + return false; + + if (memberExpression.Expression.NodeType == ExpressionType.Constant) + // Accessing a constant; client-side conversion will be more efficient since we won't be round-tripping the object + return false; + + IDatumConverter datumConverter; + if (!datumConverterFactory.TryGet(memberExpression.Expression.Type, out datumConverter)) + // No datum converter for this type. + return false; + + var fieldConverter = datumConverter as IObjectDatumConverter; + if (fieldConverter == null) + // doesn't implement IObjectDatumConverter, so we don't know how to map the MemberInfo to the field name + return false; + + var datumFieldName = fieldConverter.GetDatumFieldName(memberExpression.Member); + if (string.IsNullOrEmpty(datumFieldName)) + // At this point we're not returning false because we're expecting this should work; throwing an error instead. + throw new NotSupportedException(String.Format("Member {0} on type {1} could not be mapped to a datum field", memberExpression.Member.Name, memberExpression.Type)); + + var getAttrTerm = new Term() + { + type = Term.TermType.GET_FIELD + }; + getAttrTerm.args.Add(RecursiveMap(memberExpression.Expression)); + getAttrTerm.args.Add(new Term() + { + type = Term.TermType.DATUM, + datum = new Datum() + { + type = Datum.DatumType.R_STR, + r_str = datumFieldName + } + }); + + term = getAttrTerm; + return true; + } + private Term AttemptClientSideConversion(IDatumConverterFactory datumConverterFactory, Expression expr) { try diff --git a/rethinkdb-net/Expressions/SingleParameterLambda.cs b/rethinkdb-net/Expressions/SingleParameterLambda.cs index c04b439..51ac65e 100644 --- a/rethinkdb-net/Expressions/SingleParameterLambda.cs +++ b/rethinkdb-net/Expressions/SingleParameterLambda.cs @@ -118,79 +118,29 @@ private Term MapExpressionToTerm(Expression expr) }; } - case ExpressionType.MemberAccess: + case ExpressionType.Convert: { - var memberExpr = (MemberExpression)expr; - var member = memberExpr.Member; - - if (memberExpr.Expression == null) - { - return SimpleMap(datumConverterFactory, expr); - } - else if (memberExpr.Expression.NodeType == ExpressionType.Convert) - { - // In some cases the CLR can insert a type-cast when a generic type constrant is present on a - // generic type that's a parameter. We pretty much just ignore those casts. It might be - // valid to use the cast to switch to a different datum converter?, but the use-case isn't - // really clear right now. We do check that the type-cast makes sense for the parameter type, - // but it's just to feel safer; it seems like the compiler should've made sure about that. - - var convertExpression = (UnaryExpression)memberExpr.Expression; - if (convertExpression.Operand.NodeType != ExpressionType.Parameter) - return SimpleMap(datumConverterFactory, expr); - - var parameterExpression = (ParameterExpression)convertExpression.Operand; - if (!convertExpression.Type.IsAssignableFrom(parameterExpression.Type)) - throw new NotSupportedException(String.Format( - "Cast on parameter expression not currently supported (from type {0} to type {1})", - parameterExpression.Type, convertExpression.Type)); - } - else if (memberExpr.Expression.NodeType != ExpressionType.Parameter) - { + // In some cases the CLR can insert a type-cast when a generic type constrant is present on a + // generic type that's a parameter. We pretty much just ignore those casts. It might be + // valid to use the cast to switch to a different datum converter?, but the use-case isn't + // really clear right now. We do check that the type-cast makes sense for the parameter type, + // but it's just to feel safer; it seems like the compiler should've made sure about that. + + var convertExpression = (UnaryExpression)expr; + if (convertExpression.Operand.NodeType != ExpressionType.Parameter) + // If this isn't a cast on a Parameter, just drop it through to do continue processing. return SimpleMap(datumConverterFactory, expr); - } - DefaultExpressionConverterFactory.ExpressionMappingDelegate memberAccessMapping; - if (expressionConverterFactory.TryGetMemberAccessMapping(member, out memberAccessMapping)) - return memberAccessMapping(memberExpr, RecursiveMap, datumConverterFactory, expressionConverterFactory); + // Otherwise; type-check it, and then just strip the Convert node out and recursivemap the inside. + var parameterExpr = (ParameterExpression)convertExpression.Operand; + if (!convertExpression.Type.IsAssignableFrom(parameterExpr.Type)) + throw new NotSupportedException(String.Format( + "Cast on parameter expression not currently supported (from type {0} to type {1})", + parameterExpr.Type, convertExpression.Type)); - var getAttrTerm = new Term() { - type = Term.TermType.GET_FIELD - }; - - getAttrTerm.args.Add(new Term() { - type = Term.TermType.VAR, - args = { - new Term() { - type = Term.TermType.DATUM, - datum = new Datum() { - type = Datum.DatumType.R_NUM, - r_num = 2 - }, - } - } - }); - - var datumConverter = datumConverterFactory.Get(); - var fieldConverter = datumConverter as IObjectDatumConverter; - if (fieldConverter == null) - throw new NotSupportedException("Cannot map member access into ReQL without implementing IObjectDatumConverter"); - - var datumFieldName = fieldConverter.GetDatumFieldName(memberExpr.Member); - if (string.IsNullOrEmpty(datumFieldName)) - throw new NotSupportedException(String.Format("Member {0} on type {1} could not be mapped to a datum field", memberExpr.Member.Name, memberExpr.Type)); - - getAttrTerm.args.Add(new Term() { - type = Term.TermType.DATUM, - datum = new Datum() { - type = Datum.DatumType.R_STR, - r_str = datumFieldName - } - }); - - return getAttrTerm; + return RecursiveMap(parameterExpr); } - + default: return SimpleMap(datumConverterFactory, expr); } @@ -199,4 +149,3 @@ private Term MapExpressionToTerm(Expression expr) #endregion } } - diff --git a/rethinkdb-net/Expressions/TwoParameterLambda.cs b/rethinkdb-net/Expressions/TwoParameterLambda.cs index fc2c1eb..6069022 100644 --- a/rethinkdb-net/Expressions/TwoParameterLambda.cs +++ b/rethinkdb-net/Expressions/TwoParameterLambda.cs @@ -136,113 +136,30 @@ private Term MapExpressionToTerm(Expression expr) } }; } - - case ExpressionType.MemberAccess: + + case ExpressionType.Convert: { - var memberExpr = (MemberExpression)expr; - var member = memberExpr.Member; - ParameterExpression parameterExpr = null; - - if (memberExpr.Expression == null) - { - return SimpleMap(datumConverterFactory, expr); - } - else if (memberExpr.Expression.NodeType == ExpressionType.Convert) - { - // In some cases the CLR can insert a type-cast when a generic type constrant is present on a - // generic type that's a parameter. We pretty much just ignore those casts. It might be - // valid to use the cast to switch to a different datum converter?, but the use-case isn't - // really clear right now. We do check that the type-cast makes sense for the parameter type, - // but it's just to feel safer; it seems like the compiler should've made sure about that. - - var convertExpression = (UnaryExpression)memberExpr.Expression; - if (convertExpression.Operand.NodeType != ExpressionType.Parameter) - return SimpleMap(datumConverterFactory, expr); - - parameterExpr = (ParameterExpression)convertExpression.Operand; - if (!convertExpression.Type.IsAssignableFrom(parameterExpr.Type)) - throw new NotSupportedException(String.Format( - "Cast on parameter expression not currently supported (from type {0} to type {1})", - parameterExpr.Type, convertExpression.Type)); - } - else if (memberExpr.Expression.NodeType != ExpressionType.Parameter) - { + // In some cases the CLR can insert a type-cast when a generic type constrant is present on a + // generic type that's a parameter. We pretty much just ignore those casts. It might be + // valid to use the cast to switch to a different datum converter?, but the use-case isn't + // really clear right now. We do check that the type-cast makes sense for the parameter type, + // but it's just to feel safer; it seems like the compiler should've made sure about that. + + var convertExpression = (UnaryExpression)expr; + if (convertExpression.Operand.NodeType != ExpressionType.Parameter) + // If this isn't a cast on a Parameter, just drop it through to do continue processing. return SimpleMap(datumConverterFactory, expr); - } - - DefaultExpressionConverterFactory.ExpressionMappingDelegate memberAccessMapping; - if (expressionConverterFactory.TryGetMemberAccessMapping(member, out memberAccessMapping)) - return memberAccessMapping(memberExpr, RecursiveMap, datumConverterFactory, expressionConverterFactory); - - if (parameterExpr == null) - parameterExpr = (ParameterExpression)memberExpr.Expression; - int parameterIndex; - if (parameterExpr.Name == parameter1Name) - parameterIndex = 3; - else if (parameterExpr.Name == parameter2Name) - parameterIndex = 4; - else - throw new InvalidOperationException("Unmatched parameter name:" + parameterExpr.Name); - - var getAttrTerm = new Term() { - type = Term.TermType.GET_FIELD - }; - - getAttrTerm.args.Add(new Term() { - type = Term.TermType.VAR, - args = { - new Term() { - type = Term.TermType.DATUM, - datum = new Datum() { - type = Datum.DatumType.R_NUM, - r_num = parameterIndex - }, - } - } - }); - - if (parameterIndex == 3) - { - var datumConverter = datumConverterFactory.Get(); - var fieldConverter = datumConverter as IObjectDatumConverter; - if (fieldConverter == null) - throw new NotSupportedException("Cannot map member access into ReQL without implementing IObjectDatumConverter"); - - var datumFieldName = fieldConverter.GetDatumFieldName(memberExpr.Member); - if (string.IsNullOrEmpty(datumFieldName)) - throw new NotSupportedException(String.Format("Member {0} on type {1} could not be mapped to a datum field", memberExpr.Member.Name, memberExpr.Type)); - - getAttrTerm.args.Add(new Term() { - type = Term.TermType.DATUM, - datum = new Datum() { - type = Datum.DatumType.R_STR, - r_str = datumFieldName - } - }); - } - else if (parameterIndex == 4) - { - var datumConverter = datumConverterFactory.Get(); - var fieldConverter = datumConverter as IObjectDatumConverter; - if (fieldConverter == null) - throw new NotSupportedException("Cannot map member access into ReQL without implementing IObjectDatumConverter"); - - var datumFieldName = fieldConverter.GetDatumFieldName(memberExpr.Member); - if (string.IsNullOrEmpty(datumFieldName)) - throw new NotSupportedException(String.Format("Member {0} on type {1} could not be mapped to a datum field", memberExpr.Member.Name, memberExpr.Type)); - getAttrTerm.args.Add(new Term() { - type = Term.TermType.DATUM, - datum = new Datum() { - type = Datum.DatumType.R_STR, - r_str = datumFieldName - } - }); - } + // Otherwise; type-check it, and then just strip the Convert node out and recursivemap the inside. + var parameterExpr = (ParameterExpression)convertExpression.Operand; + if (!convertExpression.Type.IsAssignableFrom(parameterExpr.Type)) + throw new NotSupportedException(String.Format( + "Cast on parameter expression not currently supported (from type {0} to type {1})", + parameterExpr.Type, convertExpression.Type)); - return getAttrTerm; + return RecursiveMap(parameterExpr); } - + default: return SimpleMap(datumConverterFactory, expr); } From 56e04b794a8d35d804a6b4af971028615d6f6fb4 Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Wed, 15 Apr 2015 08:14:58 -0600 Subject: [PATCH 4/4] Release notes for #209 --- RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9a5e251..f194959 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -6,6 +6,8 @@ * Supports RethinkDB's regular expression matching of strings, eg. ```table.Filter(o => o.Email != null && o.Email.Match(".*@(.*)") != null)```. [Issue #107](https://github.com/mfenniak/rethinkdb-net/issues/107) & [PR #208](https://github.com/mfenniak/rethinkdb-net/pull/208) +* Expressions can now reference member variables of server-side calculated data, eg. ```table.Filter(o => o.Email.Match(".*@(.*)").Groups[0].MatchedString == "google.com")``` would filter for all records with an e-mail address that has a domain of "google.com". [PR #209](https://github.com/mfenniak/rethinkdb-net/pull/209) + ## 0.10.0.0 (2015-04-14)