added iOS source code
[wl-app.git] / iOS / Pods / Realm / include / core / realm / query_expression.hpp
diff --git a/iOS/Pods/Realm/include/core/realm/query_expression.hpp b/iOS/Pods/Realm/include/core/realm/query_expression.hpp
new file mode 100644 (file)
index 0000000..247baad
--- /dev/null
@@ -0,0 +1,3786 @@
+/*************************************************************************
+ *
+ * Copyright 2016 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************/
+
+/*
+This file lets you write queries in C++ syntax like: Expression* e = (first + 1 / second >= third + 12.3);
+
+Type conversion/promotion semantics is the same as in the C++ expressions, e.g float + int > double == float +
+(float)int > double.
+
+
+Grammar:
+-----------------------------------------------------------------------------------------------------------------------
+    Expression:         Subexpr2<T>  Compare<Cond, T>  Subexpr2<T>
+                        operator! Expression
+
+    Subexpr2<T>:        Value<T>
+                        Columns<T>
+                        Subexpr2<T>  Operator<Oper<T>  Subexpr2<T>
+                        power(Subexpr2<T>) // power(x) = x * x, as example of unary operator
+
+    Value<T>:           T
+
+    Operator<Oper<T>>:  +, -, *, /
+
+    Compare<Cond, T>:   ==, !=, >=, <=, >, <
+
+    T:                  bool, int, int64_t, float, double, StringData
+
+
+Class diagram
+-----------------------------------------------------------------------------------------------------------------------
+Subexpr2
+    void evaluate(size_t i, ValueBase* destination)
+
+Compare: public Subexpr2
+    size_t find_first(size_t start, size_t end)     // main method that executes query
+
+    unique_ptr<Subexpr2> m_left;                               // left expression subtree
+    unique_ptr<Subexpr2> m_right;                              // right expression subtree
+
+Operator: public Subexpr2
+    void evaluate(size_t i, ValueBase* destination)
+    unique_ptr<Subexpr2> m_left;                               // left expression subtree
+    unique_ptr<Subexpr2> m_right;                              // right expression subtree
+
+Value<T>: public Subexpr2
+    void evaluate(size_t i, ValueBase* destination)
+    T m_v[8];
+
+Columns<T>: public Subexpr2
+    void evaluate(size_t i, ValueBase* destination)
+    SequentialGetter<T> sg;                         // class bound to a column, lets you read values in a fast way
+    Table* m_table;
+
+class ColumnAccessor<>: public Columns<double>
+
+
+Call diagram:
+-----------------------------------------------------------------------------------------------------------------------
+Example of 'table.first > 34.6 + table.second':
+
+size_t Compare<Greater>::find_first()-------------+
+         |                                        |
+         |                                        |
+         |                                        |
+         +--> Columns<float>::evaluate()          +--------> Operator<Plus>::evaluate()
+                                                                |               |
+                                               Value<float>::evaluate()    Columns<float>::evaluate()
+
+Operator, Value and Columns have an evaluate(size_t i, ValueBase* destination) method which returns a Value<T>
+containing 8 values representing table rows i...i + 7.
+
+So Value<T> contains 8 concecutive values and all operations are based on these chunks. This is
+to save overhead by virtual calls needed for evaluating a query that has been dynamically constructed at runtime.
+
+
+Memory allocation:
+-----------------------------------------------------------------------------------------------------------------------
+Subexpressions created by the end-user are stack allocated. They are cloned to the heap when passed to UnaryOperator,
+Operator, and Compare. Those types own the clones and deallocate them when destroyed.
+
+
+Caveats, notes and todos
+-----------------------------------------------------------------------------------------------------------------------
+    * Perhaps disallow columns from two different tables in same expression
+    * The name Columns (with s) an be confusing because we also have Column (without s)
+    * We have Columns::m_table, Query::m_table and ColumnAccessorBase::m_table that point at the same thing, even with
+      ColumnAccessor<> extending Columns. So m_table is redundant, but this is in order to keep class dependencies and
+      entanglement low so that the design is flexible (if you perhaps later want a Columns class that is not dependent
+      on ColumnAccessor)
+
+Nulls
+-----------------------------------------------------------------------------------------------------------------------
+First note that at array level, nulls are distinguished between non-null in different ways:
+String:
+    m_data == 0 && m_size == 0
+
+Integer, Bool, OldDateTime stored in ArrayIntNull:
+    value == get(0) (entry 0 determins a magic value that represents nulls)
+
+Float/double:
+    null::is_null(value) which tests if value bit-matches one specific bit pattern reserved for null
+
+The Columns class encapsulates all this into a simple class that, for any type T has
+    evaluate(size_t index) that reads values from a column, taking nulls in count
+    get(index)
+    set(index)
+    is_null(index)
+    set_null(index)
+*/
+
+#ifndef REALM_QUERY_EXPRESSION_HPP
+#define REALM_QUERY_EXPRESSION_HPP
+
+#include <realm/column_link.hpp>
+#include <realm/column_linklist.hpp>
+#include <realm/column_table.hpp>
+#include <realm/column_type_traits.hpp>
+#include <realm/impl/sequential_getter.hpp>
+#include <realm/link_view.hpp>
+#include <realm/metrics/query_info.hpp>
+#include <realm/query_operators.hpp>
+#include <realm/util/optional.hpp>
+#include <realm/util/serializer.hpp>
+
+#include <numeric>
+
+// Normally, if a next-generation-syntax condition is supported by the old query_engine.hpp, a query_engine node is
+// created because it's faster (by a factor of 5 - 10). Because many of our existing next-generation-syntax unit
+// unit tests are indeed simple enough to fallback to old query_engine, query_expression gets low test coverage. Undef
+// flag to get higher query_expression test coverage. This is a good idea to try out each time you develop on/modify
+// query_expression.
+
+#define REALM_OLDQUERY_FALLBACK
+
+namespace realm {
+
+template <class T>
+T minimum(T a, T b)
+{
+    return a < b ? a : b;
+}
+
+#ifdef REALM_OLDQUERY_FALLBACK
+// Hack to avoid template instantiation errors. See create(). Todo, see if we can simplify only_numeric somehow
+namespace _impl {
+
+template <class T, class U>
+inline T only_numeric(U in)
+{
+    return static_cast<T>(util::unwrap(in));
+}
+
+template <class T>
+inline int only_numeric(const StringData&)
+{
+    REALM_ASSERT(false);
+    return 0;
+}
+
+template <class T>
+inline int only_numeric(const BinaryData&)
+{
+    REALM_ASSERT(false);
+    return 0;
+}
+
+template <class T>
+inline StringData only_string(T in)
+{
+    REALM_ASSERT(false);
+    static_cast<void>(in);
+    return StringData();
+}
+
+inline StringData only_string(StringData in)
+{
+    return in;
+}
+
+template <class T, class U>
+inline T no_timestamp(U in)
+{
+    return static_cast<T>(util::unwrap(in));
+}
+
+template <class T>
+inline int no_timestamp(const Timestamp&)
+{
+    REALM_ASSERT(false);
+    return 0;
+}
+
+} // namespace _impl
+
+#endif // REALM_OLDQUERY_FALLBACK
+
+template <class T>
+struct Plus {
+    T operator()(T v1, T v2) const
+    {
+        return v1 + v2;
+    }
+    static std::string description()
+    {
+        return "plus";
+    }
+    typedef T type;
+};
+
+template <class T>
+struct Minus {
+    T operator()(T v1, T v2) const
+    {
+        return v1 - v2;
+    }
+    static std::string description()
+    {
+        return "minus";
+    }
+    typedef T type;
+};
+
+template <class T>
+struct Div {
+    T operator()(T v1, T v2) const
+    {
+        return v1 / v2;
+    }
+    static std::string description()
+    {
+        return "divided by";
+    }
+    typedef T type;
+};
+
+template <class T>
+struct Mul {
+    T operator()(T v1, T v2) const
+    {
+        return v1 * v2;
+    }
+    static std::string description()
+    {
+        return "multiplied by";
+    }
+    typedef T type;
+};
+
+// Unary operator
+template <class T>
+struct Pow {
+    T operator()(T v) const
+    {
+        return v * v;
+    }
+    static std::string description()
+    {
+        return "to the power of";
+    }
+    typedef T type;
+};
+
+// Finds a common type for T1 and T2 according to C++ conversion/promotion in arithmetic (float + int => float, etc)
+template <class T1, class T2, bool T1_is_int = std::numeric_limits<T1>::is_integer || std::is_same<T1, null>::value,
+          bool T2_is_int = std::numeric_limits<T2>::is_integer || std::is_same<T2, null>::value,
+          bool T1_is_widest = (sizeof(T1) > sizeof(T2) || std::is_same<T2, null>::value)>
+struct Common;
+template <class T1, class T2, bool b>
+struct Common<T1, T2, b, b, true> {
+    typedef T1 type;
+};
+template <class T1, class T2, bool b>
+struct Common<T1, T2, b, b, false> {
+    typedef T2 type;
+};
+template <class T1, class T2, bool b>
+struct Common<T1, T2, false, true, b> {
+    typedef T1 type;
+};
+template <class T1, class T2, bool b>
+struct Common<T1, T2, true, false, b> {
+    typedef T2 type;
+};
+
+
+struct RowIndex {
+    enum DetachedTag {
+        Detached,
+    };
+
+    explicit RowIndex()
+        : m_row_index(npos)
+    {
+    }
+    explicit RowIndex(size_t row_index)
+        : m_row_index(row_index)
+    {
+    }
+    RowIndex(DetachedTag)
+        : m_row_index()
+    {
+    }
+
+    bool is_attached() const
+    {
+        return bool(m_row_index);
+    }
+    bool is_null() const
+    {
+        return is_attached() && *m_row_index == npos;
+    }
+
+    bool operator==(const RowIndex& other) const
+    {
+        // Row indexes that are detached are never equal to any other row index.
+        if (!is_attached() || !other.is_attached())
+            return false;
+        return m_row_index == other.m_row_index;
+    }
+    bool operator!=(const RowIndex& other) const
+    {
+        return !(*this == other);
+    }
+    template <class C, class T>
+    friend std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>&, const RowIndex&);
+
+private:
+    util::Optional<size_t> m_row_index;
+};
+
+template <class C, class T>
+inline std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& out, const RowIndex& r)
+{
+    if (!r.is_attached()) {
+        out << "detached row";
+    } else if (r.is_null()) {
+        out << "null row";
+    } else {
+        out << r.m_row_index;
+    }
+    return out;
+}
+
+struct ValueBase {
+    static const size_t default_size = 8;
+    virtual void export_bool(ValueBase& destination) const = 0;
+    virtual void export_Timestamp(ValueBase& destination) const = 0;
+    virtual void export_int(ValueBase& destination) const = 0;
+    virtual void export_float(ValueBase& destination) const = 0;
+    virtual void export_int64_t(ValueBase& destination) const = 0;
+    virtual void export_double(ValueBase& destination) const = 0;
+    virtual void export_StringData(ValueBase& destination) const = 0;
+    virtual void export_BinaryData(ValueBase& destination) const = 0;
+    virtual void export_RowIndex(ValueBase& destination) const = 0;
+    virtual void export_null(ValueBase& destination) const = 0;
+    virtual void import(const ValueBase& destination) = 0;
+
+    // If true, all values in the class come from a link list of a single field in the parent table (m_table). If
+    // false, then values come from successive rows of m_table (query operations are operated on in bulks for speed)
+    bool m_from_link_list;
+
+    // Number of values stored in the class.
+    size_t m_values;
+};
+
+class Expression {
+public:
+    Expression()
+    {
+    }
+    virtual ~Expression()
+    {
+    }
+
+    virtual size_t find_first(size_t start, size_t end) const = 0;
+    virtual void set_base_table(const Table* table) = 0;
+    virtual void verify_column() const = 0;
+    virtual const Table* get_base_table() const = 0;
+    virtual std::string description() const = 0;
+
+    virtual std::unique_ptr<Expression> clone(QueryNodeHandoverPatches*) const = 0;
+    virtual void apply_handover_patch(QueryNodeHandoverPatches&, Group&)
+    {
+    }
+};
+
+template <typename T, typename... Args>
+std::unique_ptr<Expression> make_expression(Args&&... args)
+{
+    return std::unique_ptr<Expression>(new T(std::forward<Args>(args)...));
+}
+
+class Subexpr {
+public:
+    virtual ~Subexpr()
+    {
+    }
+
+    virtual std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* = nullptr) const = 0;
+    virtual void apply_handover_patch(QueryNodeHandoverPatches&, Group&)
+    {
+    }
+
+    // When the user constructs a query, it always "belongs" to one single base/parent table (regardless of
+    // any links or not and regardless of any queries assembled with || or &&). When you do a Query::find(),
+    // then Query::m_table is set to this table, and set_base_table() is called on all Columns and LinkMaps in
+    // the query expression tree so that they can set/update their internals as required.
+    //
+    // During thread-handover of a Query, set_base_table() is also called to make objects point at the new table
+    // instead of the old one from the old thread.
+    virtual void set_base_table(const Table*)
+    {
+    }
+
+    virtual void verify_column() const = 0;
+    virtual std::string description() const = 0;
+
+    // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression
+    // and
+    // binds it to a Query at a later time
+    virtual const Table* get_base_table() const
+    {
+        return nullptr;
+    }
+
+    virtual void evaluate(size_t index, ValueBase& destination) = 0;
+};
+
+template <typename T, typename... Args>
+std::unique_ptr<Subexpr> make_subexpr(Args&&... args)
+{
+    return std::unique_ptr<Subexpr>(new T(std::forward<Args>(args)...));
+}
+
+template <class T>
+class Columns;
+template <class T>
+class Value;
+class ConstantStringValue;
+template <class T>
+class Subexpr2;
+template <class oper, class TLeft = Subexpr, class TRight = Subexpr>
+class Operator;
+template <class oper, class TLeft = Subexpr>
+class UnaryOperator;
+template <class oper, class TLeft = Subexpr>
+class SizeOperator;
+template <class TCond, class T, class TLeft = Subexpr, class TRight = Subexpr>
+class Compare;
+template <bool has_links>
+class UnaryLinkCompare;
+class ColumnAccessorBase;
+
+
+// Handle cases where left side is a constant (int, float, int64_t, double, StringData)
+template <class Cond, class L, class R>
+Query create(L left, const Subexpr2<R>& right)
+{
+// Purpose of below code is to intercept the creation of a condition and test if it's supported by the old
+// query_engine.hpp which is faster. If it's supported, create a query_engine.hpp node, otherwise create a
+// query_expression.hpp node.
+//
+// This method intercepts only Value <cond> Subexpr2. Interception of Subexpr2 <cond> Subexpr is elsewhere.
+
+#ifdef REALM_OLDQUERY_FALLBACK // if not defined, then never fallback to query_engine.hpp; always use query_expression
+    const Columns<R>* column = dynamic_cast<const Columns<R>*>(&right);
+    // TODO: recognize size operator expressions
+    // auto size_operator = dynamic_cast<const SizeOperator<Size<StringData>, Subexpr>*>(&right);
+
+    if (column && ((std::numeric_limits<L>::is_integer && std::numeric_limits<R>::is_integer) ||
+                   (std::is_same<L, double>::value && std::is_same<R, double>::value) ||
+                   (std::is_same<L, float>::value && std::is_same<R, float>::value) ||
+                   (std::is_same<L, Timestamp>::value && std::is_same<R, Timestamp>::value) ||
+                   (std::is_same<L, StringData>::value && std::is_same<R, StringData>::value) ||
+                   (std::is_same<L, BinaryData>::value && std::is_same<R, BinaryData>::value)) &&
+        !column->links_exist()) {
+        const Table* t = column->get_base_table();
+        Query q = Query(*t);
+
+        if (std::is_same<Cond, Less>::value)
+            q.greater(column->column_ndx(), _impl::only_numeric<R>(left));
+        else if (std::is_same<Cond, Greater>::value)
+            q.less(column->column_ndx(), _impl::only_numeric<R>(left));
+        else if (std::is_same<Cond, Equal>::value)
+            q.equal(column->column_ndx(), left);
+        else if (std::is_same<Cond, NotEqual>::value)
+            q.not_equal(column->column_ndx(), left);
+        else if (std::is_same<Cond, LessEqual>::value)
+            q.greater_equal(column->column_ndx(), _impl::only_numeric<R>(left));
+        else if (std::is_same<Cond, GreaterEqual>::value)
+            q.less_equal(column->column_ndx(), _impl::only_numeric<R>(left));
+        else if (std::is_same<Cond, EqualIns>::value)
+            q.equal(column->column_ndx(), _impl::only_string(left), false);
+        else if (std::is_same<Cond, NotEqualIns>::value)
+            q.not_equal(column->column_ndx(), _impl::only_string(left), false);
+        else if (std::is_same<Cond, BeginsWith>::value)
+            q.begins_with(column->column_ndx(), _impl::only_string(left));
+        else if (std::is_same<Cond, BeginsWithIns>::value)
+            q.begins_with(column->column_ndx(), _impl::only_string(left), false);
+        else if (std::is_same<Cond, EndsWith>::value)
+            q.ends_with(column->column_ndx(), _impl::only_string(left));
+        else if (std::is_same<Cond, EndsWithIns>::value)
+            q.ends_with(column->column_ndx(), _impl::only_string(left), false);
+        else if (std::is_same<Cond, Contains>::value)
+            q.contains(column->column_ndx(), _impl::only_string(left));
+        else if (std::is_same<Cond, ContainsIns>::value)
+            q.contains(column->column_ndx(), _impl::only_string(left), false);
+        else if (std::is_same<Cond, Like>::value)
+            q.like(column->column_ndx(), _impl::only_string(left));
+        else if (std::is_same<Cond, LikeIns>::value)
+            q.like(column->column_ndx(), _impl::only_string(left), false);
+        else {
+            // query_engine.hpp does not support this Cond. Please either add support for it in query_engine.hpp or
+            // fallback to using use 'return new Compare<>' instead.
+            REALM_ASSERT(false);
+        }
+        // Return query_engine.hpp node
+        return q;
+    }
+    else
+#endif
+    {
+        // Return query_expression.hpp node
+        using CommonType = typename Common<L, R>::type;
+        using ValueType =
+            typename std::conditional<std::is_same<L, StringData>::value, ConstantStringValue, Value<L>>::type;
+        return make_expression<Compare<Cond, CommonType>>(make_subexpr<ValueType>(left), right.clone());
+    }
+}
+
+
+// All overloads where left-hand-side is Subexpr2<L>:
+//
+// left-hand-side       operator                              right-hand-side
+// Subexpr2<L>          +, -, *, /, <, >, ==, !=, <=, >=      R, Subexpr2<R>
+//
+// For L = R = {int, int64_t, float, double, StringData, Timestamp}:
+template <class L, class R>
+class Overloads {
+    typedef typename Common<L, R>::type CommonType;
+
+    std::unique_ptr<Subexpr> clone_subexpr() const
+    {
+        return static_cast<const Subexpr2<L>&>(*this).clone();
+    }
+
+public:
+    // Arithmetic, right side constant
+    Operator<Plus<CommonType>> operator+(R right) const
+    {
+        return {clone_subexpr(), make_subexpr<Value<R>>(right)};
+    }
+    Operator<Minus<CommonType>> operator-(R right) const
+    {
+        return {clone_subexpr(), make_subexpr<Value<R>>(right)};
+    }
+    Operator<Mul<CommonType>> operator*(R right) const
+    {
+        return {clone_subexpr(), make_subexpr<Value<R>>(right)};
+    }
+    Operator<Div<CommonType>> operator/(R right) const
+    {
+        return {clone_subexpr(), make_subexpr<Value<R>>(right)};
+    }
+
+    // Arithmetic, right side subexpression
+    Operator<Plus<CommonType>> operator+(const Subexpr2<R>& right) const
+    {
+        return {clone_subexpr(), right.clone()};
+    }
+    Operator<Minus<CommonType>> operator-(const Subexpr2<R>& right) const
+    {
+        return {clone_subexpr(), right.clone()};
+    }
+    Operator<Mul<CommonType>> operator*(const Subexpr2<R>& right) const
+    {
+        return {clone_subexpr(), right.clone()};
+    }
+    Operator<Div<CommonType>> operator/(const Subexpr2<R>& right) const
+    {
+        return {clone_subexpr(), right.clone()};
+    }
+
+    // Compare, right side constant
+    Query operator>(R right)
+    {
+        return create<Less>(right, static_cast<Subexpr2<L>&>(*this));
+    }
+    Query operator<(R right)
+    {
+        return create<Greater>(right, static_cast<Subexpr2<L>&>(*this));
+    }
+    Query operator>=(R right)
+    {
+        return create<LessEqual>(right, static_cast<Subexpr2<L>&>(*this));
+    }
+    Query operator<=(R right)
+    {
+        return create<GreaterEqual>(right, static_cast<Subexpr2<L>&>(*this));
+    }
+    Query operator==(R right)
+    {
+        return create<Equal>(right, static_cast<Subexpr2<L>&>(*this));
+    }
+    Query operator!=(R right)
+    {
+        return create<NotEqual>(right, static_cast<Subexpr2<L>&>(*this));
+    }
+
+    // Purpose of this method is to intercept the creation of a condition and test if it's supported by the old
+    // query_engine.hpp which is faster. If it's supported, create a query_engine.hpp node, otherwise create a
+    // query_expression.hpp node.
+    //
+    // This method intercepts Subexpr2 <cond> Subexpr2 only. Value <cond> Subexpr2 is intercepted elsewhere.
+    template <class Cond>
+    Query create2(const Subexpr2<R>& right)
+    {
+#ifdef REALM_OLDQUERY_FALLBACK // if not defined, never fallback query_engine; always use query_expression
+        // Test if expressions are of type Columns. Other possibilities are Value and Operator.
+        const Columns<R>* left_col = dynamic_cast<const Columns<R>*>(static_cast<Subexpr2<L>*>(this));
+        const Columns<R>* right_col = dynamic_cast<const Columns<R>*>(&right);
+
+        // query_engine supports 'T-column <op> <T-column>' for T = {int64_t, float, double}, op = {<, >, ==, !=, <=,
+        // >=},
+        // but only if both columns are non-nullable, and aren't in linked tables.
+        if (left_col && right_col && std::is_same<L, R>::value && !left_col->is_nullable() &&
+            !right_col->is_nullable() && !left_col->links_exist() && !right_col->links_exist() &&
+            !std::is_same<L, Timestamp>::value) {
+            const Table* t = left_col->get_base_table();
+            Query q = Query(*t);
+
+            if (std::numeric_limits<L>::is_integer || std::is_same<L, OldDateTime>::value) {
+                if (std::is_same<Cond, Less>::value)
+                    q.less_int(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, Greater>::value)
+                    q.greater_int(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, Equal>::value)
+                    q.equal_int(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, NotEqual>::value)
+                    q.not_equal_int(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, LessEqual>::value)
+                    q.less_equal_int(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, GreaterEqual>::value)
+                    q.greater_equal_int(left_col->column_ndx(), right_col->column_ndx());
+                else {
+                    REALM_ASSERT(false);
+                }
+            }
+            else if (std::is_same<L, float>::value) {
+                if (std::is_same<Cond, Less>::value)
+                    q.less_float(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, Greater>::value)
+                    q.greater_float(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, Equal>::value)
+                    q.equal_float(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, NotEqual>::value)
+                    q.not_equal_float(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, LessEqual>::value)
+                    q.less_equal_float(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, GreaterEqual>::value)
+                    q.greater_equal_float(left_col->column_ndx(), right_col->column_ndx());
+                else {
+                    REALM_ASSERT(false);
+                }
+            }
+            else if (std::is_same<L, double>::value) {
+                if (std::is_same<Cond, Less>::value)
+                    q.less_double(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, Greater>::value)
+                    q.greater_double(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, Equal>::value)
+                    q.equal_double(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, NotEqual>::value)
+                    q.not_equal_double(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, LessEqual>::value)
+                    q.less_equal_double(left_col->column_ndx(), right_col->column_ndx());
+                else if (std::is_same<Cond, GreaterEqual>::value)
+                    q.greater_equal_double(left_col->column_ndx(), right_col->column_ndx());
+                else {
+                    REALM_ASSERT(false);
+                }
+            }
+            else {
+                REALM_ASSERT(false);
+            }
+            // Return query_engine.hpp node
+            return q;
+        }
+        else
+#endif
+        {
+            // Return query_expression.hpp node
+            return make_expression<Compare<Cond, typename Common<L, R>::type>>(clone_subexpr(), right.clone());
+        }
+    }
+
+    // Compare, right side subexpression
+    Query operator==(const Subexpr2<R>& right)
+    {
+        return create2<Equal>(right);
+    }
+    Query operator!=(const Subexpr2<R>& right)
+    {
+        return create2<NotEqual>(right);
+    }
+    Query operator>(const Subexpr2<R>& right)
+    {
+        return create2<Greater>(right);
+    }
+    Query operator<(const Subexpr2<R>& right)
+    {
+        return create2<Less>(right);
+    }
+    Query operator>=(const Subexpr2<R>& right)
+    {
+        return create2<GreaterEqual>(right);
+    }
+    Query operator<=(const Subexpr2<R>& right)
+    {
+        return create2<LessEqual>(right);
+    }
+};
+
+// With this wrapper class we can define just 20 overloads inside Overloads<L, R> instead of 5 * 20 = 100. Todo: We
+// can
+// consider if it's simpler/better to remove this class completely and just list all 100 overloads manually anyway.
+template <class T>
+class Subexpr2 : public Subexpr,
+                 public Overloads<T, const char*>,
+                 public Overloads<T, int>,
+                 public Overloads<T, float>,
+                 public Overloads<T, double>,
+                 public Overloads<T, int64_t>,
+                 public Overloads<T, StringData>,
+                 public Overloads<T, bool>,
+                 public Overloads<T, Timestamp>,
+                 public Overloads<T, OldDateTime>,
+                 public Overloads<T, null> {
+public:
+    virtual ~Subexpr2()
+    {
+    }
+
+#define RLM_U2(t, o) using Overloads<T, t>::operator o;
+#define RLM_U(o)                                                                                                     \
+    RLM_U2(int, o)                                                                                                   \
+    RLM_U2(float, o)                                                                                                 \
+    RLM_U2(double, o)                                                                                                \
+    RLM_U2(int64_t, o)                                                                                               \
+    RLM_U2(StringData, o) RLM_U2(bool, o) RLM_U2(OldDateTime, o) RLM_U2(Timestamp, o) RLM_U2(null, o)
+    RLM_U(+) RLM_U(-) RLM_U(*) RLM_U(/) RLM_U(>) RLM_U(<) RLM_U(==) RLM_U(!=) RLM_U(>=) RLM_U(<=)
+};
+
+// Subexpr2<Link> only provides equality comparisons. Their implementations can be found later in this file.
+template <>
+class Subexpr2<Link> : public Subexpr {
+};
+
+template <>
+class Subexpr2<StringData> : public Subexpr, public Overloads<StringData, StringData> {
+public:
+    Query equal(StringData sd, bool case_sensitive = true);
+    Query equal(const Subexpr2<StringData>& col, bool case_sensitive = true);
+    Query not_equal(StringData sd, bool case_sensitive = true);
+    Query not_equal(const Subexpr2<StringData>& col, bool case_sensitive = true);
+    Query begins_with(StringData sd, bool case_sensitive = true);
+    Query begins_with(const Subexpr2<StringData>& col, bool case_sensitive = true);
+    Query ends_with(StringData sd, bool case_sensitive = true);
+    Query ends_with(const Subexpr2<StringData>& col, bool case_sensitive = true);
+    Query contains(StringData sd, bool case_sensitive = true);
+    Query contains(const Subexpr2<StringData>& col, bool case_sensitive = true);
+    Query like(StringData sd, bool case_sensitive = true);
+    Query like(const Subexpr2<StringData>& col, bool case_sensitive = true);
+};
+
+/*
+This class is used to store N values of type T = {int64_t, bool, OldDateTime or StringData}, and allows an entry
+to be null too. It's used by the Value class for internal storage.
+
+To indicate nulls, we could have chosen a separate bool vector or some other bitmask construction. But for
+performance, we customize indication of nulls to match the same indication that is used in the persisted database
+file
+
+Queries in query_expression.hpp execute by processing chunks of 8 rows at a time. Assume you have a column:
+
+    price (int) = {1, 2, 3, null, 1, 6, 6, 9, 5, 2, null}
+
+And perform a query:
+
+    Query q = (price + 2 == 5);
+
+query_expression.hpp will then create a NullableVector<int> = {5, 5, 5, 5, 5, 5, 5, 5} and then read values
+NullableVector<int> = {1, 2, 3, null, 1, 6, 6, 9} from the column, and then perform `+` and `==` on these chunks.
+
+See the top of this file for more information on all this.
+
+Assume the user specifies the null constant in a query:
+
+Query q = (price == null)
+
+The query system will then construct a NullableVector of type `null` (NullableVector<null>). This allows compile
+time optimizations for these cases.
+*/
+
+template <class T, size_t prealloc = 8>
+struct NullableVector {
+    using Underlying = typename util::RemoveOptional<T>::type;
+    using t_storage =
+        typename std::conditional<std::is_same<Underlying, bool>::value || std::is_same<Underlying, int>::value,
+                                  int64_t, Underlying>::type;
+
+    NullableVector()
+    {
+    }
+
+    NullableVector& operator=(const NullableVector& other)
+    {
+        if (this != &other) {
+            init(other.m_size);
+            realm::safe_copy_n(other.m_first, other.m_size, m_first);
+            m_null = other.m_null;
+        }
+        return *this;
+    }
+
+    NullableVector(const NullableVector& other)
+    {
+        init(other.m_size);
+        realm::safe_copy_n(other.m_first, other.m_size, m_first);
+        m_null = other.m_null;
+    }
+
+    ~NullableVector()
+    {
+        dealloc();
+    }
+
+    T operator[](size_t index) const
+    {
+        REALM_ASSERT_3(index, <, m_size);
+        return static_cast<T>(m_first[index]);
+    }
+
+    inline bool is_null(size_t index) const
+    {
+        REALM_ASSERT((std::is_same<t_storage, int64_t>::value));
+        return m_first[index] == m_null;
+    }
+
+    inline void set_null(size_t index)
+    {
+        REALM_ASSERT((std::is_same<t_storage, int64_t>::value));
+        m_first[index] = m_null;
+    }
+
+    template <typename Type = t_storage>
+    typename std::enable_if<std::is_same<Type, int64_t>::value, void>::type set(size_t index, t_storage value)
+    {
+        REALM_ASSERT((std::is_same<t_storage, int64_t>::value));
+
+        // If value collides with magic null value, then switch to a new unique representation for null
+        if (REALM_UNLIKELY(value == m_null)) {
+            // adding a prime will generate 2^64 unique values. Todo: Only works on 2's complement architecture
+            uint64_t candidate = static_cast<uint64_t>(m_null) + 0xfffffffbULL;
+            while (std::find(m_first, m_first + m_size, static_cast<int64_t>(candidate)) != m_first + m_size)
+                candidate += 0xfffffffbULL;
+            std::replace(m_first, m_first + m_size, m_null, static_cast<int64_t>(candidate));
+        }
+        m_first[index] = value;
+    }
+
+    template <typename Type = T>
+    typename std::enable_if<realm::is_any<Type, float, double, OldDateTime, BinaryData, StringData, RowIndex,
+                                          Timestamp, ConstTableRef, null>::value,
+                            void>::type
+    set(size_t index, t_storage value)
+    {
+        m_first[index] = value;
+    }
+
+    inline util::Optional<T> get(size_t index) const
+    {
+        if (is_null(index))
+            return util::none;
+
+        return util::make_optional((*this)[index]);
+    }
+
+    inline void set(size_t index, util::Optional<Underlying> value)
+    {
+        if (value) {
+            Underlying v = *value;
+            set(index, v);
+        }
+        else {
+            set_null(index);
+        }
+    }
+
+    void fill(T value)
+    {
+        for (size_t t = 0; t < m_size; t++) {
+            if (std::is_same<T, null>::value)
+                set_null(t);
+            else
+                set(t, value);
+        }
+    }
+
+    void init(size_t size)
+    {
+        if (size == m_size)
+            return;
+
+        dealloc();
+        m_size = size;
+        if (m_size > 0) {
+            if (m_size > prealloc)
+                m_first = reinterpret_cast<t_storage*>(new t_storage[m_size]);
+            else
+                m_first = m_cache;
+        }
+    }
+
+    void init(size_t size, T values)
+    {
+        init(size);
+        fill(values);
+    }
+
+    void dealloc()
+    {
+        if (m_first) {
+            if (m_size > prealloc)
+                delete[] m_first;
+            m_first = nullptr;
+        }
+    }
+
+    t_storage m_cache[prealloc];
+    t_storage* m_first = &m_cache[0];
+    size_t m_size = 0;
+
+    int64_t m_null = reinterpret_cast<int64_t>(&m_null); // choose magic value to represent nulls
+};
+
+// Double
+// NOTE: fails in gcc 4.8 without `inline`. Do not remove. Same applies for all methods below.
+template <>
+inline bool NullableVector<double>::is_null(size_t index) const
+{
+    return null::is_null_float(m_first[index]);
+}
+
+template <>
+inline void NullableVector<double>::set_null(size_t index)
+{
+    m_first[index] = null::get_null_float<double>();
+}
+
+// Float
+template <>
+inline bool NullableVector<float>::is_null(size_t index) const
+{
+    return null::is_null_float(m_first[index]);
+}
+
+template <>
+inline void NullableVector<float>::set_null(size_t index)
+{
+    m_first[index] = null::get_null_float<float>();
+}
+
+
+// Null
+template <>
+inline void NullableVector<null>::set_null(size_t)
+{
+    return;
+}
+template <>
+inline bool NullableVector<null>::is_null(size_t) const
+{
+    return true;
+}
+
+
+// OldDateTime
+template <>
+inline bool NullableVector<OldDateTime>::is_null(size_t index) const
+{
+    return m_first[index].get_olddatetime() == m_null;
+}
+
+
+template <>
+inline void NullableVector<OldDateTime>::set_null(size_t index)
+{
+    m_first[index] = m_null;
+}
+
+// StringData
+
+template <>
+inline bool NullableVector<StringData>::is_null(size_t index) const
+{
+    return m_first[index].is_null();
+}
+
+template <>
+inline void NullableVector<StringData>::set_null(size_t index)
+{
+    m_first[index] = StringData();
+}
+
+// BinaryData
+
+template <>
+inline bool NullableVector<BinaryData>::is_null(size_t index) const
+{
+    return m_first[index].is_null();
+}
+
+template <>
+inline void NullableVector<BinaryData>::set_null(size_t index)
+{
+    m_first[index] = BinaryData();
+}
+
+// RowIndex
+template <>
+inline bool NullableVector<RowIndex>::is_null(size_t index) const
+{
+    return m_first[index].is_null();
+}
+template <>
+inline void NullableVector<RowIndex>::set_null(size_t index)
+{
+    m_first[index] = RowIndex();
+}
+
+
+// Timestamp
+
+template <>
+inline bool NullableVector<Timestamp>::is_null(size_t index) const
+{
+    return m_first[index].is_null();
+}
+
+template <>
+inline void NullableVector<Timestamp>::set_null(size_t index)
+{
+    m_first[index] = Timestamp{};
+}
+
+// ConstTableRef
+template <>
+inline bool NullableVector<ConstTableRef>::is_null(size_t index) const
+{
+    return !bool(m_first[index]);
+}
+template <>
+inline void NullableVector<ConstTableRef>::set_null(size_t index)
+{
+    m_first[index].reset();
+}
+
+template <typename Operator>
+struct OperatorOptionalAdapter {
+    template <typename L, typename R>
+    util::Optional<typename Operator::type> operator()(const util::Optional<L>& left, const util::Optional<R>& right)
+    {
+        if (!left || !right)
+            return util::none;
+        return Operator()(*left, *right);
+    }
+
+    template <typename T>
+    util::Optional<typename Operator::type> operator()(const util::Optional<T>& arg)
+    {
+        if (!arg)
+            return util::none;
+        return Operator()(*arg);
+    }
+};
+
+
+struct TrueExpression : Expression {
+    size_t find_first(size_t start, size_t end) const override
+    {
+        REALM_ASSERT(start <= end);
+        if (start != end)
+            return start;
+
+        return realm::not_found;
+    }
+    void set_base_table(const Table*) override
+    {
+    }
+    const Table* get_base_table() const override
+    {
+        return nullptr;
+    }
+    void verify_column() const override
+    {
+    }
+    std::string description() const override
+    {
+        return "TRUEPREDICATE";
+    }
+    std::unique_ptr<Expression> clone(QueryNodeHandoverPatches*) const override
+    {
+        return std::unique_ptr<Expression>(new TrueExpression(*this));
+    }
+};
+
+
+struct FalseExpression : Expression {
+    size_t find_first(size_t, size_t) const override
+    {
+        return realm::not_found;
+    }
+    void set_base_table(const Table*) override
+    {
+    }
+    void verify_column() const override
+    {
+    }
+    std::string description() const override
+    {
+        return "FALSEPREDICATE";
+    }
+    const Table* get_base_table() const override
+    {
+        return nullptr;
+    }
+    std::unique_ptr<Expression> clone(QueryNodeHandoverPatches*) const override
+    {
+        return std::unique_ptr<Expression>(new FalseExpression(*this));
+    }
+};
+
+
+// Stores N values of type T. Can also exchange data with other ValueBase of different types
+template <class T>
+class Value : public ValueBase, public Subexpr2<T> {
+public:
+    Value()
+    {
+        init(false, ValueBase::default_size, T());
+    }
+    Value(T v)
+    {
+        init(false, ValueBase::default_size, v);
+    }
+
+    Value(bool from_link_list, size_t values)
+    {
+        init(from_link_list, values, T());
+    }
+
+    Value(bool from_link_list, size_t values, T v)
+    {
+        init(from_link_list, values, v);
+    }
+
+    Value(const Value&) = default;
+    Value& operator=(const Value&) = default;
+
+    void init(bool from_link_list, size_t values, T v)
+    {
+        m_storage.init(values, v);
+        ValueBase::m_from_link_list = from_link_list;
+        ValueBase::m_values = values;
+    }
+
+    void init(bool from_link_list, size_t values)
+    {
+        m_storage.init(values);
+        ValueBase::m_from_link_list = from_link_list;
+        ValueBase::m_values = values;
+    }
+
+    void verify_column() const override
+    {
+    }
+
+    virtual std::string description() const override
+    {
+        if (ValueBase::m_from_link_list) {
+            return util::serializer::print_value(util::to_string(ValueBase::m_values)
+                                        + (ValueBase::m_values == 1 ? " value" : " values"));
+        }
+        if (m_storage.m_size > 0) {
+            return util::serializer::print_value(m_storage[0]);
+        }
+        return "";
+    }
+
+    void evaluate(size_t, ValueBase& destination) override
+    {
+        destination.import(*this);
+    }
+
+
+    template <class TOperator>
+    REALM_FORCEINLINE void fun(const Value* left, const Value* right)
+    {
+        OperatorOptionalAdapter<TOperator> o;
+
+        if (!left->m_from_link_list && !right->m_from_link_list) {
+            // Operate on values one-by-one (one value is one row; no links)
+            size_t min = std::min(left->m_values, right->m_values);
+            init(false, min);
+
+            for (size_t i = 0; i < min; i++) {
+                m_storage.set(i, o(left->m_storage.get(i), right->m_storage.get(i)));
+            }
+        }
+        else if (left->m_from_link_list && right->m_from_link_list) {
+            // FIXME: Many-to-many links not supported yet. Need to specify behaviour
+            REALM_ASSERT_DEBUG(false);
+        }
+        else if (!left->m_from_link_list && right->m_from_link_list) {
+            // Right values come from link. Left must come from single row.
+            REALM_ASSERT_DEBUG(left->m_values > 0);
+            init(true, right->m_values);
+
+            auto left_value = left->m_storage.get(0);
+            for (size_t i = 0; i < right->m_values; i++) {
+                m_storage.set(i, o(left_value, right->m_storage.get(i)));
+            }
+        }
+        else if (left->m_from_link_list && !right->m_from_link_list) {
+            // Same as above, but with left values coming from links
+            REALM_ASSERT_DEBUG(right->m_values > 0);
+            init(true, left->m_values);
+
+            auto right_value = right->m_storage.get(0);
+            for (size_t i = 0; i < left->m_values; i++) {
+                m_storage.set(i, o(left->m_storage.get(i), right_value));
+            }
+        }
+    }
+
+    template <class TOperator>
+    REALM_FORCEINLINE void fun(const Value* value)
+    {
+        init(value->m_from_link_list, value->m_values);
+
+        OperatorOptionalAdapter<TOperator> o;
+        for (size_t i = 0; i < value->m_values; i++) {
+            m_storage.set(i, o(value->m_storage.get(i)));
+        }
+    }
+
+
+    // Below import and export methods are for type conversion between float, double, int64_t, etc.
+    template <class D>
+    typename std::enable_if<std::is_convertible<T, D>::value>::type REALM_FORCEINLINE
+    export2(ValueBase& destination) const
+    {
+        Value<D>& d = static_cast<Value<D>&>(destination);
+        d.init(ValueBase::m_from_link_list, ValueBase::m_values, D());
+        for (size_t t = 0; t < ValueBase::m_values; t++) {
+            if (m_storage.is_null(t))
+                d.m_storage.set_null(t);
+            else {
+                d.m_storage.set(t, static_cast<D>(m_storage[t]));
+            }
+        }
+    }
+
+    template <class D>
+    typename std::enable_if<!std::is_convertible<T, D>::value>::type REALM_FORCEINLINE export2(ValueBase&) const
+    {
+        // export2 is instantiated for impossible conversions like T=StringData, D=int64_t. These are never
+        // performed at runtime but would result in a compiler error if we did not provide this implementation.
+        REALM_ASSERT_DEBUG(false);
+    }
+
+    REALM_FORCEINLINE void export_Timestamp(ValueBase& destination) const override
+    {
+        export2<Timestamp>(destination);
+    }
+
+    REALM_FORCEINLINE void export_bool(ValueBase& destination) const override
+    {
+        export2<bool>(destination);
+    }
+
+    REALM_FORCEINLINE void export_int64_t(ValueBase& destination) const override
+    {
+        export2<int64_t>(destination);
+    }
+
+    REALM_FORCEINLINE void export_float(ValueBase& destination) const override
+    {
+        export2<float>(destination);
+    }
+
+    REALM_FORCEINLINE void export_int(ValueBase& destination) const override
+    {
+        export2<int>(destination);
+    }
+
+    REALM_FORCEINLINE void export_double(ValueBase& destination) const override
+    {
+        export2<double>(destination);
+    }
+    REALM_FORCEINLINE void export_StringData(ValueBase& destination) const override
+    {
+        export2<StringData>(destination);
+    }
+    REALM_FORCEINLINE void export_BinaryData(ValueBase& destination) const override
+    {
+        export2<BinaryData>(destination);
+    }
+    REALM_FORCEINLINE void export_RowIndex(ValueBase& destination) const override
+    {
+        export2<RowIndex>(destination);
+    }
+    REALM_FORCEINLINE void export_null(ValueBase& destination) const override
+    {
+        Value<null>& d = static_cast<Value<null>&>(destination);
+        d.init(m_from_link_list, m_values);
+    }
+
+    REALM_FORCEINLINE void import(const ValueBase& source) override
+    {
+        if (std::is_same<T, int>::value)
+            source.export_int(*this);
+        else if (std::is_same<T, Timestamp>::value)
+            source.export_Timestamp(*this);
+        else if (std::is_same<T, bool>::value)
+            source.export_bool(*this);
+        else if (std::is_same<T, float>::value)
+            source.export_float(*this);
+        else if (std::is_same<T, double>::value)
+            source.export_double(*this);
+        else if (std::is_same<T, int64_t>::value || std::is_same<T, bool>::value ||
+                 std::is_same<T, OldDateTime>::value)
+            source.export_int64_t(*this);
+        else if (std::is_same<T, StringData>::value)
+            source.export_StringData(*this);
+        else if (std::is_same<T, BinaryData>::value)
+            source.export_BinaryData(*this);
+        else if (std::is_same<T, RowIndex>::value)
+            source.export_RowIndex(*this);
+        else if (std::is_same<T, null>::value)
+            source.export_null(*this);
+        else
+            REALM_ASSERT_DEBUG(false);
+    }
+
+    // Given a TCond (==, !=, >, <, >=, <=) and two Value<T>, return index of first match
+    template <class TCond>
+    REALM_FORCEINLINE static size_t compare(Value<T>* left, Value<T>* right)
+    {
+        TCond c;
+
+        if (!left->m_from_link_list && !right->m_from_link_list) {
+            // Compare values one-by-one (one value is one row; no link lists)
+            size_t min = minimum(left->ValueBase::m_values, right->ValueBase::m_values);
+            for (size_t m = 0; m < min; m++) {
+
+                if (c(left->m_storage[m], right->m_storage[m], left->m_storage.is_null(m),
+                      right->m_storage.is_null(m)))
+                    return m;
+            }
+        }
+        else if (left->m_from_link_list && right->m_from_link_list) {
+            // FIXME: Many-to-many links not supported yet. Need to specify behaviour
+            REALM_ASSERT_DEBUG(false);
+        }
+        else if (!left->m_from_link_list && right->m_from_link_list) {
+            // Right values come from link list. Left must come from single row. Semantics: Match if at least 1
+            // linked-to-value fulfills the condition
+            REALM_ASSERT_DEBUG(left->m_values > 0);
+            for (size_t r = 0; r < right->m_values; r++) {
+                if (c(left->m_storage[0], right->m_storage[r], left->m_storage.is_null(0),
+                      right->m_storage.is_null(r)))
+                    return 0;
+            }
+        }
+        else if (left->m_from_link_list && !right->m_from_link_list) {
+            // Same as above, but with left values coming from link list.
+            REALM_ASSERT_DEBUG(right->m_values > 0);
+            for (size_t l = 0; l < left->m_values; l++) {
+                if (c(left->m_storage[l], right->m_storage[0], left->m_storage.is_null(l),
+                      right->m_storage.is_null(0)))
+                    return 0;
+            }
+        }
+
+        return not_found; // no match
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches*) const override
+    {
+        return make_subexpr<Value<T>>(*this);
+    }
+
+    NullableVector<T> m_storage;
+};
+
+class ConstantStringValue : public Value<StringData> {
+public:
+    ConstantStringValue(const StringData& string)
+        : Value()
+        , m_string(string.is_null() ? util::none : util::make_optional(std::string(string)))
+    {
+        init(false, ValueBase::default_size, m_string);
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches*) const override
+    {
+        return std::unique_ptr<Subexpr>(new ConstantStringValue(*this));
+    }
+
+private:
+    ConstantStringValue(const ConstantStringValue& other)
+        : Value()
+        , m_string(other.m_string)
+    {
+        init(other.m_from_link_list, other.m_values, m_string);
+    }
+
+    util::Optional<std::string> m_string;
+};
+
+// All overloads where left-hand-side is L:
+//
+// left-hand-side       operator                              right-hand-side
+// L                    +, -, *, /, <, >, ==, !=, <=, >=      Subexpr2<R>
+//
+// For L = R = {int, int64_t, float, double, Timestamp}:
+// Compare numeric values
+template <class R>
+Query operator>(double left, const Subexpr2<R>& right)
+{
+    return create<Greater>(left, right);
+}
+template <class R>
+Query operator>(float left, const Subexpr2<R>& right)
+{
+    return create<Greater>(left, right);
+}
+template <class R>
+Query operator>(int left, const Subexpr2<R>& right)
+{
+    return create<Greater>(left, right);
+}
+template <class R>
+Query operator>(int64_t left, const Subexpr2<R>& right)
+{
+    return create<Greater>(left, right);
+}
+template <class R>
+Query operator>(Timestamp left, const Subexpr2<R>& right)
+{
+    return create<Greater>(left, right);
+}
+
+template <class R>
+Query operator<(double left, const Subexpr2<R>& right)
+{
+    return create<Less>(left, right);
+}
+template <class R>
+Query operator<(float left, const Subexpr2<R>& right)
+{
+    return create<Less>(left, right);
+}
+template <class R>
+Query operator<(int left, const Subexpr2<R>& right)
+{
+    return create<Less>(left, right);
+}
+template <class R>
+Query operator<(int64_t left, const Subexpr2<R>& right)
+{
+    return create<Less>(left, right);
+}
+template <class R>
+Query operator<(Timestamp left, const Subexpr2<R>& right)
+{
+    return create<Less>(left, right);
+}
+template <class R>
+Query operator==(double left, const Subexpr2<R>& right)
+{
+    return create<Equal>(left, right);
+}
+template <class R>
+Query operator==(float left, const Subexpr2<R>& right)
+{
+    return create<Equal>(left, right);
+}
+template <class R>
+Query operator==(int left, const Subexpr2<R>& right)
+{
+    return create<Equal>(left, right);
+}
+template <class R>
+Query operator==(int64_t left, const Subexpr2<R>& right)
+{
+    return create<Equal>(left, right);
+}
+template <class R>
+Query operator==(Timestamp left, const Subexpr2<R>& right)
+{
+    return create<Equal>(left, right);
+}
+template <class R>
+Query operator>=(double left, const Subexpr2<R>& right)
+{
+    return create<GreaterEqual>(left, right);
+}
+template <class R>
+Query operator>=(float left, const Subexpr2<R>& right)
+{
+    return create<GreaterEqual>(left, right);
+}
+template <class R>
+Query operator>=(int left, const Subexpr2<R>& right)
+{
+    return create<GreaterEqual>(left, right);
+}
+template <class R>
+Query operator>=(int64_t left, const Subexpr2<R>& right)
+{
+    return create<GreaterEqual>(left, right);
+}
+template <class R>
+Query operator>=(Timestamp left, const Subexpr2<R>& right)
+{
+    return create<GreaterEqual>(left, right);
+}
+template <class R>
+Query operator<=(double left, const Subexpr2<R>& right)
+{
+    return create<LessEqual>(left, right);
+}
+template <class R>
+Query operator<=(float left, const Subexpr2<R>& right)
+{
+    return create<LessEqual>(left, right);
+}
+template <class R>
+Query operator<=(int left, const Subexpr2<R>& right)
+{
+    return create<LessEqual>(left, right);
+}
+template <class R>
+Query operator<=(int64_t left, const Subexpr2<R>& right)
+{
+    return create<LessEqual>(left, right);
+}
+template <class R>
+Query operator<=(Timestamp left, const Subexpr2<R>& right)
+{
+    return create<LessEqual>(left, right);
+}
+template <class R>
+Query operator!=(double left, const Subexpr2<R>& right)
+{
+    return create<NotEqual>(left, right);
+}
+template <class R>
+Query operator!=(float left, const Subexpr2<R>& right)
+{
+    return create<NotEqual>(left, right);
+}
+template <class R>
+Query operator!=(int left, const Subexpr2<R>& right)
+{
+    return create<NotEqual>(left, right);
+}
+template <class R>
+Query operator!=(int64_t left, const Subexpr2<R>& right)
+{
+    return create<NotEqual>(left, right);
+}
+template <class R>
+Query operator!=(Timestamp left, const Subexpr2<R>& right)
+{
+    return create<NotEqual>(left, right);
+}
+
+// Arithmetic
+template <class R>
+Operator<Plus<typename Common<R, double>::type>> operator+(double left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<double>>(left), right.clone()};
+}
+template <class R>
+Operator<Plus<typename Common<R, float>::type>> operator+(float left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<float>>(left), right.clone()};
+}
+template <class R>
+Operator<Plus<typename Common<R, int>::type>> operator+(int left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int>>(left), right.clone()};
+}
+template <class R>
+Operator<Plus<typename Common<R, int64_t>::type>> operator+(int64_t left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int64_t>>(left), right.clone()};
+}
+template <class R>
+Operator<Minus<typename Common<R, double>::type>> operator-(double left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<double>>(left), right.clone()};
+}
+template <class R>
+Operator<Minus<typename Common<R, float>::type>> operator-(float left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<float>>(left), right.clone()};
+}
+template <class R>
+Operator<Minus<typename Common<R, int>::type>> operator-(int left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int>>(left), right.clone()};
+}
+template <class R>
+Operator<Minus<typename Common<R, int64_t>::type>> operator-(int64_t left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int64_t>>(left), right.clone()};
+}
+template <class R>
+Operator<Mul<typename Common<R, double>::type>> operator*(double left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<double>>(left), right.clone()};
+}
+template <class R>
+Operator<Mul<typename Common<R, float>::type>> operator*(float left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<float>>(left), right.clone()};
+}
+template <class R>
+Operator<Mul<typename Common<R, int>::type>> operator*(int left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int>>(left), right.clone()};
+}
+template <class R>
+Operator<Mul<typename Common<R, int64_t>::type>> operator*(int64_t left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int64_t>>(left), right.clone()};
+}
+template <class R>
+Operator<Div<typename Common<R, double>::type>> operator/(double left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<double>>(left), right.clone()};
+}
+template <class R>
+Operator<Div<typename Common<R, float>::type>> operator/(float left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<float>>(left), right.clone()};
+}
+template <class R>
+Operator<Div<typename Common<R, int>::type>> operator/(int left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int>>(left), right.clone()};
+}
+template <class R>
+Operator<Div<typename Common<R, int64_t>::type>> operator/(int64_t left, const Subexpr2<R>& right)
+{
+    return {make_subexpr<Value<int64_t>>(left), right.clone()};
+}
+
+// Unary operators
+template <class T>
+UnaryOperator<Pow<T>> power(const Subexpr2<T>& left)
+{
+    return {left.clone()};
+}
+
+// Classes used for LinkMap (see below).
+struct LinkMapFunction {
+    // Your consume() method is given row index of the linked-to table as argument, and you must return whether or
+    // not you want the LinkMapFunction to exit (return false) or continue (return true) harvesting the link tree
+    // for the current main table row index (it will be a link tree if you have multiple type_LinkList columns
+    // in a link()->link() query.
+    virtual bool consume(size_t row_index) = 0;
+};
+
+struct FindNullLinks : public LinkMapFunction {
+    bool consume(size_t row_index) override
+    {
+        static_cast<void>(row_index);
+        m_has_link = true;
+        return false; // we've found a row index, so this can't be a null-link, so exit link harvesting
+    }
+
+    bool m_has_link = false;
+};
+
+struct MakeLinkVector : public LinkMapFunction {
+    MakeLinkVector(std::vector<size_t>& result)
+        : m_links(result)
+    {
+    }
+
+    bool consume(size_t row_index) override
+    {
+        m_links.push_back(row_index);
+        return true; // continue evaluation
+    }
+    std::vector<size_t>& m_links;
+};
+
+struct CountLinks : public LinkMapFunction {
+    bool consume(size_t) override
+    {
+        m_link_count++;
+        return true;
+    }
+
+    size_t result() const
+    {
+        return m_link_count;
+    }
+
+    size_t m_link_count = 0;
+};
+
+
+/*
+The LinkMap and LinkMapFunction classes are used for query conditions on links themselves (contrary to conditions on
+the value payload they point at).
+
+MapLink::map_links() takes a row index of the link column as argument and follows any link chain stated in the query
+(through the link()->link() methods) until the final payload table is reached, and then applies LinkMapFunction on
+the linked-to row index(es).
+
+If all link columns are type_Link, then LinkMapFunction is only invoked for a single row index. If one or more
+columns are type_LinkList, then it may result in multiple row indexes.
+
+The reason we use this map pattern is that we can exit the link-tree-traversal as early as possible, e.g. when we've
+found the first link that points to row '5'. Other solutions could be a std::vector<size_t> harvest_all_links(), or an
+iterator pattern. First solution can't exit, second solution requires internal state.
+*/
+class LinkMap {
+public:
+    LinkMap() = default;
+    LinkMap(const Table* table, std::vector<size_t> columns)
+        : m_link_column_indexes(std::move(columns))
+    {
+        set_base_table(table);
+    }
+
+    LinkMap(LinkMap const& other, QueryNodeHandoverPatches* patches)
+        : LinkMap(other)
+    {
+        if (!patches)
+            return;
+
+        m_link_column_indexes.clear();
+        const Table* table = base_table();
+        m_tables.clear();
+        for (auto column : m_link_columns) {
+            m_link_column_indexes.push_back(column->get_column_index());
+            if (table->get_real_column_type(m_link_column_indexes.back()) == col_type_BackLink)
+                table = &static_cast<const BacklinkColumn*>(column)->get_origin_table();
+            else
+                table = &static_cast<const LinkColumnBase*>(column)->get_target_table();
+        }
+    }
+
+    void set_base_table(const Table* table)
+    {
+        if (table == base_table())
+            return;
+
+        m_tables.clear();
+        m_tables.push_back(table);
+        m_link_columns.clear();
+        m_link_types.clear();
+        m_only_unary_links = true;
+
+        for (size_t link_column_index : m_link_column_indexes) {
+            // Link column can be either LinkList or single Link
+            const Table* t = m_tables.back();
+            ColumnType type = t->get_real_column_type(link_column_index);
+            REALM_ASSERT(Table::is_link_type(type) || type == col_type_BackLink);
+            m_link_types.push_back(type);
+
+            if (type == col_type_LinkList) {
+                const LinkListColumn& cll = t->get_column_link_list(link_column_index);
+                m_link_columns.push_back(&cll);
+                m_only_unary_links = false;
+                m_tables.push_back(&cll.get_target_table());
+            }
+            else if (type == col_type_Link) {
+                const LinkColumn& cl = t->get_column_link(link_column_index);
+                m_link_columns.push_back(&cl);
+                m_tables.push_back(&cl.get_target_table());
+            }
+            else if (type == col_type_BackLink) {
+                const BacklinkColumn& bl = t->get_column_backlink(link_column_index);
+                m_link_columns.push_back(&bl);
+                m_only_unary_links = false;
+                m_tables.push_back(&bl.get_origin_table());
+            }
+        }
+    }
+
+    void verify_columns() const
+    {
+        for (size_t i = 0; i < m_link_column_indexes.size(); i++) {
+            m_tables[i]->verify_column(m_link_column_indexes[i], m_link_columns[i]);
+        }
+    }
+
+    virtual std::string description() const
+    {
+        std::string s;
+        for (size_t i = 0; i < m_link_column_indexes.size(); ++i) {
+            if (i < m_tables.size() && m_tables[i]) {
+                if (m_link_types[i] == col_type_BackLink) {
+                    s += "backlink";
+                } else if (m_link_column_indexes[i] < m_tables[i]->get_column_count()) {
+                    s += std::string(m_tables[i]->get_column_name(m_link_column_indexes[i]));
+                }
+                if (i != m_link_column_indexes.size() - 1) {
+                    s += util::serializer::value_separator;
+                }
+            }
+        }
+        return s;
+    }
+
+    std::vector<size_t> get_links(size_t index)
+    {
+        std::vector<size_t> res;
+        get_links(index, res);
+        return res;
+    }
+
+    size_t count_links(size_t row)
+    {
+        CountLinks counter;
+        map_links(row, counter);
+        return counter.result();
+    }
+
+    void map_links(size_t row, LinkMapFunction& lm)
+    {
+        map_links(0, row, lm);
+    }
+
+    bool only_unary_links() const
+    {
+        return m_only_unary_links;
+    }
+
+    const Table* base_table() const
+    {
+        return m_tables.empty() ? nullptr : m_tables[0];
+    }
+
+    const Table* target_table() const
+    {
+        REALM_ASSERT(!m_tables.empty());
+        return m_tables.back();
+    }
+
+    std::vector<const ColumnBase*> m_link_columns;
+
+private:
+    void map_links(size_t column, size_t row, LinkMapFunction& lm)
+    {
+        bool last = (column + 1 == m_link_columns.size());
+        ColumnType type = m_link_types[column];
+        if (type == col_type_Link) {
+            const LinkColumn& cl = *static_cast<const LinkColumn*>(m_link_columns[column]);
+            size_t r = to_size_t(cl.get(row));
+            if (r == 0)
+                return;
+            r--; // LinkColumn stores link to row N as N + 1
+            if (last) {
+                bool continue2 = lm.consume(r);
+                if (!continue2)
+                    return;
+            }
+            else
+                map_links(column + 1, r, lm);
+        }
+        else if (type == col_type_LinkList) {
+            const LinkListColumn& cll = *static_cast<const LinkListColumn*>(m_link_columns[column]);
+            ConstLinkViewRef lvr = cll.get(row);
+            for (size_t t = 0; t < lvr->size(); t++) {
+                size_t r = lvr->get(t).get_index();
+                if (last) {
+                    bool continue2 = lm.consume(r);
+                    if (!continue2)
+                        return;
+                }
+                else
+                    map_links(column + 1, r, lm);
+            }
+        }
+        else if (type == col_type_BackLink) {
+            const BacklinkColumn& bl = *static_cast<const BacklinkColumn*>(m_link_columns[column]);
+            size_t count = bl.get_backlink_count(row);
+            for (size_t i = 0; i < count; ++i) {
+                size_t r = bl.get_backlink(row, i);
+                if (last) {
+                    bool continue2 = lm.consume(r);
+                    if (!continue2)
+                        return;
+                }
+                else
+                    map_links(column + 1, r, lm);
+            }
+        }
+    }
+
+
+    void get_links(size_t row, std::vector<size_t>& result)
+    {
+        MakeLinkVector mlv = MakeLinkVector(result);
+        map_links(row, mlv);
+    }
+
+    std::vector<size_t> m_link_column_indexes;
+    std::vector<ColumnType> m_link_types;
+    std::vector<const Table*> m_tables;
+    bool m_only_unary_links = true;
+
+    template <class>
+    friend Query compare(const Subexpr2<Link>&, const ConstRow&);
+};
+
+template <class T, class S, class I>
+Query string_compare(const Subexpr2<StringData>& left, T right, bool case_insensitive);
+template <class S, class I>
+Query string_compare(const Subexpr2<StringData>& left, const Subexpr2<StringData>& right, bool case_insensitive);
+
+template <class T>
+Value<T> make_value_for_link(bool only_unary_links, size_t size)
+{
+    Value<T> value;
+    if (only_unary_links) {
+        REALM_ASSERT(size <= 1);
+        value.init(false, 1);
+        value.m_storage.set_null(0);
+    }
+    else {
+        value.init(true, size);
+    }
+    return value;
+}
+
+
+// If we add a new Realm type T and quickly want Query support for it, then simply inherit from it like
+// `template <> class Columns<T> : public SimpleQuerySupport<T>` and you're done. Any operators of the set
+// { ==, >=, <=, !=, >, < } that are supported by T will be supported by the "query expression syntax"
+// automatically. NOTE: This method of Query support will be slow because it goes through Table::get<T>.
+// To get faster Query support, either add SequentialGetter support (faster) or create a query_engine.hpp
+// node for it (super fast).
+
+template <class T>
+class SimpleQuerySupport : public Subexpr2<T> {
+public:
+    SimpleQuerySupport(size_t column, const Table* table, std::vector<size_t> links = {})
+        : m_column_ndx(column)
+        , m_link_map(table, std::move(links))
+    {
+        m_column = &m_link_map.target_table()->get_column_base(m_column_ndx);
+    }
+
+    bool is_nullable() const noexcept
+    {
+        return m_link_map.base_table()->is_nullable(m_column->get_column_index());
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        if (table != get_base_table()) {
+            m_link_map.set_base_table(table);
+            m_column = &m_link_map.target_table()->get_column_base(m_column_ndx);
+        }
+    }
+
+    void verify_column() const override
+    {
+        // verify links
+        m_link_map.verify_columns();
+        // verify target table
+        const Table* target_table = m_link_map.target_table();
+        if (target_table && m_column_ndx != npos) {
+            target_table->verify_column(m_column_ndx, m_column);
+        }
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        Value<T>& d = static_cast<Value<T>&>(destination);
+        size_t col = column_ndx();
+
+        if (links_exist()) {
+            std::vector<size_t> links = m_link_map.get_links(index);
+            Value<T> v = make_value_for_link<T>(m_link_map.only_unary_links(), links.size());
+
+            for (size_t t = 0; t < links.size(); t++) {
+                size_t link_to = links[t];
+                v.m_storage.set(t, m_link_map.target_table()->template get<T>(col, link_to));
+            }
+            destination.import(v);
+        }
+        else {
+            // Not a link column
+            const Table* target_table = m_link_map.target_table();
+            for (size_t t = 0; t < destination.m_values && index + t < target_table->size(); t++) {
+                d.m_storage.set(t, target_table->get<T>(col, index + t));
+            }
+        }
+    }
+
+    bool links_exist() const
+    {
+        return m_link_map.m_link_columns.size() > 0;
+    }
+
+    virtual std::string description() const override
+    {
+        std::string desc;
+        if (links_exist()) {
+            desc = m_link_map.description() + util::serializer::value_separator;
+        }
+        const Table* target_table = m_link_map.target_table();
+        if (target_table && target_table->is_attached()) {
+            desc += std::string(target_table->get_column_name(m_column_ndx));
+        }
+        return desc;
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches = nullptr) const override
+    {
+        return make_subexpr<Columns<T>>(static_cast<const Columns<T>&>(*this), patches);
+    }
+
+    SimpleQuerySupport(SimpleQuerySupport const& other, QueryNodeHandoverPatches* patches)
+        : Subexpr2<T>(other)
+        , m_column_ndx(other.m_column_ndx)
+        , m_column(other.m_column)
+        , m_link_map(other.m_link_map, patches)
+    {
+        if (patches && m_column) {
+            m_column_ndx = column_ndx();
+            m_column = nullptr;
+        }
+    }
+
+    size_t column_ndx() const
+    {
+        return m_column->get_column_index();
+    }
+
+    SizeOperator<Size<T>> size()
+    {
+        return SizeOperator<Size<T>>(this->clone(nullptr));
+    }
+
+private:
+    // Column index of payload column of m_table
+    mutable size_t m_column_ndx;
+    const ColumnBase* m_column;
+    LinkMap m_link_map;
+};
+
+
+template <>
+class Columns<Timestamp> : public SimpleQuerySupport<Timestamp> {
+    using SimpleQuerySupport::SimpleQuerySupport;
+};
+
+template <>
+class Columns<BinaryData> : public SimpleQuerySupport<BinaryData> {
+    using SimpleQuerySupport::SimpleQuerySupport;
+};
+
+template <>
+class Columns<StringData> : public SimpleQuerySupport<StringData> {
+public:
+    Columns(size_t column, const Table* table, std::vector<size_t> links = {})
+        : SimpleQuerySupport(column, table, links)
+    {
+    }
+
+    Columns(Columns const& other, QueryNodeHandoverPatches* patches = nullptr)
+        : SimpleQuerySupport(other, patches)
+    {
+    }
+
+    Columns(Columns&& other)
+        : SimpleQuerySupport(other)
+    {
+    }
+};
+
+template <class T, class S, class I>
+Query string_compare(const Subexpr2<StringData>& left, T right, bool case_sensitive)
+{
+    StringData sd(right);
+    if (case_sensitive)
+        return create<S>(sd, left);
+    else
+        return create<I>(sd, left);
+}
+
+template <class S, class I>
+Query string_compare(const Subexpr2<StringData>& left, const Subexpr2<StringData>& right, bool case_sensitive)
+{
+    if (case_sensitive)
+        return make_expression<Compare<S, StringData>>(right.clone(), left.clone());
+    else
+        return make_expression<Compare<I, StringData>>(right.clone(), left.clone());
+}
+
+// Columns<String> == Columns<String>
+inline Query operator==(const Columns<StringData>& left, const Columns<StringData>& right)
+{
+    return string_compare<Equal, EqualIns>(left, right, true);
+}
+
+// Columns<String> != Columns<String>
+inline Query operator!=(const Columns<StringData>& left, const Columns<StringData>& right)
+{
+    return string_compare<NotEqual, NotEqualIns>(left, right, true);
+}
+
+// String == Columns<String>
+template <class T>
+Query operator==(T left, const Columns<StringData>& right)
+{
+    return operator==(right, left);
+}
+
+// String != Columns<String>
+template <class T>
+Query operator!=(T left, const Columns<StringData>& right)
+{
+    return operator!=(right, left);
+}
+
+// Columns<String> == String
+template <class T>
+Query operator==(const Columns<StringData>& left, T right)
+{
+    return string_compare<T, Equal, EqualIns>(left, right, true);
+}
+
+// Columns<String> != String
+template <class T>
+Query operator!=(const Columns<StringData>& left, T right)
+{
+    return string_compare<T, NotEqual, NotEqualIns>(left, right, true);
+}
+
+
+inline Query operator==(const Columns<BinaryData>& left, BinaryData right)
+{
+    return create<Equal>(right, left);
+}
+
+inline Query operator==(BinaryData left, const Columns<BinaryData>& right)
+{
+    return create<Equal>(left, right);
+}
+
+inline Query operator!=(const Columns<BinaryData>& left, BinaryData right)
+{
+    return create<NotEqual>(right, left);
+}
+
+inline Query operator!=(BinaryData left, const Columns<BinaryData>& right)
+{
+    return create<NotEqual>(left, right);
+}
+
+
+// This class is intended to perform queries on the *pointers* of links, contrary to performing queries on *payload*
+// in linked-to tables. Queries can be "find first link that points at row X" or "find first null-link". Currently
+// only "find first null link" and "find first non-null link" is supported. More will be added later. When we add
+// more, I propose to remove the <bool has_links> template argument from this class and instead template it by
+// a criteria-class (like the FindNullLinks class below in find_first()) in some generalized fashion.
+template <bool has_links>
+class UnaryLinkCompare : public Expression {
+public:
+    UnaryLinkCompare(LinkMap lm)
+        : m_link_map(std::move(lm))
+    {
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_link_map.set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_link_map.verify_columns();
+    }
+
+    // Return main table of query (table on which table->where()... is invoked). Note that this is not the same as
+    // any linked-to payload tables
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    size_t find_first(size_t start, size_t end) const override
+    {
+        for (; start < end;) {
+            FindNullLinks fnl;
+            m_link_map.map_links(start, fnl);
+            if (fnl.m_has_link == has_links)
+                return start;
+
+            start++;
+        }
+
+        return not_found;
+    }
+
+    virtual std::string description() const override
+    {
+        return m_link_map.description() + (has_links ? " != NULL" : " == NULL");
+    }
+
+    std::unique_ptr<Expression> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return std::unique_ptr<Expression>(new UnaryLinkCompare(*this, patches));
+    }
+
+private:
+    UnaryLinkCompare(const UnaryLinkCompare& other, QueryNodeHandoverPatches* patches = nullptr)
+        : Expression(other)
+        , m_link_map(other.m_link_map, patches)
+    {
+    }
+
+    mutable LinkMap m_link_map;
+};
+
+class LinkCount : public Subexpr2<Int> {
+public:
+    LinkCount(LinkMap link_map)
+        : m_link_map(std::move(link_map))
+    {
+    }
+    LinkCount(LinkCount const& other, QueryNodeHandoverPatches* patches)
+        : Subexpr2<Int>(other)
+        , m_link_map(other.m_link_map, patches)
+    {
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return make_subexpr<LinkCount>(*this, patches);
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_link_map.set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_link_map.verify_columns();
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        size_t count = m_link_map.count_links(index);
+        destination.import(Value<Int>(false, 1, count));
+    }
+
+    virtual std::string description() const override
+    {
+        return m_link_map.description() + util::serializer::value_separator + "@count";
+    }
+
+private:
+    LinkMap m_link_map;
+};
+
+template <class oper, class TExpr>
+class SizeOperator : public Subexpr2<Int> {
+public:
+    SizeOperator(std::unique_ptr<TExpr> left)
+        : m_expr(std::move(left))
+    {
+    }
+
+    // See comment in base class
+    void set_base_table(const Table* table) override
+    {
+        m_expr->set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_expr->verify_column();
+    }
+
+    // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression
+    // and binds it to a Query at a later time
+    const Table* get_base_table() const override
+    {
+        return m_expr->get_base_table();
+    }
+
+    // destination = operator(left)
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        REALM_ASSERT_DEBUG(dynamic_cast<Value<Int>*>(&destination) != nullptr);
+        Value<Int>* d = static_cast<Value<Int>*>(&destination);
+        REALM_ASSERT(d);
+
+        Value<T> v;
+        m_expr->evaluate(index, v);
+
+        size_t sz = v.m_values;
+        d->init(v.m_from_link_list, sz);
+
+        for (size_t i = 0; i < sz; i++) {
+            auto elem = v.m_storage.get(i);
+            if (!elem) {
+                d->m_storage.set_null(i);
+            }
+            else {
+                d->m_storage.set(i, oper()(*elem));
+            }
+        }
+    }
+
+    std::string description() const override
+    {
+        if (m_expr) {
+            return m_expr->description() + util::serializer::value_separator + "@size";
+        }
+        return "@size";
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return std::unique_ptr<Subexpr>(new SizeOperator(*this, patches));
+    }
+
+    void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override
+    {
+        m_expr->apply_handover_patch(patches, group);
+    }
+
+private:
+    SizeOperator(const SizeOperator& other, QueryNodeHandoverPatches* patches)
+        : m_expr(other.m_expr->clone(patches))
+    {
+    }
+
+    typedef typename oper::type T;
+    std::unique_ptr<TExpr> m_expr;
+};
+
+struct ConstantRowValueHandoverPatch : public QueryNodeHandoverPatch {
+    std::unique_ptr<RowBaseHandoverPatch> row_patch;
+};
+
+class ConstantRowValue : public Subexpr2<Link> {
+public:
+    ConstantRowValue(const ConstRow& row)
+        : m_row(row)
+    {
+    }
+
+    void set_base_table(const Table*) override
+    {
+    }
+
+    void verify_column() const override
+    {
+    }
+
+    const Table* get_base_table() const override
+    {
+        return nullptr;
+    }
+
+    void evaluate(size_t, ValueBase& destination) override
+    {
+        if (m_row.is_attached()) {
+            Value<RowIndex> v(RowIndex(m_row.get_index()));
+            destination.import(v);
+        }
+        else {
+            Value<RowIndex> v(RowIndex::Detached);
+            destination.import(v);
+        }
+    }
+
+    virtual std::string description() const override
+    {
+        if (!m_row.is_attached()) {
+            return util::serializer::print_value("detached object");
+        }
+        return util::serializer::print_value(m_row.get_index());
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return std::unique_ptr<Subexpr>(new ConstantRowValue(*this, patches));
+    }
+
+    void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override
+    {
+        REALM_ASSERT(patches.size());
+        std::unique_ptr<QueryNodeHandoverPatch> abstract_patch = std::move(patches.back());
+        patches.pop_back();
+
+        auto patch = dynamic_cast<ConstantRowValueHandoverPatch*>(abstract_patch.get());
+        REALM_ASSERT(patch);
+
+        m_row.apply_and_consume_patch(patch->row_patch, group);
+    }
+
+private:
+    ConstantRowValue(const ConstantRowValue& source, QueryNodeHandoverPatches* patches)
+        : m_row(patches ? ConstRow() : source.m_row)
+    {
+        if (!patches)
+            return;
+
+        std::unique_ptr<ConstantRowValueHandoverPatch> patch(new ConstantRowValueHandoverPatch);
+        ConstRow::generate_patch(source.m_row, patch->row_patch);
+        patches->emplace_back(patch.release());
+    }
+
+    ConstRow m_row;
+};
+
+template <typename T>
+class SubColumns;
+
+// This is for LinkList and BackLink too since they're declared as typedefs of Link.
+template <>
+class Columns<Link> : public Subexpr2<Link> {
+public:
+    Query is_null()
+    {
+        if (m_link_map.m_link_columns.size() > 1)
+            throw std::runtime_error("Combining link() and is_null() is currently not supported");
+        // Todo, it may be useful to support the above, but we would need to figure out an intuitive behaviour
+        return make_expression<UnaryLinkCompare<false>>(m_link_map);
+    }
+
+    Query is_not_null()
+    {
+        if (m_link_map.m_link_columns.size() > 1)
+            throw std::runtime_error("Combining link() and is_not_null() is currently not supported");
+        // Todo, it may be useful to support the above, but we would need to figure out an intuitive behaviour
+        return make_expression<UnaryLinkCompare<true>>(m_link_map);
+    }
+
+    LinkCount count() const
+    {
+        return LinkCount(m_link_map);
+    }
+
+    template <typename C>
+    SubColumns<C> column(size_t column_ndx) const
+    {
+        return SubColumns<C>(Columns<C>(column_ndx, m_link_map.target_table()), m_link_map);
+    }
+
+    const LinkMap& link_map() const
+    {
+        return m_link_map;
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+    void set_base_table(const Table* table) override
+    {
+        m_link_map.set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_link_map.verify_columns();
+    }
+
+    std::string description() const override
+    {
+        return m_link_map.description();
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return std::unique_ptr<Subexpr>(new Columns<Link>(*this, patches));
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override;
+
+
+private:
+    LinkMap m_link_map;
+    friend class Table;
+
+    Columns(size_t column_ndx, const Table* table, const std::vector<size_t>& links = {})
+        : m_link_map(table, links)
+    {
+        static_cast<void>(column_ndx);
+    }
+    Columns(const Columns& other, QueryNodeHandoverPatches* patches)
+        : Subexpr2<Link>(other)
+        , m_link_map(other.m_link_map, patches)
+    {
+    }
+};
+
+template <typename T>
+class ListColumns;
+template <typename T, typename Operation>
+class ListColumnAggregate;
+namespace aggregate_operations {
+template <typename T>
+class Minimum;
+template <typename T>
+class Maximum;
+template <typename T>
+class Sum;
+template <typename T>
+class Average;
+}
+
+template <>
+class Columns<SubTable> : public Subexpr2<SubTable> {
+public:
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_link_map.set_base_table(table);
+        m_column = &m_link_map.target_table()->get_column_table(m_column_ndx);
+    }
+
+    void verify_column() const override
+    {
+        m_link_map.verify_columns();
+        m_link_map.target_table()->verify_column(m_column_ndx, m_column);
+    }
+
+    std::string description() const override
+    {
+        return m_link_map.description();
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return std::unique_ptr<Subexpr>(new Columns<SubTable>(*this, patches));
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        evaluate_internal(index, destination, ValueBase::default_size);
+    }
+
+    void evaluate_internal(size_t index, ValueBase& destination, size_t nb_elements);
+
+    template <typename T>
+    ListColumns<T> column(size_t ndx) const
+    {
+        return ListColumns<T>(ndx, Columns<SubTable>(*this, nullptr));
+    }
+
+    template <typename T>
+    ListColumns<T> list() const
+    {
+        return column<T>(0);
+    }
+
+    SizeOperator<Size<ConstTableRef>> size()
+    {
+        return SizeOperator<Size<ConstTableRef>>(this->clone(nullptr));
+    }
+
+private:
+    LinkMap m_link_map;
+    size_t m_column_ndx;
+    const SubtableColumn* m_column = nullptr;
+    friend class Table;
+    template <class T>
+    friend class ListColumnsBase;
+    template <class T, class U>
+    friend class ListColumnAggregate;
+
+    Columns(size_t column_ndx, const Table* table, const std::vector<size_t>& links = {})
+        : m_link_map(table, links)
+        , m_column_ndx(column_ndx)
+        , m_column(&m_link_map.target_table()->get_column_table(column_ndx))
+    {
+    }
+
+    Columns(const Columns<SubTable>& other, QueryNodeHandoverPatches* patches)
+        : Subexpr2<SubTable>(other)
+        , m_link_map(other.m_link_map, patches)
+        , m_column_ndx(other.m_column_ndx)
+        , m_column(other.m_column)
+    {
+        if (m_column && patches)
+            m_column_ndx = m_column->get_column_index();
+    }
+};
+
+template <typename T>
+class ListColumnsBase : public Subexpr2<T> {
+public:
+    ListColumnsBase(size_t column_ndx, Columns<SubTable> column)
+        : m_column_ndx(column_ndx)
+        , m_subtable_column(std::move(column))
+    {
+    }
+
+    ListColumnsBase(const ListColumnsBase& other, QueryNodeHandoverPatches* patches)
+        : m_column_ndx(other.m_column_ndx)
+        , m_subtable_column(other.m_subtable_column, patches)
+    {
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return make_subexpr<ListColumns<T>>(*this, patches);
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_subtable_column.get_base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_subtable_column.set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_subtable_column.verify_column();
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        Value<ConstTableRef> subtables;
+        m_subtable_column.evaluate_internal(index, subtables, 1);
+        size_t sz = 0;
+        for (size_t i = 0; i < subtables.m_values; i++) {
+            auto val = subtables.m_storage[i];
+            if (val)
+                sz += val->size();
+        }
+        auto v = make_value_for_link<typename util::RemoveOptional<T>::type>(false, sz);
+        size_t k = 0;
+        for (size_t i = 0; i < subtables.m_values; i++) {
+            auto table = subtables.m_storage[i];
+            if (table) {
+                size_t s = table->size();
+                for (size_t j = 0; j < s; j++) {
+                    if (!table->is_null(m_column_ndx, j)) {
+                        v.m_storage.set(k++, table->get<T>(m_column_ndx, j));
+                    }
+                }
+            }
+        }
+        destination.import(v);
+    }
+
+    virtual std::string description() const override
+    {
+        const Table* table = get_base_table();
+        if (table && table->is_attached()) {
+            if (m_subtable_column.m_column) {
+                return std::string(table->get_column_name(m_subtable_column.m_column_ndx));
+
+            }
+            else {
+                return std::string(table->get_column_name(m_column_ndx));
+            }
+        }
+        return "";
+    }
+
+    ListColumnAggregate<T, aggregate_operations::Minimum<T>> min() const
+    {
+        return {m_column_ndx, m_subtable_column};
+    }
+
+    ListColumnAggregate<T, aggregate_operations::Maximum<T>> max() const
+    {
+        return {m_column_ndx, m_subtable_column};
+    }
+
+    ListColumnAggregate<T, aggregate_operations::Sum<T>> sum() const
+    {
+        return {m_column_ndx, m_subtable_column};
+    }
+
+    ListColumnAggregate<T, aggregate_operations::Average<T>> average() const
+    {
+        return {m_column_ndx, m_subtable_column};
+    }
+
+
+private:
+    // Storing the column index here could be a potential problem if the column
+    // changes id due to insertion/deletion.
+    size_t m_column_ndx;
+    Columns<SubTable> m_subtable_column;
+};
+
+template <class T>
+class ListColumns : public ListColumnsBase<T> {
+public:
+    using ListColumnsBase<T>::ListColumnsBase;
+};
+
+template <>
+class ListColumns<StringData> : public ListColumnsBase<StringData> {
+public:
+    ListColumns(size_t column_ndx, Columns<SubTable> column)
+        : ListColumnsBase(column_ndx, column)
+    {
+    }
+
+    ListColumns(const ListColumnsBase& other, QueryNodeHandoverPatches* patches)
+        : ListColumnsBase(other, patches)
+    {
+    }
+
+    ListColumns(ListColumns&& other)
+        : ListColumnsBase(other)
+    {
+    }
+};
+
+template <typename T, typename Operation>
+class ListColumnAggregate : public Subexpr2<typename Operation::ResultType> {
+public:
+    using R = typename Operation::ResultType;
+
+    ListColumnAggregate(size_t column_ndx, Columns<SubTable> column)
+        : m_column_ndx(column_ndx)
+        , m_subtable_column(std::move(column))
+    {
+    }
+
+    ListColumnAggregate(const ListColumnAggregate& other, QueryNodeHandoverPatches* patches)
+        : m_column_ndx(other.m_column_ndx)
+        , m_subtable_column(other.m_subtable_column, patches)
+    {
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return make_subexpr<ListColumnAggregate>(*this, patches);
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_subtable_column.get_base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_subtable_column.set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_subtable_column.verify_column();
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        Value<ConstTableRef> subtables;
+        m_subtable_column.evaluate_internal(index, subtables, 1);
+        REALM_ASSERT_DEBUG(subtables.m_values > 0 || subtables.m_from_link_list);
+        size_t sz = subtables.m_values;
+        // The result is an aggregate value for each table
+        auto v = make_value_for_link<R>(!subtables.m_from_link_list, sz);
+        for (unsigned i = 0; i < sz; i++) {
+            auto table = subtables.m_storage[i];
+            Operation op;
+            if (table) {
+                size_t s = table->size();
+                for (unsigned j = 0; j < s; j++) {
+                    op.accumulate(table->get<T>(m_column_ndx, j));
+                }
+            }
+            if (op.is_null()) {
+                v.m_storage.set_null(i);
+            }
+            else {
+                v.m_storage.set(i, op.result());
+            }
+        }
+        destination.import(v);
+    }
+
+    virtual std::string description() const override
+    {
+        const Table* table = get_base_table();
+        if (table && table->is_attached()) {
+            return std::string(table->get_column_name(m_column_ndx)) + util::serializer::value_separator + Operation::description() + "()";
+        }
+        return "";
+    }
+
+private:
+    size_t m_column_ndx;
+    Columns<SubTable> m_subtable_column;
+};
+
+template <class Operator>
+Query compare(const Subexpr2<Link>& left, const ConstRow& row)
+{
+    static_assert(std::is_same<Operator, Equal>::value || std::is_same<Operator, NotEqual>::value,
+                  "Links can only be compared for equality.");
+    const Columns<Link>* column = dynamic_cast<const Columns<Link>*>(&left);
+    if (column) {
+        const LinkMap& link_map = column->link_map();
+        REALM_ASSERT(link_map.target_table() == row.get_table() || !row.is_attached());
+#ifdef REALM_OLDQUERY_FALLBACK
+        if (link_map.m_link_columns.size() == 1) {
+            // We can fall back to Query::links_to for != and == operations on links, but only
+            // for == on link lists. This is because negating query.links_to() is equivalent to
+            // to "ALL linklist != row" rather than the "ANY linklist != row" semantics we're after.
+            if (link_map.m_link_types[0] == col_type_Link ||
+                (link_map.m_link_types[0] == col_type_LinkList && std::is_same<Operator, Equal>::value)) {
+                const Table* t = column->get_base_table();
+                Query query(*t);
+
+                if (std::is_same<Operator, NotEqual>::value) {
+                    // Negate the following `links_to`.
+                    query.Not();
+                }
+                query.links_to(link_map.m_link_column_indexes[0], row);
+                return query;
+            }
+        }
+#endif
+    }
+    return make_expression<Compare<Operator, RowIndex>>(left.clone(), make_subexpr<ConstantRowValue>(row));
+}
+
+inline Query operator==(const Subexpr2<Link>& left, const ConstRow& row)
+{
+    return compare<Equal>(left, row);
+}
+inline Query operator!=(const Subexpr2<Link>& left, const ConstRow& row)
+{
+    return compare<NotEqual>(left, row);
+}
+inline Query operator==(const ConstRow& row, const Subexpr2<Link>& right)
+{
+    return compare<Equal>(right, row);
+}
+inline Query operator!=(const ConstRow& row, const Subexpr2<Link>& right)
+{
+    return compare<NotEqual>(right, row);
+}
+
+template <class Operator>
+Query compare(const Subexpr2<Link>& left, null)
+{
+    static_assert(std::is_same<Operator, Equal>::value || std::is_same<Operator, NotEqual>::value,
+                  "Links can only be compared for equality.");
+    return make_expression<Compare<Operator, RowIndex>>(left.clone(), make_subexpr<Value<RowIndex>>());
+}
+
+inline Query operator==(const Subexpr2<Link>& left, null)
+{
+    return compare<Equal>(left, null());
+}
+inline Query operator!=(const Subexpr2<Link>& left, null)
+{
+    return compare<NotEqual>(left, null());
+}
+inline Query operator==(null, const Subexpr2<Link>& right)
+{
+    return compare<Equal>(right, null());
+}
+inline Query operator!=(null, const Subexpr2<Link>& right)
+{
+    return compare<NotEqual>(right, null());
+}
+
+
+template <class T>
+class Columns : public Subexpr2<T> {
+public:
+    using ColType = typename ColumnTypeTraits<T>::column_type;
+
+    Columns(size_t column, const Table* table, std::vector<size_t> links = {})
+        : m_link_map(table, std::move(links))
+        , m_column_ndx(column)
+        , m_nullable(m_link_map.target_table()->is_nullable(m_column_ndx))
+    {
+    }
+
+    Columns(const Columns& other, QueryNodeHandoverPatches* patches = nullptr)
+        : m_link_map(other.m_link_map, patches)
+        , m_column_ndx(other.m_column_ndx)
+        , m_nullable(other.m_nullable)
+    {
+        if (!other.m_sg)
+            return;
+
+        if (patches) {
+            m_column_ndx = other.get_column_base().get_column_index();
+        }
+        else {
+            if (m_nullable && std::is_same<typename ColType::value_type, int64_t>::value) {
+                init<IntNullColumn>(&other.get_column_base());
+            }
+            else {
+                init<ColType>(&other.get_column_base());
+            }
+        }
+    }
+
+    Columns& operator=(const Columns& other)
+    {
+        if (this != &other) {
+            m_link_map = other.m_link_map;
+            m_sg.reset();
+            m_column_ndx = other.m_column_ndx;
+            m_nullable = other.m_nullable;
+        }
+        return *this;
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return make_subexpr<Columns<T>>(*this, patches);
+    }
+
+    // See comment in base class
+    void set_base_table(const Table* table) override
+    {
+        if (m_sg && table == get_base_table())
+            return;
+
+        m_link_map.set_base_table(table);
+        m_nullable = m_link_map.target_table()->is_nullable(m_column_ndx);
+
+        const ColumnBase* c = &m_link_map.target_table()->get_column_base(m_column_ndx);
+        if (m_nullable && std::is_same<typename ColType::value_type, int64_t>::value) {
+            init<IntNullColumn>(c);
+        }
+        else {
+            init<ColType>(c);
+        }
+    }
+
+    void verify_column() const override
+    {
+        // verify links
+        m_link_map.verify_columns();
+        // verify target table
+        const Table* target_table = m_link_map.target_table();
+        if (target_table && m_column_ndx != npos) {
+            target_table->verify_column(m_column_ndx, &get_column_base());
+        }
+    }
+
+    template <class ActualColType>
+    void init(const ColumnBase* c)
+    {
+        REALM_ASSERT_DEBUG(dynamic_cast<const ActualColType*>(c));
+        if (m_sg == nullptr) {
+            m_sg.reset(new SequentialGetter<ActualColType>());
+        }
+        static_cast<SequentialGetter<ActualColType>&>(*m_sg).init(static_cast<const ActualColType*>(c));
+    }
+
+    // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression
+    // and binds it to a Query at a later time
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    template <class ColType2 = ColType>
+    void evaluate_internal(size_t index, ValueBase& destination)
+    {
+        REALM_ASSERT_DEBUG(m_sg.get());
+        REALM_ASSERT_DEBUG(dynamic_cast<SequentialGetter<ColType2>*>(m_sg.get()));
+
+        using U = typename ColType2::value_type;
+        auto sgc = static_cast<SequentialGetter<ColType2>*>(m_sg.get());
+        REALM_ASSERT_DEBUG(sgc->m_column);
+
+        if (links_exist()) {
+            // LinkList with more than 0 values. Create Value with payload for all fields
+
+            std::vector<size_t> links = m_link_map.get_links(index);
+            auto v = make_value_for_link<typename util::RemoveOptional<U>::type>(m_link_map.only_unary_links(),
+                                                                                 links.size());
+
+            for (size_t t = 0; t < links.size(); t++) {
+                size_t link_to = links[t];
+                sgc->cache_next(link_to);
+
+                if (sgc->m_column->is_null(link_to))
+                    v.m_storage.set_null(t);
+                else
+                    v.m_storage.set(t, sgc->get_next(link_to));
+            }
+            destination.import(v);
+        }
+        else {
+            // Not a Link column
+            // make sequential getter load the respective leaf to access data at column row 'index'
+            sgc->cache_next(index);
+            size_t colsize = sgc->m_column->size();
+
+            // Now load `ValueBase::default_size` rows from from the leaf into m_storage. If it's an integer
+            // leaf, then it contains the method get_chunk() which copies these values in a super fast way (first
+            // case of the `if` below. Otherwise, copy the values one by one in a for-loop (the `else` case).
+            if (std::is_same<U, int64_t>::value && index + ValueBase::default_size <= sgc->m_leaf_end) {
+                Value<int64_t> v;
+
+                // If you want to modify 'default_size' then update Array::get_chunk()
+                REALM_ASSERT_3(ValueBase::default_size, ==, 8);
+
+                auto sgc_2 = static_cast<SequentialGetter<ColType>*>(m_sg.get());
+                sgc_2->m_leaf_ptr->get_chunk(index - sgc->m_leaf_start, v.m_storage.m_first);
+
+                destination.import(v);
+            }
+            else {
+                size_t rows = colsize - index;
+                if (rows > ValueBase::default_size)
+                    rows = ValueBase::default_size;
+                Value<typename util::RemoveOptional<U>::type> v(false, rows);
+
+                for (size_t t = 0; t < rows; t++)
+                    v.m_storage.set(t, sgc->get_next(index + t));
+
+                destination.import(v);
+            }
+        }
+    }
+
+    virtual std::string description() const override
+    {
+        std::string desc = "";
+        if (links_exist()) {
+            desc = m_link_map.description() + util::serializer::value_separator;
+        }
+        const Table* target_table = m_link_map.target_table();
+        if (target_table && target_table->is_attached() && m_column_ndx != npos) {
+            desc += std::string(target_table->get_column_name(m_column_ndx));
+            return desc;
+        }
+        return "";
+    }
+
+    // Load values from Column into destination
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        if (m_nullable && std::is_same<typename ColType::value_type, int64_t>::value) {
+            evaluate_internal<IntNullColumn>(index, destination);
+        }
+        else {
+            evaluate_internal<ColType>(index, destination);
+        }
+    }
+
+    bool links_exist() const
+    {
+        return m_link_map.m_link_columns.size() > 0;
+    }
+
+    bool is_nullable() const
+    {
+        return m_nullable;
+    }
+
+    size_t column_ndx() const noexcept
+    {
+        return m_sg ? get_column_base().get_column_index() : m_column_ndx;
+    }
+
+private:
+    LinkMap m_link_map;
+
+    // Fast (leaf caching) value getter for payload column (column in table on which query condition is executed)
+    std::unique_ptr<SequentialGetterBase> m_sg;
+
+    // Column index of payload column of m_table
+    size_t m_column_ndx;
+
+    // set to false by default for stand-alone Columns declaration that are not yet associated with any table
+    // or oclumn. Call init() to update it or use a constructor that takes table + column index as argument.
+    bool m_nullable = false;
+
+    const ColumnBase& get_column_base() const noexcept
+    {
+        if (m_nullable && std::is_same<int64_t, T>::value)
+            return *static_cast<SequentialGetter<IntNullColumn>&>(*m_sg).m_column;
+        else
+            return *static_cast<SequentialGetter<ColType>&>(*m_sg).m_column;
+    }
+};
+
+template <typename T, typename Operation>
+class SubColumnAggregate;
+
+template <typename T>
+class SubColumns : public Subexpr {
+public:
+    SubColumns(Columns<T> column, LinkMap link_map)
+        : m_column(std::move(column))
+        , m_link_map(std::move(link_map))
+    {
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches*) const override
+    {
+        return make_subexpr<SubColumns<T>>(*this);
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_link_map.set_base_table(table);
+        m_column.set_base_table(m_link_map.target_table());
+    }
+
+    void verify_column() const override
+    {
+        m_link_map.verify_columns();
+        m_column.verify_column();
+    }
+
+    void evaluate(size_t, ValueBase&) override
+    {
+        // SubColumns can only be used in an expression in conjunction with its aggregate methods.
+        REALM_ASSERT(false);
+    }
+
+    virtual std::string description() const override
+    {
+        return ""; // by itself there are no conditions, see SubColumnAggregate
+    }
+
+    SubColumnAggregate<T, aggregate_operations::Minimum<T>> min() const
+    {
+        return {m_column, m_link_map};
+    }
+
+    SubColumnAggregate<T, aggregate_operations::Maximum<T>> max() const
+    {
+        return {m_column, m_link_map};
+    }
+
+    SubColumnAggregate<T, aggregate_operations::Sum<T>> sum() const
+    {
+        return {m_column, m_link_map};
+    }
+
+    SubColumnAggregate<T, aggregate_operations::Average<T>> average() const
+    {
+        return {m_column, m_link_map};
+    }
+
+private:
+    Columns<T> m_column;
+    LinkMap m_link_map;
+};
+
+template <typename T, typename Operation>
+class SubColumnAggregate : public Subexpr2<typename Operation::ResultType> {
+public:
+    SubColumnAggregate(Columns<T> column, LinkMap link_map)
+        : m_column(std::move(column))
+        , m_link_map(std::move(link_map))
+    {
+    }
+    SubColumnAggregate(SubColumnAggregate const& other, QueryNodeHandoverPatches* patches)
+        : m_column(other.m_column, patches)
+        , m_link_map(other.m_link_map, patches)
+    {
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return make_subexpr<SubColumnAggregate>(*this, patches);
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_link_map.set_base_table(table);
+        m_column.set_base_table(m_link_map.target_table());
+    }
+
+    void verify_column() const override
+    {
+        m_link_map.verify_columns();
+        m_column.verify_column();
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        std::vector<size_t> links = m_link_map.get_links(index);
+        std::sort(links.begin(), links.end());
+
+        Operation op;
+        for (size_t link_index = 0; link_index < links.size();) {
+            Value<T> value;
+            size_t link = links[link_index];
+            m_column.evaluate(link, value);
+
+            // Columns<T>::evaluate fetches values in chunks of ValueBase::default_size. Process all values
+            // within the chunk that came from rows that we link to.
+            const auto& value_storage = value.m_storage;
+            for (size_t value_index = 0; value_index < value.m_values;) {
+                if (!value_storage.is_null(value_index)) {
+                    op.accumulate(value_storage[value_index]);
+                }
+                if (++link_index >= links.size()) {
+                    break;
+                }
+
+                size_t previous_link = link;
+                link = links[link_index];
+                value_index += link - previous_link;
+            }
+        }
+        if (op.is_null()) {
+            destination.import(Value<null>(false, 1, null()));
+        }
+        else {
+            destination.import(Value<typename Operation::ResultType>(false, 1, op.result()));
+        }
+    }
+
+    virtual std::string description() const override
+    {
+        return m_link_map.description() + util::serializer::value_separator + Operation::description() + util::serializer::value_separator + m_column.description();
+    }
+
+private:
+    Columns<T> m_column;
+    LinkMap m_link_map;
+};
+
+struct SubQueryCountHandoverPatch : QueryNodeHandoverPatch {
+    QueryHandoverPatch m_query;
+};
+
+class SubQueryCount : public Subexpr2<Int> {
+public:
+    SubQueryCount(Query q, LinkMap link_map)
+        : m_query(std::move(q))
+        , m_link_map(std::move(link_map))
+    {
+    }
+
+    const Table* get_base_table() const override
+    {
+        return m_link_map.base_table();
+    }
+
+    void set_base_table(const Table* table) override
+    {
+        m_link_map.set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_link_map.verify_columns();
+    }
+
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        std::vector<size_t> links = m_link_map.get_links(index);
+        std::sort(links.begin(), links.end());
+
+        size_t count = std::accumulate(links.begin(), links.end(), size_t(0), [this](size_t running_count, size_t link) {
+            return running_count + m_query.count(link, link + 1, 1);
+        });
+
+        destination.import(Value<Int>(false, 1, size_t(count)));
+    }
+
+    virtual std::string description() const override
+    {
+        return m_link_map.description() + util::serializer::value_separator + "SUBQUERY(" + m_query.get_description() + ")"
+            + util::serializer::value_separator + "@count";
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        if (patches)
+            return std::unique_ptr<Subexpr>(new SubQueryCount(*this, patches));
+
+        return make_subexpr<SubQueryCount>(*this);
+    }
+
+    void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override
+    {
+        REALM_ASSERT(patches.size());
+        std::unique_ptr<QueryNodeHandoverPatch> abstract_patch = std::move(patches.back());
+        patches.pop_back();
+
+        auto patch = dynamic_cast<SubQueryCountHandoverPatch*>(abstract_patch.get());
+        REALM_ASSERT(patch);
+
+        m_query.apply_patch(patch->m_query, group);
+    }
+
+private:
+    SubQueryCount(const SubQueryCount& other, QueryNodeHandoverPatches* patches)
+        : m_link_map(other.m_link_map, patches)
+    {
+        std::unique_ptr<SubQueryCountHandoverPatch> patch(new SubQueryCountHandoverPatch);
+        m_query = Query(other.m_query, patch->m_query, ConstSourcePayload::Copy);
+        patches->emplace_back(patch.release());
+    }
+
+    Query m_query;
+    LinkMap m_link_map;
+};
+
+// The unused template parameter is a hack to avoid a circular dependency between table.hpp and query_expression.hpp.
+template <class>
+class SubQuery {
+public:
+    SubQuery(Columns<Link> link_column, Query query)
+        : m_query(std::move(query))
+        , m_link_map(link_column.link_map())
+    {
+        REALM_ASSERT(m_link_map.target_table() == m_query.get_table());
+    }
+
+    SubQueryCount count() const
+    {
+        return SubQueryCount(m_query, m_link_map);
+    }
+
+private:
+    Query m_query;
+    LinkMap m_link_map;
+};
+
+namespace aggregate_operations {
+template <typename T, typename Derived, typename R = T>
+class BaseAggregateOperation {
+    static_assert(std::is_same<T, Int>::value || std::is_same<T, Float>::value || std::is_same<T, Double>::value,
+                  "Numeric aggregates can only be used with subcolumns of numeric types");
+
+public:
+    using ResultType = R;
+
+    void accumulate(T value)
+    {
+        m_count++;
+        m_result = Derived::apply(m_result, value);
+    }
+
+    bool is_null() const
+    {
+        return m_count == 0;
+    }
+    ResultType result() const
+    {
+        return m_result;
+    }
+
+protected:
+    size_t m_count = 0;
+    ResultType m_result = Derived::initial_value();
+};
+
+template <typename T>
+class Minimum : public BaseAggregateOperation<T, Minimum<T>> {
+public:
+    static T initial_value()
+    {
+        return std::numeric_limits<T>::max();
+    }
+    static T apply(T a, T b)
+    {
+        return std::min(a, b);
+    }
+    static std::string description()
+    {
+        return "@min";
+    }
+};
+
+template <typename T>
+class Maximum : public BaseAggregateOperation<T, Maximum<T>> {
+public:
+    static T initial_value()
+    {
+        return std::numeric_limits<T>::min();
+    }
+    static T apply(T a, T b)
+    {
+        return std::max(a, b);
+    }
+    static std::string description()
+    {
+        return "@max";
+    }
+};
+
+template <typename T>
+class Sum : public BaseAggregateOperation<T, Sum<T>> {
+public:
+    static T initial_value()
+    {
+        return T();
+    }
+    static T apply(T a, T b)
+    {
+        return a + b;
+    }
+    bool is_null() const
+    {
+        return false;
+    }
+    static std::string description()
+    {
+        return "@sum";
+    }
+};
+
+template <typename T>
+class Average : public BaseAggregateOperation<T, Average<T>, double> {
+    using Base = BaseAggregateOperation<T, Average<T>, double>;
+
+public:
+    static double initial_value()
+    {
+        return 0;
+    }
+    static double apply(double a, T b)
+    {
+        return a + b;
+    }
+    double result() const
+    {
+        return Base::m_result / Base::m_count;
+    }
+    static std::string description()
+    {
+        return "@avg";
+    }
+
+};
+}
+
+template <class oper, class TLeft>
+class UnaryOperator : public Subexpr2<typename oper::type> {
+public:
+    UnaryOperator(std::unique_ptr<TLeft> left)
+        : m_left(std::move(left))
+    {
+    }
+
+    UnaryOperator(const UnaryOperator& other, QueryNodeHandoverPatches* patches)
+        : m_left(other.m_left->clone(patches))
+    {
+    }
+
+    UnaryOperator& operator=(const UnaryOperator& other)
+    {
+        if (this != &other) {
+            m_left = other.m_left->clone();
+        }
+        return *this;
+    }
+
+    UnaryOperator(UnaryOperator&&) = default;
+    UnaryOperator& operator=(UnaryOperator&&) = default;
+
+    // See comment in base class
+    void set_base_table(const Table* table) override
+    {
+        m_left->set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_left->verify_column();
+    }
+
+    // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression
+    // and binds it to a Query at a later time
+    const Table* get_base_table() const override
+    {
+        return m_left->get_base_table();
+    }
+
+    // destination = operator(left)
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        Value<T> result;
+        Value<T> left;
+        m_left->evaluate(index, left);
+        result.template fun<oper>(&left);
+        destination.import(result);
+    }
+
+    virtual std::string description() const override
+    {
+        if (m_left) {
+            return m_left->description();
+        }
+        return "";
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return make_subexpr<UnaryOperator>(*this, patches);
+    }
+
+    void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override
+    {
+        m_left->apply_handover_patch(patches, group);
+    }
+
+private:
+    typedef typename oper::type T;
+    std::unique_ptr<TLeft> m_left;
+};
+
+
+template <class oper, class TLeft, class TRight>
+class Operator : public Subexpr2<typename oper::type> {
+public:
+    Operator(std::unique_ptr<TLeft> left, std::unique_ptr<TRight> right)
+        : m_left(std::move(left))
+        , m_right(std::move(right))
+    {
+    }
+
+    Operator(const Operator& other, QueryNodeHandoverPatches* patches)
+        : m_left(other.m_left->clone(patches))
+        , m_right(other.m_right->clone(patches))
+    {
+    }
+
+    Operator& operator=(const Operator& other)
+    {
+        if (this != &other) {
+            m_left = other.m_left->clone();
+            m_right = other.m_right->clone();
+        }
+        return *this;
+    }
+
+    Operator(Operator&&) = default;
+    Operator& operator=(Operator&&) = default;
+
+    // See comment in base class
+    void set_base_table(const Table* table) override
+    {
+        m_left->set_base_table(table);
+        m_right->set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_left->verify_column();
+        m_right->verify_column();
+    }
+
+    // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression
+    // and
+    // binds it to a Query at a later time
+    const Table* get_base_table() const override
+    {
+        const Table* l = m_left->get_base_table();
+        const Table* r = m_right->get_base_table();
+
+        // Queries do not support multiple different tables; all tables must be the same.
+        REALM_ASSERT(l == nullptr || r == nullptr || l == r);
+
+        // nullptr pointer means expression which isn't yet associated with any table, or is a Value<T>
+        return l ? l : r;
+    }
+
+    // destination = operator(left, right)
+    void evaluate(size_t index, ValueBase& destination) override
+    {
+        Value<T> result;
+        Value<T> left;
+        Value<T> right;
+        m_left->evaluate(index, left);
+        m_right->evaluate(index, right);
+        result.template fun<oper>(&left, &right);
+        destination.import(result);
+    }
+
+    virtual std::string description() const override
+    {
+        std::string s;
+        if (m_left) {
+            s += m_left->description();
+        }
+        s += oper::description();
+        if (m_right) {
+            s += m_right->description();
+        }
+        return s;
+    }
+
+    std::unique_ptr<Subexpr> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return make_subexpr<Operator>(*this, patches);
+    }
+
+    void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override
+    {
+        m_right->apply_handover_patch(patches, group);
+        m_left->apply_handover_patch(patches, group);
+    }
+
+private:
+    typedef typename oper::type T;
+    std::unique_ptr<TLeft> m_left;
+    std::unique_ptr<TRight> m_right;
+};
+
+
+template <class TCond, class T, class TLeft, class TRight>
+class Compare : public Expression {
+public:
+    Compare(std::unique_ptr<TLeft> left, std::unique_ptr<TRight> right)
+        : m_left(std::move(left))
+        , m_right(std::move(right))
+    {
+    }
+
+    // See comment in base class
+    void set_base_table(const Table* table) override
+    {
+        m_left->set_base_table(table);
+        m_right->set_base_table(table);
+    }
+
+    void verify_column() const override
+    {
+        m_left->verify_column();
+        m_right->verify_column();
+    }
+
+    // Recursively fetch tables of columns in expression tree. Used when user first builds a stand-alone expression
+    // and
+    // binds it to a Query at a later time
+    const Table* get_base_table() const override
+    {
+        const Table* l = m_left->get_base_table();
+        const Table* r = m_right->get_base_table();
+
+        // All main tables in each subexpression of a query (table.columns() or table.link()) must be the same.
+        REALM_ASSERT(l == nullptr || r == nullptr || l == r);
+
+        // nullptr pointer means expression which isn't yet associated with any table, or is a Value<T>
+        return l ? l : r;
+    }
+
+    size_t find_first(size_t start, size_t end) const override
+    {
+        size_t match;
+        Value<T> right;
+        Value<T> left;
+
+        for (; start < end;) {
+            m_left->evaluate(start, left);
+            m_right->evaluate(start, right);
+            match = Value<T>::template compare<TCond>(&left, &right);
+
+            if (match != not_found && match + start < end)
+                return start + match;
+
+            size_t rows =
+                (left.m_from_link_list || right.m_from_link_list) ? 1 : minimum(right.m_values, left.m_values);
+            start += rows;
+        }
+
+        return not_found; // no match
+    }
+
+    virtual std::string description() const override
+    {
+        if (std::is_same<TCond, BeginsWith>::value
+            || std::is_same<TCond, BeginsWithIns>::value
+            || std::is_same<TCond, EndsWith>::value
+            || std::is_same<TCond, EndsWithIns>::value
+            || std::is_same<TCond, Contains>::value
+            || std::is_same<TCond, ContainsIns>::value
+            || std::is_same<TCond, Like>::value
+            || std::is_same<TCond, LikeIns>::value) {
+            // these string conditions have the arguments reversed but the order is important
+            // operations ==, and != can be reversed because the produce the same results both ways
+            return util::serializer::print_value(m_right->description() + " " + TCond::description()
+                                                 + " " + m_left->description());
+        }
+        return util::serializer::print_value(m_left->description() + " " + TCond::description()
+                                             + " " + m_right->description());
+    }
+
+    std::unique_ptr<Expression> clone(QueryNodeHandoverPatches* patches) const override
+    {
+        return std::unique_ptr<Expression>(new Compare(*this, patches));
+    }
+
+    void apply_handover_patch(QueryNodeHandoverPatches& patches, Group& group) override
+    {
+        m_right->apply_handover_patch(patches, group);
+        m_left->apply_handover_patch(patches, group);
+    }
+
+private:
+    Compare(const Compare& other, QueryNodeHandoverPatches* patches)
+        : m_left(other.m_left->clone(patches))
+        , m_right(other.m_right->clone(patches))
+    {
+    }
+
+    std::unique_ptr<TLeft> m_left;
+    std::unique_ptr<TRight> m_right;
+};
+}
+#endif // REALM_QUERY_EXPRESSION_HPP