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; } }
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; } }
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"); }