private static void AddQueryGreaterThanOrEqual(QueryHandle queryHandle, string columnName, object value)
        {
            var columnIndex = NativeQuery.get_column_index((QueryHandle)queryHandle, columnName, (IntPtr)columnName.Length);

            var valueType = value.GetType();

            if (valueType == typeof(int))
            {
                NativeQuery.int_greater_equal((QueryHandle)queryHandle, columnIndex, (IntPtr)((int)value));
            }
            else if (valueType == typeof(float))
            {
                NativeQuery.float_greater_equal((QueryHandle)queryHandle, columnIndex, (float)value);
            }
            else if (valueType == typeof(double))
            {
                NativeQuery.double_greater_equal((QueryHandle)queryHandle, columnIndex, (double)value);
            }
            else if (valueType == typeof(DateTimeOffset))
            {
                NativeQuery.datetime_seconds_greater_equal(queryHandle, columnIndex, ((DateTimeOffset)value).ToUnixTimeSeconds());
            }
            else if (valueType == typeof(string) || valueType == typeof(bool))
            {
                throw new Exception("Unsupported type " + valueType.Name);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        private static void AddQueryLessThan(QueryHandle queryHandle, string columnName, object value)
        {
            var columnIndex = NativeQuery.get_column_index((QueryHandle)queryHandle, columnName, (IntPtr)columnName.Length);

            var valueType = value.GetType();

            if (valueType == typeof(int))
            {
                NativeQuery.int_less((QueryHandle)queryHandle, columnIndex, (IntPtr)((int)value));
            }
            else if (valueType == typeof(long))
            {
                NativeQuery.long_less((QueryHandle)queryHandle, columnIndex, (long)value);
            }
            else if (valueType == typeof(float))
            {
                NativeQuery.float_less((QueryHandle)queryHandle, columnIndex, (float)value);
            }
            else if (valueType == typeof(double))
            {
                NativeQuery.double_less((QueryHandle)queryHandle, columnIndex, (double)value);
            }
            else if (valueType == typeof(DateTimeOffset))
            {
                NativeQuery.timestamp_milliseconds_less(queryHandle, columnIndex, ((DateTimeOffset)value).ToRealmUnixTimeMilliseconds());
            }
            else if (valueType == typeof(string) || valueType == typeof(bool))
            {
                throw new Exception("Unsupported type " + valueType.Name);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
 protected void VisitCombination(BinaryExpression b, Action <QueryHandle> combineWith)
 {
     NativeQuery.group_begin(_coreQueryHandle);
     Visit(b.Left);
     combineWith(_coreQueryHandle);
     Visit(b.Right);
     NativeQuery.group_end(_coreQueryHandle);
 }
        internal override Expression VisitBinary(BinaryExpression b)
        {
            if (b.NodeType == ExpressionType.AndAlso)  // Boolean And with short-circuit
            {
                VisitCombination(b, (qh) => { /* noop -- AND is the default combinator */ });
            }
            else if (b.NodeType == ExpressionType.OrElse)  // Boolean Or with short-circuit
            {
                VisitCombination(b, qh => NativeQuery.or(qh));
            }
            else
            {
                var leftMember = b.Left as MemberExpression;
                if (leftMember == null)
                {
                    throw new NotSupportedException(
                              $"The lhs of the binary operator '{b.NodeType}' should be a member expression");
                }
                var leftName = leftMember.Member.Name;

                var rightValue = ExtractConstantValue(b.Right);
                if (rightValue == null)
                {
                    throw new NotSupportedException($"The rhs of the binary operator '{b.NodeType}' should be a constant or closure variable expression");
                }

                switch (b.NodeType)
                {
                case ExpressionType.Equal:
                    AddQueryEqual(_coreQueryHandle, leftName, rightValue);
                    break;

                case ExpressionType.NotEqual:
                    AddQueryNotEqual(_coreQueryHandle, leftName, rightValue);
                    break;

                case ExpressionType.LessThan:
                    AddQueryLessThan(_coreQueryHandle, leftName, rightValue);
                    break;

                case ExpressionType.LessThanOrEqual:
                    AddQueryLessThanOrEqual(_coreQueryHandle, leftName, rightValue);
                    break;

                case ExpressionType.GreaterThan:
                    AddQueryGreaterThan(_coreQueryHandle, leftName, rightValue);
                    break;

                case ExpressionType.GreaterThanOrEqual:
                    AddQueryGreaterThanOrEqual(_coreQueryHandle, leftName, rightValue);
                    break;

                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
                }
            }
            return(b);
        }
        private static void AddQueryNotEqual(QueryHandle queryHandle, string columnName, object value)
        {
            var columnIndex = NativeQuery.get_column_index((QueryHandle)queryHandle, columnName, (IntPtr)columnName.Length);

            var valueType = value.GetType();

            if (value.GetType() == typeof(string))
            {
                string valueStr = (string)value;
                NativeQuery.string_not_equal((QueryHandle)queryHandle, columnIndex, valueStr, (IntPtr)valueStr.Length);
            }
            else if (valueType == typeof(bool))
            {
                NativeQuery.bool_not_equal((QueryHandle)queryHandle, columnIndex, MarshalHelpers.BoolToIntPtr((bool)value));
            }
            else if (valueType == typeof(int))
            {
                NativeQuery.int_not_equal((QueryHandle)queryHandle, columnIndex, (IntPtr)((int)value));
            }
            else if (valueType == typeof(long))
            {
                NativeQuery.long_not_equal((QueryHandle)queryHandle, columnIndex, (long)value);
            }
            else if (valueType == typeof(float))
            {
                NativeQuery.float_not_equal((QueryHandle)queryHandle, columnIndex, (float)value);
            }
            else if (valueType == typeof(double))
            {
                NativeQuery.double_not_equal((QueryHandle)queryHandle, columnIndex, (double)value);
            }
            else if (valueType == typeof(DateTimeOffset))
            {
                NativeQuery.timestamp_milliseconds_not_equal(queryHandle, columnIndex, ((DateTimeOffset)value).ToRealmUnixTimeMilliseconds());
            }
            else if (valueType == typeof(byte[]))
            {
                var buffer = (byte[])value;
                if (buffer.Length == 0)
                {
                    // see RealmObject.SetByteArrayValue
                    NativeQuery.binary_not_equal(queryHandle, columnIndex, (IntPtr)0x1, IntPtr.Zero);
                    return;
                }

                unsafe
                {
                    fixed(byte *bufferPtr = (byte[])value)
                    {
                        NativeQuery.binary_not_equal(queryHandle, columnIndex, (IntPtr)bufferPtr, (IntPtr)buffer.LongLength);
                    }
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        internal override Expression VisitUnary(UnaryExpression u)
        {
            switch (u.NodeType)
            {
            case ExpressionType.Not:
            {
                NativeQuery.not(_coreQueryHandle);
                this.Visit(u.Operand);       // recurse into richer expression, expect to VisitCombination
            }
            break;

            default:
                throw new NotSupportedException($"The unary operator '{u.NodeType}' is not supported");
            }
            return(u);
        }
        private static void AddQueryNotEqual(QueryHandle queryHandle, string columnName, object value)
        {
            var columnIndex = NativeQuery.get_column_index((QueryHandle)queryHandle, columnName, (IntPtr)columnName.Length);

            var valueType = value.GetType();

            if (value.GetType() == typeof(string))
            {
                string valueStr = (string)value;
                NativeQuery.string_not_equal((QueryHandle)queryHandle, columnIndex, valueStr, (IntPtr)valueStr.Length);
            }
            else if (valueType == typeof(bool))
            {
                NativeQuery.bool_not_equal((QueryHandle)queryHandle, columnIndex, MarshalHelpers.BoolToIntPtr((bool)value));
            }
            else if (valueType == typeof(int))
            {
                NativeQuery.int_not_equal((QueryHandle)queryHandle, columnIndex, (IntPtr)((int)value));
            }
            else if (valueType == typeof(float))
            {
                NativeQuery.float_not_equal((QueryHandle)queryHandle, columnIndex, (float)value);
            }
            else if (valueType == typeof(double))
            {
                NativeQuery.double_not_equal((QueryHandle)queryHandle, columnIndex, (double)value);
            }
            else if (valueType == typeof(DateTimeOffset))
            {
                NativeQuery.datetime_seconds_not_equal(queryHandle, columnIndex, ((DateTimeOffset)value).ToUnixTimeSeconds());
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        internal override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable))
            {
                if (m.Method.Name == "Where")
                {
                    this.Visit(m.Arguments[0]);
                    LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
                    this.Visit(lambda.Body);
                    return(m);
                }
                if (m.Method.Name == "OrderBy")
                {
                    this.Visit(m.Arguments[0]);
                    AddSort((LambdaExpression)StripQuotes(m.Arguments[1]), true, true);
                    return(m);
                }
                if (m.Method.Name == "OrderByDescending")
                {
                    this.Visit(m.Arguments[0]);
                    AddSort((LambdaExpression)StripQuotes(m.Arguments[1]), true, false);
                    return(m);
                }
                if (m.Method.Name == "ThenBy")
                {
                    this.Visit(m.Arguments[0]);
                    AddSort((LambdaExpression)StripQuotes(m.Arguments[1]), false, true);
                    return(m);
                }
                if (m.Method.Name == "ThenByDescending")
                {
                    this.Visit(m.Arguments[0]);
                    AddSort((LambdaExpression)StripQuotes(m.Arguments[1]), false, false);
                    return(m);
                }
                if (m.Method.Name == "Count")
                {
                    RecurseToWhereOrRunLambda(m);
                    int foundCount = (int)NativeQuery.count(_coreQueryHandle);
                    return(Expression.Constant(foundCount));
                }
                if (m.Method.Name == "Any")
                {
                    RecurseToWhereOrRunLambda(m);
                    var  rowPtr   = NativeQuery.findDirect(_coreQueryHandle, IntPtr.Zero);
                    var  firstRow = Realm.CreateRowHandle(rowPtr, _realm.SharedRealmHandle);
                    bool foundAny = !firstRow.IsInvalid;
                    return(Expression.Constant(foundAny));
                }
                if (m.Method.Name == "First")
                {
                    RecurseToWhereOrRunLambda(m);
                    RowHandle firstRow = null;
                    if (_optionalSortOrderHandle == null)
                    {
                        var rowPtr = NativeQuery.findDirect(_coreQueryHandle, IntPtr.Zero);
                        firstRow = Realm.CreateRowHandle(rowPtr, _realm.SharedRealmHandle);
                    }
                    else
                    {
                        using (ResultsHandle rh = _realm.MakeResultsForQuery(_retType, _coreQueryHandle, _optionalSortOrderHandle))
                        {
                            var rowPtr = NativeResults.get_row(rh, IntPtr.Zero);
                            firstRow = Realm.CreateRowHandle(rowPtr, _realm.SharedRealmHandle);
                        }
                    }
                    if (firstRow == null || firstRow.IsInvalid)
                    {
                        throw new InvalidOperationException("Sequence contains no matching element");
                    }
                    return(Expression.Constant(_realm.MakeObjectForRow(_retType, firstRow)));
                }
                if (m.Method.Name == "Single")  // same as unsorted First with extra checks
                {
                    RecurseToWhereOrRunLambda(m);
                    var rowPtr   = NativeQuery.findDirect(_coreQueryHandle, IntPtr.Zero);
                    var firstRow = Realm.CreateRowHandle(rowPtr, _realm.SharedRealmHandle);
                    if (firstRow.IsInvalid)
                    {
                        throw new InvalidOperationException("Sequence contains no matching element");
                    }
                    IntPtr nextIndex  = (IntPtr)(firstRow.RowIndex + 1);
                    var    nextRowPtr = NativeQuery.findDirect(_coreQueryHandle, nextIndex);
                    var    nextRow    = Realm.CreateRowHandle(nextRowPtr, _realm.SharedRealmHandle);
                    if (!nextRow.IsInvalid)
                    {
                        throw new InvalidOperationException("Sequence contains more than one matching element");
                    }
                    return(Expression.Constant(_realm.MakeObjectForRow(_retType, firstRow)));
                }
            }

            if (m.Method.DeclaringType == typeof(string))
            {
                NativeQuery.Operation <string> queryMethod = null;

                if (m.Method == Methods.String.Contains.Value)
                {
                    queryMethod = (q, c, v) => NativeQuery.string_contains(q, c, v, (IntPtr)v.Length);
                }
                else if (m.Method == Methods.String.StartsWith.Value)
                {
                    queryMethod = (q, c, v) => NativeQuery.string_starts_with(q, c, v, (IntPtr)v.Length);
                }
                else if (m.Method == Methods.String.EndsWith.Value)
                {
                    queryMethod = (q, c, v) => NativeQuery.string_ends_with(q, c, v, (IntPtr)v.Length);
                }

                if (queryMethod != null)
                {
                    var member = m.Object as MemberExpression;
                    if (member == null)
                    {
                        throw new NotSupportedException($"The method '{m.Method}' has to be invoked on a RealmObject member");
                    }
                    var columnIndex = NativeQuery.get_column_index(_coreQueryHandle, member.Member.Name, (IntPtr)member.Member.Name.Length);

                    var argument = ExtractConstantValue(m.Arguments.SingleOrDefault());
                    if (argument == null || argument.GetType() != typeof(string))
                    {
                        throw new NotSupportedException($"The method '{m.Method}' has to be invoked with a single string constant argument or closure variable");
                    }
                    queryMethod(_coreQueryHandle, columnIndex, (string)argument);
                    return(m);
                }
            }

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