Пример #1
0
        private static void AddQueryNotEqual(QueryHandle queryHandle, string columnName, object value, Type columnType)
        {
            var columnKey = queryHandle.GetColumnKey(columnName);

            switch (value)
            {
            case null:
                queryHandle.NullNotEqual(columnKey);
                break;

            case string stringValue:
                queryHandle.StringNotEqual(columnKey, stringValue, caseSensitive: true);
                break;

            case bool boolValue:
                queryHandle.BoolNotEqual(columnKey, boolValue);
                break;

            case DateTimeOffset date:
                queryHandle.TimestampTicksNotEqual(columnKey, date);
                break;

            case byte[] buffer:
                if (buffer.Length == 0)
                {
                    // see RealmObject.SetByteArrayValue
                    queryHandle.BinaryNotEqual(columnKey, (IntPtr)0x1, IntPtr.Zero);
                    return;
                }

                unsafe
                {
                    fixed(byte *bufferPtr = (byte[])value)
                    {
                        queryHandle.BinaryNotEqual(columnKey, (IntPtr)bufferPtr, (IntPtr)buffer.Length);
                    }
                }

                break;

            case RealmObject obj:
                queryHandle.Not();
                queryHandle.ObjectEqual(columnKey, obj.ObjectHandle);
                break;

            default:
                // The other types aren't handled by the switch because of potential compiler applied conversions
                AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericNotEqualMethods);
                break;
            }
        }
Пример #2
0
        private static void AddQueryGreaterThanOrEqual(QueryHandle queryHandle, string columnName, object value, Type columnType)
        {
            var columnKey = queryHandle.GetColumnKey(columnName);

            switch (value)
            {
            case DateTimeOffset date:
                queryHandle.TimestampTicksGreaterEqual(columnKey, date);
                break;

            case string _:
            case bool _:
                throw new Exception($"Unsupported type {value.GetType().Name}");

            default:
                // The other types aren't handled by the switch because of potential compiler applied conversions
                AddQueryForConvertibleTypes(columnKey, value, columnType, queryHandle.NumericGreaterEqualMethods);
                break;
            }
        }
Пример #3
0
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.DeclaringType == typeof(Queryable))
            {
                if (node.Method.Name == nameof(Queryable.Where))
                {
                    Visit(node.Arguments[0]);
                    var lambda = (LambdaExpression)StripQuotes(node.Arguments[1]);
                    Visit(lambda.Body);
                    return(node);
                }

                if (node.Method.Name == nameof(Queryable.OrderBy) || node.Method.Name == nameof(Queryable.ThenBy))
                {
                    Visit(node.Arguments[0]);
                    AddSort((LambdaExpression)StripQuotes(node.Arguments[1]), true);
                    return(node);
                }

                if (node.Method.Name == nameof(Queryable.OrderByDescending) || node.Method.Name == nameof(Queryable.ThenByDescending))
                {
                    Visit(node.Arguments[0]);
                    AddSort((LambdaExpression)StripQuotes(node.Arguments[1]), false);
                    return(node);
                }

                if (node.Method.Name == nameof(Queryable.Count))
                {
                    RecurseToWhereOrRunLambda(node);
                    var foundCount = _coreQueryHandle.Count();
                    return(Expression.Constant(foundCount));
                }

                if (node.Method.Name == nameof(Queryable.Any))
                {
                    RecurseToWhereOrRunLambda(node);
                    return(Expression.Constant(_coreQueryHandle.Count() > 0));
                }

                if (node.Method.Name.StartsWith(nameof(Queryable.First)))
                {
                    RecurseToWhereOrRunLambda(node);
                    using (var rh = MakeResultsForQuery())
                    {
                        if (rh.TryGetObjectAtIndex(0, out var firstObject))
                        {
                            return(Expression.Constant(_realm.MakeObject(_metadata, firstObject)));
                        }
                        else if (node.Method.Name == nameof(Queryable.First))
                        {
                            throw new InvalidOperationException("Sequence contains no matching element");
                        }
                        else
                        {
                            Debug.Assert(node.Method.Name == nameof(Queryable.FirstOrDefault), $"The method {node.Method.Name}  is not supported. We expected {nameof(Queryable.FirstOrDefault)}.");
                            return(Expression.Constant(null));
                        }
                    }
                }

                /*
                 * // FIXME: See discussion in the test DefaultIfEmptyReturnsDefault
                 * // kept because it shows part of what might be a viable implementation if can work out architectural issues
                 *
                 *              if (m.Method.Name == nameof(Queryable.DefaultIfEmpty))
                 *              {
                 *                  RecurseToWhereOrRunLambda(m);
                 *                  IntPtr firstObjectPtr = _coreQueryHandle.FindDirect(IntPtr.Zero);
                 *                  if (firstObjectPtr != IntPtr.Zero)
                 *                      return m;  // as if just a "Where"
                 *                  var innerType = m.Type.GetGenericArguments()[0];
                 *                  var listType = typeof(List<>).MakeGenericType(innerType);
                 *                  var singleNullItemList = Activator.CreateInstance(listType);
                 *                  ((IList)singleNullItemList).Add(null);
                 *                  return Expression.Constant(singleNullItemList);
                 *              }
                 */

                // same as unsorted First with extra checks
                if (node.Method.Name.StartsWith(nameof(Queryable.Single)))
                {
                    RecurseToWhereOrRunLambda(node);
                    using (var rh = MakeResultsForQuery())
                    {
                        var count = rh.Count();
                        if (count == 0)
                        {
                            if (node.Method.Name == nameof(Queryable.Single))
                            {
                                throw new InvalidOperationException("Sequence contains no matching element");
                            }

                            Debug.Assert(node.Method.Name == nameof(Queryable.SingleOrDefault), $"The method {node.Method.Name}  is not supported. We expected {nameof(Queryable.SingleOrDefault)}.");
                            return(Expression.Constant(null));
                        }
                        else if (count > 1)
                        {
                            throw new InvalidOperationException("Sequence contains more than one matching element");
                        }
                        else
                        {
                            rh.TryGetObjectAtIndex(0, out var firstObject);
                            return(Expression.Constant(_realm.MakeObject(_metadata, firstObject)));
                        }
                    }
                }

                if (node.Method.Name.StartsWith(nameof(Queryable.Last)))
                {
                    RecurseToWhereOrRunLambda(node);

                    ObjectHandle lastObject = null;
                    using (var rh = MakeResultsForQuery())
                    {
                        var lastIndex = rh.Count() - 1;
                        if (lastIndex >= 0)
                        {
                            rh.TryGetObjectAtIndex(lastIndex, out lastObject);
                        }
                    }

                    if (lastObject != null)
                    {
                        return(Expression.Constant(_realm.MakeObject(_metadata, lastObject)));
                    }

                    if (node.Method.Name == nameof(Queryable.Last))
                    {
                        throw new InvalidOperationException("Sequence contains no matching element");
                    }

                    Debug.Assert(node.Method.Name == nameof(Queryable.LastOrDefault), $"The method {node.Method.Name}  is not supported. We expected {nameof(Queryable.LastOrDefault)}.");
                    return(Expression.Constant(null));
                }

                if (node.Method.Name.StartsWith(nameof(Queryable.ElementAt)))
                {
                    Visit(node.Arguments.First());
                    if (!TryExtractConstantValue(node.Arguments.Last(), out object argument) || argument.GetType() != typeof(int))
                    {
                        throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a single integer constant argument or closure variable");
                    }

                    ObjectHandle objectHandle;
                    var          index = (int)argument;
                    using (var rh = MakeResultsForQuery())
                    {
                        rh.TryGetObjectAtIndex(index, out objectHandle);
                    }

                    if (objectHandle != null)
                    {
                        return(Expression.Constant(_realm.MakeObject(_metadata, objectHandle)));
                    }

                    if (node.Method.Name == nameof(Queryable.ElementAt))
                    {
                        throw new ArgumentOutOfRangeException("index");
                    }

                    Debug.Assert(node.Method.Name == nameof(Queryable.ElementAtOrDefault), $"The method {node.Method.Name}  is not supported. We expected {nameof(Queryable.ElementAtOrDefault)}.");
                    return(Expression.Constant(null));
                }
            }

            if (node.Method.DeclaringType == typeof(string) ||
                node.Method.DeclaringType == typeof(StringExtensions))
            {
                QueryHandle.Operation <string> queryMethod = null;

                // For extension methods, member should be m.Arguments[0] as MemberExpression;
                MemberExpression member = null;

                // For extension methods, that should be 1
                var stringArgumentIndex = 0;

                if (AreMethodsSame(node.Method, Methods.String.Contains.Value))
                {
                    queryMethod = (q, c, v) => q.StringContains(c, v, caseSensitive: true);
                }
                else if (IsStringContainsWithComparison(node.Method, out var index))
                {
                    member = node.Arguments[0] as MemberExpression;
                    stringArgumentIndex = index;
                    queryMethod         = (q, c, v) => q.StringContains(c, v, GetComparisonCaseSensitive(node));
                }
                else if (AreMethodsSame(node.Method, Methods.String.StartsWith.Value))
                {
                    queryMethod = (q, c, v) => q.StringStartsWith(c, v, caseSensitive: true);
                }
                else if (AreMethodsSame(node.Method, Methods.String.StartsWithStringComparison.Value))
                {
                    queryMethod = (q, c, v) => q.StringStartsWith(c, v, GetComparisonCaseSensitive(node));
                }
                else if (AreMethodsSame(node.Method, Methods.String.EndsWith.Value))
                {
                    queryMethod = (q, c, v) => q.StringEndsWith(c, v, caseSensitive: true);
                }
                else if (AreMethodsSame(node.Method, Methods.String.EndsWithStringComparison.Value))
                {
                    queryMethod = (q, c, v) => q.StringEndsWith(c, v, GetComparisonCaseSensitive(node));
                }
                else if (AreMethodsSame(node.Method, Methods.String.IsNullOrEmpty.Value))
                {
                    member = node.Arguments.SingleOrDefault() as MemberExpression;
                    if (member == null)
                    {
                        throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a RealmObject member");
                    }

                    var columnName = GetColumnName(member, node.NodeType);
                    var columnKey  = _coreQueryHandle.GetColumnKey(columnName);

                    _coreQueryHandle.GroupBegin();
                    _coreQueryHandle.NullEqual(columnKey);
                    _coreQueryHandle.Or();
                    _coreQueryHandle.StringEqual(columnKey, string.Empty, caseSensitive: true);
                    _coreQueryHandle.GroupEnd();
                    return(node);
                }
                else if (AreMethodsSame(node.Method, Methods.String.EqualsMethod.Value))
                {
                    queryMethod = (q, c, v) => q.StringEqual(c, v, caseSensitive: true);
                }
                else if (AreMethodsSame(node.Method, Methods.String.EqualsStringComparison.Value))
                {
                    queryMethod = (q, c, v) => q.StringEqual(c, v, GetComparisonCaseSensitive(node));
                }
                else if (AreMethodsSame(node.Method, Methods.String.Like.Value))
                {
                    member = node.Arguments[0] as MemberExpression;
                    stringArgumentIndex = 1;
                    if (!TryExtractConstantValue(node.Arguments.Last(), out object caseSensitive) || !(caseSensitive is bool))
                    {
                        throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a string and boolean constant arguments.");
                    }

                    queryMethod = (q, c, v) => q.StringLike(c, v, (bool)caseSensitive);
                }

                if (queryMethod != null)
                {
                    member = member ?? node.Object as MemberExpression;

                    if (member == null)
                    {
                        throw new NotSupportedException($"The method '{node.Method}' has to be invoked on a RealmObject member");
                    }

                    var columnName = GetColumnName(member, node.NodeType);
                    var columnKey  = _coreQueryHandle.GetColumnKey(columnName);

                    if (!TryExtractConstantValue(node.Arguments[stringArgumentIndex], out object argument) ||
                        (argument != null && argument.GetType() != typeof(string)))
                    {
                        throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a single string constant argument or closure variable");
                    }

                    queryMethod(_coreQueryHandle, columnKey, (string)argument);
                    return(node);
                }
            }

            throw new NotSupportedException($"The method '{node.Method.Name}' is not supported");
        }