added iOS source code
[wl-app.git] / iOS / Pods / Realm / include / core / realm / table.hpp
diff --git a/iOS/Pods/Realm/include/core/realm/table.hpp b/iOS/Pods/Realm/include/core/realm/table.hpp
new file mode 100644 (file)
index 0000000..5f92a9d
--- /dev/null
@@ -0,0 +1,2751 @@
+/*************************************************************************
+ *
+ * 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.
+ *
+ **************************************************************************/
+
+#ifndef REALM_TABLE_HPP
+#define REALM_TABLE_HPP
+
+#include <algorithm>
+#include <map>
+#include <utility>
+#include <typeinfo>
+#include <memory>
+#include <mutex>
+
+#include <realm/util/features.h>
+#include <realm/util/thread.hpp>
+#include <realm/table_ref.hpp>
+#include <realm/link_view_fwd.hpp>
+#include <realm/row.hpp>
+#include <realm/descriptor_fwd.hpp>
+#include <realm/spec.hpp>
+#include <realm/mixed.hpp>
+#include <realm/query.hpp>
+#include <realm/column.hpp>
+#include <realm/column_binary.hpp>
+
+namespace realm {
+
+class BacklinkColumn;
+class BinaryColumy;
+class ConstTableView;
+class Group;
+class LinkColumn;
+class LinkColumnBase;
+class LinkListColumn;
+class LinkView;
+class SortDescriptor;
+class StringIndex;
+class TableView;
+class TableViewBase;
+class TimestampColumn;
+template <class>
+class Columns;
+template <class>
+class SubQuery;
+struct LinkTargetInfo;
+
+struct SubTable {
+};
+struct Link {
+};
+typedef Link LinkList;
+typedef Link BackLink;
+
+namespace _impl {
+class TableFriend;
+}
+namespace metrics {
+class QueryInfo;
+}
+
+class Replication;
+
+
+/// FIXME: Table assignment (from any group to any group) could be made aliasing
+/// safe as follows: Start by cloning source table into target allocator. On
+/// success, assign, and then deallocate any previous structure at the target.
+///
+/// FIXME: It might be desirable to have a 'table move' feature between two
+/// places inside the same group (say from a subtable or a mixed column to group
+/// level). This could be done in a very efficient manner.
+///
+/// FIXME: When compiling in debug mode, all public non-static table functions
+/// should REALM_ASSERT(is_attached()).
+class Table {
+public:
+    /// Construct a new freestanding top-level table with static
+    /// lifetime.
+    ///
+    /// This constructor should be used only when placing a table
+    /// instance on the stack, and it is then the responsibility of
+    /// the application that there are no objects of type TableRef or
+    /// ConstTableRef that refer to it, or to any of its subtables,
+    /// when it goes out of scope. To create a top-level table with
+    /// dynamic lifetime, use Table::create() instead.
+    Table(Allocator& = Allocator::get_default());
+
+    /// Construct a copy of the specified table as a new freestanding
+    /// top-level table with static lifetime.
+    ///
+    /// This constructor should be used only when placing a table
+    /// instance on the stack, and it is then the responsibility of
+    /// the application that there are no objects of type TableRef or
+    /// ConstTableRef that refer to it, or to any of its subtables,
+    /// when it goes out of scope. To create a top-level table with
+    /// dynamic lifetime, use Table::copy() instead.
+    Table(const Table&, Allocator& = Allocator::get_default());
+
+    ~Table() noexcept;
+
+    Allocator& get_alloc() const;
+
+    /// Construct a new freestanding top-level table with dynamic lifetime.
+    static TableRef create(Allocator& = Allocator::get_default());
+
+    /// Construct a copy of the specified table as a new freestanding top-level
+    /// table with dynamic lifetime.
+    TableRef copy(Allocator& = Allocator::get_default()) const;
+
+    /// Returns true if, and only if this accessor is currently attached to an
+    /// underlying table.
+    ///
+    /// A table accessor may get detached from the underlying row for various
+    /// reasons (see below). When it does, it no longer refers to anything, and
+    /// can no longer be used, except for calling is_attached(). The
+    /// consequences of calling other non-static functions on a detached table
+    /// accessor are unspecified. Table accessors obtained by calling functions in
+    /// the Realm API are always in the 'attached' state immediately upon
+    /// return from those functions.
+    ///
+    /// A table accessor of a free-standing table never becomes detached (except
+    /// during its eventual destruction). A group-level table accessor becomes
+    /// detached if the underlying table is removed from the group, or when the
+    /// group accessor is destroyed. A subtable accessor becomes detached if the
+    /// underlying subtable is removed, or if the parent table accessor is
+    /// detached. A table accessor does not become detached for any other reason
+    /// than those mentioned here.
+    ///
+    /// FIXME: High level language bindings will probably want to be able to
+    /// explicitely detach a group and all tables of that group if any modifying
+    /// operation fails (e.g. memory allocation failure) (and something similar
+    /// for freestanding tables) since that leaves the group in state where any
+    /// further access is disallowed. This way they will be able to reliably
+    /// intercept any attempt at accessing such a failed group.
+    ///
+    /// FIXME: The C++ documentation must state that if any modifying operation
+    /// on a group (incl. tables, subtables, and specs) or on a free standing
+    /// table (incl. subtables and specs) fails, then any further access to that
+    /// group (except ~Group()) or freestanding table (except ~Table()) has
+    /// undefined behaviour and is considered an error on behalf of the
+    /// application. Note that even Table::is_attached() is disallowed in this
+    /// case.
+    bool is_attached() const noexcept;
+
+    /// Get the name of this table, if it has one. Only group-level tables have
+    /// names. For a table of any other kind, this function returns the empty
+    /// string.
+    StringData get_name() const noexcept;
+
+    // Whether or not elements can be null.
+    bool is_nullable(size_t col_ndx) const;
+
+    //@{
+    /// Conventience functions for inspecting the dynamic table type.
+    ///
+    /// These functions behave as if they were called on the descriptor returned
+    /// by get_descriptor().
+    size_t get_column_count() const noexcept;
+    DataType get_column_type(size_t column_ndx) const noexcept;
+    StringData get_column_name(size_t column_ndx) const noexcept;
+    size_t get_column_index(StringData name) const noexcept;
+    //@}
+
+    //@{
+    /// Convenience functions for manipulating the dynamic table type.
+    ///
+    /// These function must be called only for tables with independent dynamic
+    /// type. A table has independent dynamic type if the function
+    /// has_shared_type() returns false. A table that is a direct member of a
+    /// group has independent dynamic type. So does a free-standing table, and a
+    /// subtable in a column of type 'mixed'. All other tables have shared
+    /// dynamic type. The consequences of calling any of these functions for a
+    /// table with shared dynamic type are undefined.
+    ///
+    /// Apart from that, these functions behave as if they were called on the
+    /// descriptor returned by get_descriptor(). Note especially that the
+    /// `_link` suffixed functions must be used when inserting link-type
+    /// columns.
+    ///
+    /// If you need to change the shared dynamic type of the subtables in a
+    /// subtable column, consider using the API offered by the Descriptor class.
+    ///
+    /// \sa has_shared_type()
+    /// \sa get_descriptor()
+
+    size_t add_column(DataType type, StringData name, bool nullable = false, DescriptorRef* subdesc = nullptr);
+    void insert_column(size_t column_ndx, DataType type, StringData name, bool nullable = false,
+                       DescriptorRef* subdesc = nullptr);
+
+    // Todo, these prototypes only exist for backwards compatibility. We should remove them because they are error
+    // prone (optional arguments and implicit bool to null-ptr conversion)
+    size_t add_column(DataType type, StringData name, DescriptorRef* subdesc)
+    {
+        return add_column(type, name, false, subdesc);
+    }
+    void insert_column(size_t column_ndx, DataType type, StringData name, DescriptorRef* subdesc)
+    {
+        insert_column(column_ndx, type, name, false, subdesc);
+    }
+
+    size_t add_column_link(DataType type, StringData name, Table& target, LinkType link_type = link_Weak);
+    void insert_column_link(size_t column_ndx, DataType type, StringData name, Table& target,
+                            LinkType link_type = link_Weak);
+    void remove_column(size_t column_ndx);
+    void rename_column(size_t column_ndx, StringData new_name);
+    //@}
+    //@{
+
+    /// has_search_index() returns true if, and only if a search index has been
+    /// added to the specified column. Rather than throwing, it returns false if
+    /// the table accessor is detached or the specified index is out of range.
+    ///
+    /// add_search_index() adds a search index to the specified column of the
+    /// table. It has no effect if a search index has already been added to the
+    /// specified column (idempotency).
+    ///
+    /// remove_search_index() removes the search index from the specified column
+    /// of the table. It has no effect if the specified column has no search
+    /// index. The search index cannot be removed from the primary key of a
+    /// table.
+    ///
+    /// This table must be a root table; that is, it must have an independent
+    /// descriptor. Freestanding tables, group-level tables, and subtables in a
+    /// column of type 'mixed' are all examples of root tables. See add_column()
+    /// for more on this. If you want to manipulate subtable indexes, you must use
+    /// the Descriptor interface.
+    ///
+    /// \param column_ndx The index of a column of the table.
+
+    bool has_search_index(size_t column_ndx) const noexcept;
+    void add_search_index(size_t column_ndx);
+    void remove_search_index(size_t column_ndx);
+
+    //@}
+
+    //@{
+    /// Get the dynamic type descriptor for this table.
+    ///
+    /// Every table has an associated descriptor that specifies its dynamic
+    /// type. For simple tables, that is, tables without subtable columns, the
+    /// dynamic type can be inspected and modified directly using member
+    /// functions such as get_column_count() and add_column(). For more complex
+    /// tables, the type is best managed through the associated descriptor
+    /// object which is returned by this function.
+    ///
+    /// \sa has_shared_type()
+    DescriptorRef get_descriptor();
+    ConstDescriptorRef get_descriptor() const;
+    //@}
+
+    //@{
+    /// Get the dynamic type descriptor for the column with the
+    /// specified index. That column must have type 'table'.
+    ///
+    /// This is merely a shorthand for calling `get_subdescriptor(column_ndx)`
+    /// on the descriptor returned by `get_descriptor()`.
+    DescriptorRef get_subdescriptor(size_t column_ndx);
+    ConstDescriptorRef get_subdescriptor(size_t column_ndx) const;
+    //@}
+
+    //@{
+    /// Get access to an arbitrarily nested dynamic type descriptor.
+    ///
+    /// The returned descriptor is the one you would get by calling
+    /// Descriptor::get_subdescriptor() once for each entry in the specified
+    /// path, starting with the descriptor returned by get_descriptor(). The
+    /// path is allowed to be empty.
+    typedef std::vector<size_t> path_vec;
+    DescriptorRef get_subdescriptor(const path_vec& path);
+    ConstDescriptorRef get_subdescriptor(const path_vec& path) const;
+    //@}
+
+    //@{
+    /// Convenience functions for manipulating nested table types.
+    ///
+    /// These functions behave as if they were called on the descriptor returned
+    /// by `get_subdescriptor(path)`. These function must be called only on
+    /// tables with independent dynamic type.
+    ///
+    /// \return The value returned by add_subcolumn(), is the index of
+    /// the added column within the descriptor referenced by the
+    /// specified path.
+    ///
+    /// \sa Descriptor::add_column()
+    /// \sa has_shared_type()
+    size_t add_subcolumn(const path_vec& path, DataType type, StringData name);
+    void insert_subcolumn(const path_vec& path, size_t column_ndx, DataType type, StringData name);
+    void remove_subcolumn(const path_vec& path, size_t column_ndx);
+    void rename_subcolumn(const path_vec& path, size_t column_ndx, StringData new_name);
+    //@}
+
+    /// Does this table share its type with other tables?
+    ///
+    /// Tables that are direct members of groups have independent
+    /// dynamic types. The same is true for free-standing tables and
+    /// subtables in coulmns of type 'mixed'. For such tables, this
+    /// function returns false.
+    ///
+    /// When a table has a column of type 'table', the cells in that
+    /// column contain subtables. All those subtables have the same
+    /// dynamic type, and they share a single type descriptor. For all
+    /// such subtables, this function returns true. See
+    /// Descriptor::is_root() for more on this.
+    ///
+    /// Please note that Table functions that modify the dynamic type
+    /// directly, such as add_column(), are only allowed to be used on
+    /// tables with non-shared type. If you need to modify a shared
+    /// type, you will have to do that through the descriptor returned
+    /// by get_descriptor(), but note that it will then affect all the
+    /// tables sharing that descriptor.
+    ///
+    /// \sa get_descriptor()
+    /// \sa Descriptor::is_root()
+    bool has_shared_type() const noexcept;
+
+
+    template <class T>
+    Columns<T> column(size_t column); // FIXME: Should this one have been declared noexcept?
+    template <class T>
+    Columns<T> column(const Table& origin, size_t origin_column_ndx);
+
+    template <class T>
+    SubQuery<T> column(size_t column, Query subquery);
+    template <class T>
+    SubQuery<T> column(const Table& origin, size_t origin_column_ndx, Query subquery);
+
+    // Table size and deletion
+    bool is_empty() const noexcept;
+    size_t size() const noexcept;
+
+    typedef BasicRowExpr<Table> RowExpr;
+    typedef BasicRowExpr<const Table> ConstRowExpr;
+
+    RowExpr get(size_t row_ndx) noexcept;
+    ConstRowExpr get(size_t row_ndx) const noexcept;
+
+    RowExpr front() noexcept;
+    ConstRowExpr front() const noexcept;
+
+    RowExpr back() noexcept;
+    ConstRowExpr back() const noexcept;
+
+    RowExpr operator[](size_t row_ndx) noexcept;
+    ConstRowExpr operator[](size_t row_ndx) const noexcept;
+
+
+    //@{
+
+    /// Row handling.
+    ///
+    /// remove() removes the specified row from the table and shifts all rows at
+    /// higher index to fill the vacated slot. This operation assumes that the
+    /// table is ordered, and it is therefore allowed only on tables **without**
+    /// link columns, as link columns are only allowed in unordered tables.
+    ///
+    /// move_last_over() removes the specified row from the table, and if it is
+    /// not the last row in the table, it then moves the last row into the
+    /// vacated slot. This operation assumes that the table is unordered, and it
+    /// may therfore be used on tables with link columns.
+    ///
+    /// remove_recursive() will delete linked rows if the removed link was the
+    /// last one holding on to the row in question. This will be done recursively.
+    ///
+    /// The removal of a row from an unordered table (move_last_over()) may
+    /// cause other linked rows to be cascade-removed. The clearing of a table
+    /// may also cause linked rows to be cascade-removed, but in this respect,
+    /// the effect is exactly as if each row had been removed individually. See
+    /// Descriptor::set_link_type() for details.
+
+    size_t add_empty_row(size_t num_rows = 1);
+    void insert_empty_row(size_t row_ndx, size_t num_rows = 1);
+    size_t add_row_with_key(size_t col_ndx, int64_t key);
+    void remove(size_t row_ndx);
+    void remove_recursive(size_t row_ndx);
+    void remove_last();
+    void move_last_over(size_t row_ndx);
+    void clear();
+    void swap_rows(size_t row_ndx_1, size_t row_ndx_2);
+    void move_row(size_t from_ndx, size_t to_ndx);
+    //@}
+
+    /// Replaces all links to \a row_ndx with links to \a new_row_ndx.
+    ///
+    /// This operation is usually followed by Table::move_last_over()
+    /// as part of Table::set_int_unique() or Table::set_string_unique()
+    /// or Table::set_null_unique() detecting a collision.
+    ///
+    /// \sa Table::move_last_over()
+    /// \sa Table::set_int_unique()
+    /// \sa Table::set_string_unique()
+    /// \sa Table::set_null_unique()
+    void merge_rows(size_t row_ndx, size_t new_row_ndx);
+
+    //@{
+
+    /// Get cell values.
+    /// Will assert if the requested type does not match the column type.
+    ///
+    /// When fetching from a nullable column and the value is null, a default
+    /// value will be returned, except for object like types (StringData,
+    /// BinaryData, Timestamp) which have support for storing nulls. In that
+    /// case, call the `is_null()` method on the returned object to check
+    /// whether the stored value was null. If nullability matters and returning
+    /// a default value is unacceptable, check Table::is_null() before getting a
+    /// cell value.
+    ///
+    /// \sa Table::is_nullable(size_t col_ndx)
+    /// \sa Table::is_null(size_t col_ndx, size_t row_ndx)
+    /// \sa StringData::is_null()
+    int64_t get_int(size_t column_ndx, size_t row_ndx) const noexcept;
+    bool get_bool(size_t column_ndx, size_t row_ndx) const noexcept;
+    OldDateTime get_olddatetime(size_t column_ndx, size_t row_ndx) const noexcept;
+    float get_float(size_t column_ndx, size_t row_ndx) const noexcept;
+    double get_double(size_t column_ndx, size_t row_ndx) const noexcept;
+    StringData get_string(size_t column_ndx, size_t row_ndx) const noexcept;
+    BinaryData get_binary(size_t column_ndx, size_t row_ndx) const noexcept;
+    BinaryIterator get_binary_iterator(size_t column_ndx, size_t row_ndx) const noexcept;
+    Mixed get_mixed(size_t column_ndx, size_t row_ndx) const noexcept;
+    DataType get_mixed_type(size_t column_ndx, size_t row_ndx) const noexcept;
+    Timestamp get_timestamp(size_t column_ndx, size_t row_ndx) const noexcept;
+
+    //@}
+
+    /// Return data from position 'pos' and onwards. If the blob is distributed
+    /// across multiple arrays, you will only get data from one array. 'pos'
+    /// will be updated to be an index to next available data. It will be 0
+    /// if no more data.
+    BinaryData get_binary_at(size_t col_ndx, size_t ndx, size_t& pos) const noexcept;
+
+    template <class T>
+    T get(size_t c, size_t r) const noexcept;
+
+    size_t get_link(size_t column_ndx, size_t row_ndx) const noexcept;
+    bool is_null_link(size_t column_ndx, size_t row_ndx) const noexcept;
+    LinkViewRef get_linklist(size_t column_ndx, size_t row_ndx);
+    ConstLinkViewRef get_linklist(size_t column_ndx, size_t row_ndx) const;
+    size_t get_link_count(size_t column_ndx, size_t row_ndx) const noexcept;
+    bool linklist_is_empty(size_t column_ndx, size_t row_ndx) const noexcept;
+    bool is_null(size_t column_ndx, size_t row_ndx) const noexcept;
+
+    TableRef get_link_target(size_t column_ndx) noexcept;
+    ConstTableRef get_link_target(size_t column_ndx) const noexcept;
+
+    //@{
+
+    /// Set cell values.
+    ///
+    /// It is an error to specify a column index, row index, or string position
+    /// that is out of range.
+    ///
+    /// The number of bytes in a string value must not exceed `max_string_size`,
+    /// and the number of bytes in a binary data value must not exceed
+    /// `max_binary_size`. String must also contain valid UTF-8 encodings. These
+    /// requirements also apply when modifying a string with insert_substring()
+    /// and remove_substring(), and for strings in a mixed columnt. Passing, or
+    /// producing an oversized string or binary data value will cause an
+    /// exception to be thrown.
+    ///
+    /// The "unique" variants (set_int_unique(), set_string_unique(), set_null_unique())
+    /// are intended to be used in the implementation of primary key support. They
+    /// check if the given column already contains one or more values that are
+    /// equal to \a value, and if there are conflicts, it calls
+    /// Table::merge_rows() for the row_ndx to be replaced by the
+    /// existing row, followed by a Table::move_last_over() of row_ndx. The
+    /// return value is always a row index of a row that contains \a value in
+    /// the specified column, possibly different from \a row_ndx if a conflict
+    /// occurred.  Users intending to implement primary keys must therefore
+    /// manually check for duplicates if they want to raise an error instead.
+    ///
+    /// NOTE:  It is an error to call either function after adding elements to a
+    /// linklist in the object. In general, calling set_int_unique() or
+    /// set_string_unique() or set_null_unique() should be the first thing that
+    /// happens after creating a row. These limitations are imposed by limitations
+    /// in the Realm Object Server and may be relaxed in the future. A violation of
+    /// these rules results in a LogicError being thrown.
+    ///
+    /// add_int() adds a 64-bit signed integer to the current value of the
+    /// cell.  If the addition would cause signed integer overflow or
+    /// underflow, the addition "wraps around" with semantics similar to
+    /// unsigned integer arithmetic, such that Table::max_integer + 1 ==
+    /// Table::min_integer and Table::min_integer - 1 == Table::max_integer.
+    /// Note that the wrapping is platform-independent (all platforms wrap in
+    /// the same way regardless of integer representation). If the existing
+    /// value in the cell is null, a LogicError exception is thrown.
+    ///
+    /// insert_substring() inserts the specified string into the currently
+    /// stored string at the specified position. The position must be less than
+    /// or equal to the size of the currently stored string.
+    ///
+    /// remove_substring() removes the specified byte range from the currently
+    /// stored string. The beginning of the range (\a pos) must be less than or
+    /// equal to the size of the currently stored string. If the specified range
+    /// extends beyond the end of the currently stored string, it will be
+    /// silently clamped.
+    ///
+    /// String level modifications performed via insert_substring() and
+    /// remove_substring() are mergable and subject to operational
+    /// transformation. That is, the effect of two causally unrelated
+    /// modifications will in general both be retained during synchronization.
+
+    static const size_t max_string_size = 0xFFFFF8 - Array::header_size - 1;
+    static const size_t max_binary_size = 0xFFFFF8 - Array::header_size;
+
+    // FIXME: These limits should be chosen independently of the underlying
+    // platform's choice to define int64_t and independent of the integer
+    // representation. The current values only work for 2's complement, which is
+    // not guaranteed by the standard.
+    static constexpr int_fast64_t max_integer = std::numeric_limits<int64_t>::max();
+    static constexpr int_fast64_t min_integer = std::numeric_limits<int64_t>::min();
+
+    template <class T>
+    void set(size_t c, size_t r, T value, bool is_default = false);
+
+    template <class T>
+    size_t set_unique(size_t c, size_t r, T value);
+
+    void set_int(size_t column_ndx, size_t row_ndx, int_fast64_t value, bool is_default = false);
+    size_t set_int_unique(size_t column_ndx, size_t row_ndx, int_fast64_t value);
+    void set_bool(size_t column_ndx, size_t row_ndx, bool value, bool is_default = false);
+    void set_olddatetime(size_t column_ndx, size_t row_ndx, OldDateTime value, bool is_default = false);
+    void set_timestamp(size_t column_ndx, size_t row_ndx, Timestamp value, bool is_default = false);
+    template <class E>
+    void set_enum(size_t column_ndx, size_t row_ndx, E value);
+    void set_float(size_t column_ndx, size_t row_ndx, float value, bool is_default = false);
+    void set_double(size_t column_ndx, size_t row_ndx, double value, bool is_default = false);
+    void set_string(size_t column_ndx, size_t row_ndx, StringData value, bool is_default = false);
+    size_t set_string_unique(size_t column_ndx, size_t row_ndx, StringData value);
+    void set_binary(size_t column_ndx, size_t row_ndx, BinaryData value, bool is_default = false);
+    void set_mixed(size_t column_ndx, size_t row_ndx, Mixed value, bool is_default = false);
+    void set_link(size_t column_ndx, size_t row_ndx, size_t target_row_ndx, bool is_default = false);
+    void nullify_link(size_t column_ndx, size_t row_ndx);
+    void set_null(size_t column_ndx, size_t row_ndx, bool is_default = false);
+    void set_null_unique(size_t col_ndx, size_t row_ndx);
+
+    // Sync needs to store blobs bigger than 16 M. This function can be used for that. Data should be read
+    // out again using the get_binary_at() function. Should not be used for user data as normal get_binary()
+    // will just return null if the data is bigger than the limit.
+    void set_binary_big(size_t column_ndx, size_t row_ndx, BinaryData value, bool is_default = false);
+
+    void add_int(size_t column_ndx, size_t row_ndx, int_fast64_t value);
+
+    void insert_substring(size_t col_ndx, size_t row_ndx, size_t pos, StringData);
+    void remove_substring(size_t col_ndx, size_t row_ndx, size_t pos, size_t substring_size = realm::npos);
+
+    //@}
+
+    /// Assumes that the specified column is a subtable column (in
+    /// particular, not a mixed column) and that the specified table
+    /// has a spec that is compatible with that column, that is, the
+    /// number of columns must be the same, and corresponding columns
+    /// must have identical data types (as returned by
+    /// get_column_type()).
+    void set_subtable(size_t col_ndx, size_t row_ndx, const Table*);
+    void set_mixed_subtable(size_t col_ndx, size_t row_ndx, const Table*);
+
+
+    // Sub-tables (works on columns whose type is either 'subtable' or
+    // 'mixed', for a value in a mixed column that is not a subtable,
+    // get_subtable() returns null, get_subtable_size() returns zero,
+    // and clear_subtable() replaces the value with an empty table.)
+    // Currently, subtables of subtables are not supported.
+    TableRef get_subtable(size_t column_ndx, size_t row_ndx);
+    ConstTableRef get_subtable(size_t column_ndx, size_t row_ndx) const;
+    size_t get_subtable_size(size_t column_ndx, size_t row_ndx) const noexcept;
+    void clear_subtable(size_t column_ndx, size_t row_ndx);
+
+    // Backlinks
+    size_t get_backlink_count(size_t row_ndx, bool only_strong_links = false) const noexcept;
+    size_t get_backlink_count(size_t row_ndx, const Table& origin, size_t origin_col_ndx) const noexcept;
+    size_t get_backlink(size_t row_ndx, const Table& origin, size_t origin_col_ndx, size_t backlink_ndx) const
+        noexcept;
+
+
+    //@{
+
+    /// If this accessor is attached to a subtable, then that subtable has a
+    /// parent table, and the subtable either resides in a column of type
+    /// `table` or of type `mixed` in that parent. In that case
+    /// get_parent_table() returns a reference to the accessor associated with
+    /// the parent, and get_parent_row_index() returns the index of the row in
+    /// which the subtable resides. In all other cases (free-standing and
+    /// group-level tables), get_parent_table() returns null and
+    /// get_parent_row_index() returns realm::npos.
+    ///
+    /// If this accessor is attached to a subtable, and \a column_ndx_out is
+    /// specified, then `*column_ndx_out` is set to the index of the column of
+    /// the parent table in which the subtable resides. If this accessor is not
+    /// attached to a subtable, then `*column_ndx_out` will retain its original
+    /// value upon return.
+
+    TableRef get_parent_table(size_t* column_ndx_out = nullptr) noexcept;
+    ConstTableRef get_parent_table(size_t* column_ndx_out = nullptr) const noexcept;
+    size_t get_parent_row_index() const noexcept;
+
+    //@}
+
+
+    /// Only group-level unordered tables can be used as origins or targets of
+    /// links.
+    bool is_group_level() const noexcept;
+
+    /// If this table is a group-level table, then this function returns the
+    /// index of this table within the group. Otherwise it returns realm::npos.
+    size_t get_index_in_group() const noexcept;
+
+    // Aggregate functions
+    size_t count_int(size_t column_ndx, int64_t value) const;
+    size_t count_string(size_t column_ndx, StringData value) const;
+    size_t count_float(size_t column_ndx, float value) const;
+    size_t count_double(size_t column_ndx, double value) const;
+
+    int64_t sum_int(size_t column_ndx) const;
+    double sum_float(size_t column_ndx) const;
+    double sum_double(size_t column_ndx) const;
+    int64_t maximum_int(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    float maximum_float(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    double maximum_double(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    OldDateTime maximum_olddatetime(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    Timestamp maximum_timestamp(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    int64_t minimum_int(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    float minimum_float(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    double minimum_double(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    OldDateTime minimum_olddatetime(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    Timestamp minimum_timestamp(size_t column_ndx, size_t* return_ndx = nullptr) const;
+    double average_int(size_t column_ndx, size_t* value_count = nullptr) const;
+    double average_float(size_t column_ndx, size_t* value_count = nullptr) const;
+    double average_double(size_t column_ndx, size_t* value_count = nullptr) const;
+
+    // Searching
+    template <class T>
+    size_t find_first(size_t column_ndx, T value) const;
+
+    size_t find_first_link(size_t target_row_index) const;
+    size_t find_first_int(size_t column_ndx, int64_t value) const;
+    size_t find_first_bool(size_t column_ndx, bool value) const;
+    size_t find_first_olddatetime(size_t column_ndx, OldDateTime value) const;
+    size_t find_first_timestamp(size_t column_ndx, Timestamp value) const;
+    size_t find_first_float(size_t column_ndx, float value) const;
+    size_t find_first_double(size_t column_ndx, double value) const;
+    size_t find_first_string(size_t column_ndx, StringData value) const;
+    size_t find_first_binary(size_t column_ndx, BinaryData value) const;
+    size_t find_first_null(size_t column_ndx) const;
+
+    TableView find_all_link(size_t target_row_index);
+    ConstTableView find_all_link(size_t target_row_index) const;
+    TableView find_all_int(size_t column_ndx, int64_t value);
+    ConstTableView find_all_int(size_t column_ndx, int64_t value) const;
+    TableView find_all_bool(size_t column_ndx, bool value);
+    ConstTableView find_all_bool(size_t column_ndx, bool value) const;
+    TableView find_all_olddatetime(size_t column_ndx, OldDateTime value);
+    ConstTableView find_all_olddatetime(size_t column_ndx, OldDateTime value) const;
+    TableView find_all_float(size_t column_ndx, float value);
+    ConstTableView find_all_float(size_t column_ndx, float value) const;
+    TableView find_all_double(size_t column_ndx, double value);
+    ConstTableView find_all_double(size_t column_ndx, double value) const;
+    TableView find_all_string(size_t column_ndx, StringData value);
+    ConstTableView find_all_string(size_t column_ndx, StringData value) const;
+    TableView find_all_binary(size_t column_ndx, BinaryData value);
+    ConstTableView find_all_binary(size_t column_ndx, BinaryData value) const;
+    TableView find_all_null(size_t column_ndx);
+    ConstTableView find_all_null(size_t column_ndx) const;
+
+    /// The following column types are supported: String, Integer, OldDateTime, Bool
+    TableView get_distinct_view(size_t column_ndx);
+    ConstTableView get_distinct_view(size_t column_ndx) const;
+
+    TableView get_sorted_view(size_t column_ndx, bool ascending = true);
+    ConstTableView get_sorted_view(size_t column_ndx, bool ascending = true) const;
+
+    TableView get_sorted_view(SortDescriptor order);
+    ConstTableView get_sorted_view(SortDescriptor order) const;
+
+    TableView get_range_view(size_t begin, size_t end);
+    ConstTableView get_range_view(size_t begin, size_t end) const;
+
+    TableView get_backlink_view(size_t row_ndx, Table* src_table, size_t src_col_ndx);
+
+
+    // Pivot / aggregate operation types. Experimental! Please do not document method publicly.
+    enum AggrType {
+        aggr_count,
+        aggr_sum,
+        aggr_avg,
+        aggr_min,
+        aggr_max,
+    };
+
+    // Simple pivot aggregate method. Experimental! Please do not document method publicly.
+    void aggregate(size_t group_by_column, size_t aggr_column, AggrType op, Table& result,
+                   const IntegerColumn* viewrefs = nullptr) const;
+
+    /// Report the current versioning counter for the table. The versioning counter is guaranteed to
+    /// change when the contents of the table changes after advance_read() or promote_to_write(), or
+    /// immediately after calls to methods which change the table. The term "change" means "change of
+    /// value": The storage layout of the table may change, for example due to optimization, but this
+    /// is not considered a change of a value. This means that you *cannot* use a non-changing version
+    /// count to indicate that object addresses (e.g. strings, binary data) remain the same.
+    /// The versioning counter *may* change (but is not required to do so) when another table linked
+    /// from this table, or linking to this table, is changed. The version counter *may* also change
+    /// without any apparent reason.
+    uint_fast64_t get_version_counter() const noexcept;
+
+private:
+    template <class T>
+    TableView find_all(size_t column_ndx, T value);
+
+public:
+    //@{
+    /// Find the lower/upper bound according to a column that is
+    /// already sorted in ascending order.
+    ///
+    /// For an integer column at index 0, and an integer value '`v`',
+    /// lower_bound_int(0,v) returns the index '`l`' of the first row
+    /// such that `get_int(0,l) &ge; v`, and upper_bound_int(0,v)
+    /// returns the index '`u`' of the first row such that
+    /// `get_int(0,u) &gt; v`. In both cases, if no such row is found,
+    /// the returned value is the number of rows in the table.
+    ///
+    ///     3 3 3 4 4 4 5 6 7 9 9 9
+    ///     ^     ^     ^     ^     ^
+    ///     |     |     |     |     |
+    ///     |     |     |     |      -- Lower and upper bound of 15
+    ///     |     |     |     |
+    ///     |     |     |      -- Lower and upper bound of 8
+    ///     |     |     |
+    ///     |     |      -- Upper bound of 4
+    ///     |     |
+    ///     |      -- Lower bound of 4
+    ///     |
+    ///      -- Lower and upper bound of 1
+    ///
+    /// These functions are similar to std::lower_bound() and
+    /// std::upper_bound().
+    ///
+    /// The string versions assume that the column is sorted according
+    /// to StringData::operator<().
+    size_t lower_bound_int(size_t column_ndx, int64_t value) const noexcept;
+    size_t upper_bound_int(size_t column_ndx, int64_t value) const noexcept;
+    size_t lower_bound_bool(size_t column_ndx, bool value) const noexcept;
+    size_t upper_bound_bool(size_t column_ndx, bool value) const noexcept;
+    size_t lower_bound_float(size_t column_ndx, float value) const noexcept;
+    size_t upper_bound_float(size_t column_ndx, float value) const noexcept;
+    size_t lower_bound_double(size_t column_ndx, double value) const noexcept;
+    size_t upper_bound_double(size_t column_ndx, double value) const noexcept;
+    size_t lower_bound_string(size_t column_ndx, StringData value) const noexcept;
+    size_t upper_bound_string(size_t column_ndx, StringData value) const noexcept;
+    //@}
+
+    // Queries
+    // Using where(tv) is the new method to perform queries on TableView. The 'tv' can have any order; it does not
+    // need to be sorted, and, resulting view retains its order.
+    Query where(TableViewBase* tv = nullptr)
+    {
+        return Query(*this, tv);
+    }
+
+    // FIXME: We need a ConstQuery class or runtime check against modifications in read transaction.
+    Query where(TableViewBase* tv = nullptr) const
+    {
+        return Query(*this, tv);
+    }
+
+    // Perform queries on a LinkView. The returned Query holds a reference to lv.
+    Query where(const LinkViewRef& lv)
+    {
+        return Query(*this, lv);
+    }
+
+    Table& link(size_t link_column);
+    Table& backlink(const Table& origin, size_t origin_col_ndx);
+
+    // Optimizing. enforce == true will enforce enumeration of all string columns;
+    // enforce == false will auto-evaluate if they should be enumerated or not
+    void optimize(bool enforce = false);
+
+    /// Write this table (or a slice of this table) to the specified
+    /// output stream.
+    ///
+    /// The output will have the same format as any other Realm
+    /// database file, such as those produced by Group::write(). In
+    /// this case, however, the resulting database file will contain
+    /// exactly one table, and that table will contain only the
+    /// specified slice of the source table (this table).
+    ///
+    /// The new table will always have the same dynamic type (see
+    /// Descriptor) as the source table (this table), and unless it is
+    /// overridden (\a override_table_name), the new table will have
+    /// the same name as the source table (see get_name()). Indexes
+    /// (see add_search_index()) will not be carried over to the new
+    /// table.
+    ///
+    /// \param out The destination output stream buffer.
+    ///
+    /// \param offset Index of first row to include (if `slice_size >
+    /// 0`). Must be less than, or equal to size().
+    ///
+    /// \param slice_size Number of rows to include. May be zero. If
+    /// `slice_size > size() - offset`, then the effective size of
+    /// the written slice will be `size() - offset`.
+    ///
+    /// \param override_table_name Custom name to write out instead of
+    /// the actual table name.
+    ///
+    /// \throw std::out_of_range If `offset > size()`.
+    ///
+    /// FIXME: While this function does provided a maximally efficient
+    /// way of serializing part of a table, it offers little in terms
+    /// of general utility. This is unfortunate, because it pulls
+    /// quite a large amount of code into the core library to support
+    /// it.
+    void write(std::ostream& out, size_t offset = 0, size_t slice_size = npos,
+               StringData override_table_name = StringData()) const;
+
+    // Conversion
+    void to_json(std::ostream& out, size_t link_depth = 0,
+                 std::map<std::string, std::string>* renames = nullptr) const;
+    void to_string(std::ostream& out, size_t limit = 500) const;
+    void row_to_string(size_t row_ndx, std::ostream& out) const;
+
+    // Get a reference to this table
+    TableRef get_table_ref()
+    {
+        return TableRef(this);
+    }
+    ConstTableRef get_table_ref() const
+    {
+        return ConstTableRef(this);
+    }
+
+    /// \brief Compare two tables for equality.
+    ///
+    /// Two tables are equal if they have equal descriptors
+    /// (`Descriptor::operator==()`) and equal contents. Equal descriptors imply
+    /// that the two tables have the same columns in the same order. Equal
+    /// contents means that the two tables must have the same number of rows,
+    /// and that for each row index, the two rows must have the same values in
+    /// each column.
+    ///
+    /// In mixed columns, both the value types and the values are required to be
+    /// equal.
+    ///
+    /// For a particular row and column, if the two values are themselves tables
+    /// (subtable and mixed columns) value equality implies a recursive
+    /// invocation of `Table::operator==()`.
+    bool operator==(const Table&) const;
+
+    /// \brief Compare two tables for inequality.
+    ///
+    /// See operator==().
+    bool operator!=(const Table& t) const;
+
+    /// A subtable in a column of type 'table' (which shares descriptor with
+    /// other subtables in the same column) is initially in a degenerate state
+    /// where it takes up a minimal amout of space. This function returns true
+    /// if, and only if the table accessor is attached to such a subtable. This
+    /// function is mainly intended for debugging purposes.
+    bool is_degenerate() const;
+
+    /// Compute the sum of the sizes in number of bytes of all the array nodes
+    /// that currently make up this table. See also
+    /// Group::compute_aggregate_byte_size().
+    ///
+    /// If this table accessor is the detached state, this function returns
+    /// zero.
+    size_t compute_aggregated_byte_size() const noexcept;
+
+    // Debug
+    void verify() const;
+#ifdef REALM_DEBUG
+    void to_dot(std::ostream&, StringData title = StringData()) const;
+    void print() const;
+    MemStats stats() const;
+    void dump_node_structure() const; // To std::cerr (for GDB)
+    void dump_node_structure(std::ostream&, int level) const;
+#endif
+
+    class Parent;
+    using HandoverPatch = TableHandoverPatch;
+    static void generate_patch(const Table* ref, std::unique_ptr<HandoverPatch>& patch);
+    static TableRef create_from_and_consume_patch(std::unique_ptr<HandoverPatch>& patch, Group& group);
+
+protected:
+    /// Get a pointer to the accessor of the specified subtable. The
+    /// accessor will be created if it does not already exist.
+    ///
+    /// The returned table pointer must **always** end up being
+    /// wrapped in some instantiation of BasicTableRef<>.
+    TableRef get_subtable_tableref(size_t col_ndx, size_t row_ndx);
+
+    /// See non-const get_subtable_tableref().
+    ConstTableRef get_subtable_tableref(size_t col_ndx, size_t row_ndx) const;
+
+    /// Compare the rows of two tables under the assumption that the two tables
+    /// have the same number of columns, and the same data type at each column
+    /// index (as expressed through the DataType enum).
+    bool compare_rows(const Table&) const;
+
+    void set_into_mixed(Table* parent, size_t col_ndx, size_t row_ndx) const;
+
+    void check_lists_are_empty(size_t row_ndx) const;
+
+private:
+    class SliceWriter;
+
+    // Number of rows in this table
+    size_t m_size;
+
+    // Underlying array structure. `m_top` is in use only for root tables; that
+    // is, for tables with independent descriptor. `m_columns` contains a ref
+    // for each column and search index in order of the columns. A search index
+    // ref always occurs immediately after the ref of the column to which the
+    // search index belongs.
+    //
+    // A subtable column (a column of type `type_table`) is essentially just a
+    // column of 'refs' pointing to the root node of each subtable.
+    //
+    // To save space in the database file, a subtable in such a column always
+    // starts out in a degenerate form where nothing is allocated on its behalf,
+    // and a null 'ref' is stored in the corresponding slot of the column. A
+    // subtable remains in this degenerate state until the first row is added to
+    // the subtable.
+    //
+    // For this scheme to work, it must be (and is) possible to create a table
+    // accessor that refers to a degenerate subtable. A table accessor (instance
+    // of `Table`) refers to a degenerate subtable if, and only if `m_columns`
+    // is unattached.
+    //
+    // FIXME: The fact that `m_columns` may be detached means that many
+    // functions (even non-modifying functions) need to check for that before
+    // accessing the contents of the table. This incurs a runtime
+    // overhead. Consider whether this overhead can be eliminated by having
+    // `Table::m_columns` always attached to something, and then detect the
+    // degenerate state in a different way.
+    Array m_top;
+    Array m_columns; // 2nd slot in m_top (for root tables)
+
+    // Management class for the spec object. Only if the table has an independent
+    // spec, the spec object should be deleted when the table object is deleted.
+    // If the table has a shared spec, the spec object is managed by the spec object
+    // of the containing table.
+    class SpecPtr {
+    public:
+        ~SpecPtr()
+         {
+            optionally_delete();
+         }
+        void manage(Spec* ptr)
+        {
+            optionally_delete();
+            m_p = ptr;
+            m_is_managed = true;
+        }
+        void detach()
+        {
+            if (m_is_managed) {
+                m_p->detach();
+            }
+        }
+        SpecPtr& operator=(Spec* ptr)
+        {
+            optionally_delete();
+            m_p = ptr;
+            m_is_managed = false;
+            return *this;
+        }
+        Spec* operator->() const
+        {
+            return m_p;
+        }
+        Spec* get() const
+        {
+            return m_p;
+        }
+        Spec& operator*() const
+        {
+            return *m_p;
+        }
+        operator bool() const
+        {
+            return m_p != nullptr;
+        }
+        bool is_managed() const
+        {
+            return m_is_managed;
+        }
+
+    private:
+        Spec* m_p = nullptr;
+        bool m_is_managed = false;
+
+        void optionally_delete()
+        {
+            if (m_is_managed) {
+                delete m_p;
+            }
+        }
+    };
+
+    SpecPtr m_spec; // 1st slot in m_top (for root tables)
+
+    // Is guaranteed to be empty for a detached accessor. Otherwise it is empty
+    // when the table accessor is attached to a degenerate subtable (unattached
+    // `m_columns`), otherwise it contains precisely one column accessor for
+    // each column in the table, in order.
+    //
+    // In some cases an entry may be null. This is currently possible only in
+    // connection with Group::advance_transact(), but it means that several
+    // member functions must be prepared to handle these null entries; in
+    // particular, detach(), ~Table(), functions called on behalf of detach()
+    // and ~Table(), and functiones called on behalf of
+    // Group::advance_transact().
+    typedef std::vector<ColumnBase*> column_accessors;
+    column_accessors m_cols;
+
+    mutable std::atomic<size_t> m_ref_count;
+
+    // If this table is a root table (has independent descriptor),
+    // then Table::m_descriptor refers to the accessor of its
+    // descriptor when, and only when the descriptor accessor
+    // exists. This is used to ensure that at most one descriptor
+    // accessor exists for each underlying descriptor at any given
+    // point in time. Subdescriptors are kept unique by means of a
+    // registry in the parent descriptor. Table::m_descriptor is
+    // always null for tables with shared descriptor.
+    mutable std::weak_ptr<Descriptor> m_descriptor;
+
+    // Table view instances
+    // Access needs to be protected by m_accessor_mutex
+    typedef std::vector<TableViewBase*> views;
+    mutable views m_views;
+
+    // Points to first bound row accessor, or is null if there are none.
+    mutable RowBase* m_row_accessors = nullptr;
+
+    // Mutex which must be locked any time the row accessor chain or m_views is used
+    mutable util::Mutex m_accessor_mutex;
+
+    // Used for queries: Items are added with link() method during buildup of query
+    mutable std::vector<size_t> m_link_chain;
+
+    /// Used only in connection with Group::advance_transact() and
+    /// Table::refresh_accessor_tree().
+    mutable bool m_mark;
+
+    mutable uint_fast64_t m_version;
+
+    void erase_row(size_t row_ndx, bool is_move_last_over);
+    void batch_erase_rows(const IntegerColumn& row_indexes, bool is_move_last_over);
+    void do_remove(size_t row_ndx, bool broken_reciprocal_backlinks);
+    void do_move_last_over(size_t row_ndx, bool broken_reciprocal_backlinks);
+    void do_swap_rows(size_t row_ndx_1, size_t row_ndx_2);
+    void do_move_row(size_t from_ndx, size_t to_ndx);
+    void do_merge_rows(size_t row_ndx, size_t new_row_ndx);
+    void do_clear(bool broken_reciprocal_backlinks);
+    size_t do_set_link(size_t col_ndx, size_t row_ndx, size_t target_row_ndx);
+    template <class ColType, class T>
+    size_t do_find_unique(ColType& col, size_t ndx, T&& value, bool& conflict);
+    template <class ColType>
+    size_t do_set_unique_null(ColType& col, size_t ndx, bool& conflict);
+    template <class ColType, class T>
+    size_t do_set_unique(ColType& column, size_t row_ndx, T&& value, bool& conflict);
+
+    void _add_search_index(size_t column_ndx);
+    void _remove_search_index(size_t column_ndx);
+
+    void rebuild_search_index(size_t current_file_format_version);
+
+    // Upgrades OldDateTime columns to Timestamp columns
+    void upgrade_olddatetime();
+
+    // Indicate that the current global state version has been "observed". Until this
+    // happens, bumping of the global version counter can be bypassed, as any query
+    // checking for a version change will see the older version change anyways.
+    // Also returns the table-local version.
+    uint64_t observe_version() const noexcept;
+
+    /// Update the version of this table and all tables which have links to it.
+    /// This causes all views referring to those tables to go out of sync, so that
+    /// calls to sync_if_needed() will bring the view up to date by reexecuting the
+    /// query.
+    ///
+    /// \param bump_global chooses whether the global versioning counter must be
+    /// bumped first as part of the update. This is the normal mode of operation,
+    /// when a change is made to the table. When calling recursively (following links
+    /// or going to the parent table), the parameter should be set to false to correctly
+    /// prune traversal.
+    void bump_version(bool bump_global = true) const noexcept;
+
+    /// Disable copying assignment.
+    ///
+    /// It could easily be implemented by calling assign(), but the
+    /// non-checking nature of the low-level dynamically typed API
+    /// makes it too risky to offer this feature as an
+    /// operator.
+    ///
+    /// FIXME: assign() has not yet been implemented, but the
+    /// intention is that it will copy the rows of the argument table
+    /// into this table after clearing the original contents, and for
+    /// target tables without a shared spec, it would also copy the
+    /// spec. For target tables with shared spec, it would be an error
+    /// to pass an argument table with an incompatible spec, but
+    /// assign() would not check for spec compatibility. This would
+    /// make it ideal as a basis for implementing operator=() for
+    /// typed tables.
+    Table& operator=(const Table&) = delete;
+
+    /// Used when constructing an accessor whose lifetime is going to be managed
+    /// by reference counting. The lifetime of accessors of free-standing tables
+    /// allocated on the stack by the application is not managed by reference
+    /// counting, so that is a case where this tag must **not** be specified.
+    class ref_count_tag {
+    };
+
+    /// Create an uninitialized accessor whose lifetime is managed by reference
+    /// counting.
+    Table(ref_count_tag, Allocator&);
+
+    void init(ref_type top_ref, ArrayParent*, size_t ndx_in_parent, bool skip_create_column_accessors = false);
+    void init(Spec* shared_spec, ArrayParent* parent_column, size_t parent_row_ndx);
+
+    static void do_insert_column(Descriptor&, size_t col_ndx, DataType type, StringData name,
+                                 LinkTargetInfo& link_target_info, bool nullable = false);
+    static void do_insert_column_unless_exists(Descriptor&, size_t col_ndx, DataType type, StringData name,
+                                               LinkTargetInfo& link, bool nullable = false,
+                                               bool* was_inserted = nullptr);
+    static void do_erase_column(Descriptor&, size_t col_ndx);
+    static void do_rename_column(Descriptor&, size_t col_ndx, StringData name);
+
+    static void do_add_search_index(Descriptor&, size_t col_ndx);
+    static void do_remove_search_index(Descriptor&, size_t col_ndx);
+
+    struct InsertSubtableColumns;
+    struct EraseSubtableColumns;
+    struct RenameSubtableColumns;
+
+    void insert_root_column(size_t col_ndx, DataType type, StringData name, LinkTargetInfo& link_target,
+                            bool nullable = false);
+    void erase_root_column(size_t col_ndx);
+    void do_insert_root_column(size_t col_ndx, ColumnType, StringData name, bool nullable = false);
+    void do_erase_root_column(size_t col_ndx);
+    void do_set_link_type(size_t col_ndx, LinkType);
+    void insert_backlink_column(size_t origin_table_ndx, size_t origin_col_ndx, size_t backlink_col_ndx);
+    void erase_backlink_column(size_t origin_table_ndx, size_t origin_col_ndx);
+    void update_link_target_tables(size_t old_col_ndx_begin, size_t new_col_ndx_begin);
+    void update_link_target_tables_after_column_move(size_t moved_from, size_t moved_to);
+
+    struct SubtableUpdater {
+        virtual void update(const SubtableColumn&, Array& subcolumns) = 0;
+        virtual void update_accessor(Table&) = 0;
+        virtual ~SubtableUpdater()
+        {
+        }
+    };
+    static void update_subtables(Descriptor&, SubtableUpdater*);
+    void update_subtables(const size_t* col_path_begin, const size_t* col_path_end, SubtableUpdater*);
+
+    struct AccessorUpdater {
+        virtual void update(Table&) = 0;
+        virtual void update_parent(Table&) = 0;
+        virtual ~AccessorUpdater()
+        {
+        }
+    };
+    void update_accessors(const size_t* col_path_begin, const size_t* col_path_end, AccessorUpdater&);
+
+    void create_degen_subtab_columns();
+    ColumnBase* create_column_accessor(ColumnType, size_t col_ndx, size_t ndx_in_parent);
+    void destroy_column_accessors() noexcept;
+
+    /// Called in the context of Group::commit() to ensure that
+    /// attached table accessors stay valid across a commit. Please
+    /// note that this works only for non-transactional commits. Table
+    /// accessors obtained during a transaction are always detached
+    /// when the transaction ends.
+    void update_from_parent(size_t old_baseline) noexcept;
+
+    // Support function for conversions
+    void to_string_header(std::ostream& out, std::vector<size_t>& widths) const;
+    void to_string_row(size_t row_ndx, std::ostream& out, const std::vector<size_t>& widths) const;
+
+    // recursive methods called by to_json, to follow links
+    void to_json(std::ostream& out, size_t link_depth, std::map<std::string, std::string>& renames,
+                 std::vector<ref_type>& followed) const;
+    void to_json_row(size_t row_ndx, std::ostream& out, size_t link_depth,
+                     std::map<std::string, std::string>& renames, std::vector<ref_type>& followed) const;
+    void to_json_row(size_t row_ndx, std::ostream& out, size_t link_depth = 0,
+                     std::map<std::string, std::string>* renames = nullptr) const;
+
+    // Detach accessor from underlying table. Caller must ensure that
+    // a reference count exists upon return, for example by obtaining
+    // an extra reference count before the call.
+    //
+    // This function puts this table accessor into the detached
+    // state. This detaches it from the underlying structure of array
+    // nodes. It also recursively detaches accessors for subtables,
+    // and the type descriptor accessor. When this function returns,
+    // is_attached() will return false.
+    //
+    // This function may be called for a table accessor that is
+    // already in the detached state (idempotency).
+    //
+    // It is also valid to call this function for a table accessor
+    // that has not yet been detached, but whose underlying structure
+    // of arrays have changed in an unpredictable/unknown way. This
+    // kind of change generally happens when a modifying table
+    // operation fails, and also when one transaction is ended and a
+    // new one is started.
+    void detach() noexcept;
+
+    /// Detach and remove all attached row, link list, and subtable
+    /// accessors. This function does not discard the descriptor accessor, if
+    /// any, and it does not discard column accessors either.
+    void discard_child_accessors() noexcept;
+
+    void discard_row_accessors() noexcept;
+
+    // Detach the type descriptor accessor if it exists.
+    void discard_desc_accessor() noexcept;
+
+    void bind_ptr() const noexcept;
+    void unbind_ptr() const noexcept;
+
+    void register_view(const TableViewBase* view);
+    void unregister_view(const TableViewBase* view) noexcept;
+    void move_registered_view(const TableViewBase* old_addr, const TableViewBase* new_addr) noexcept;
+    void discard_views() noexcept;
+
+    void register_row_accessor(RowBase*) const noexcept;
+    void unregister_row_accessor(RowBase*) const noexcept;
+    void do_unregister_row_accessor(RowBase*) const noexcept;
+
+    class UnbindGuard;
+
+    ColumnType get_real_column_type(size_t column_ndx) const noexcept;
+
+    /// If this table is a group-level table, the parent group is returned,
+    /// otherwise null is returned.
+    Group* get_parent_group() const noexcept;
+
+    const ColumnBase& get_column_base(size_t column_ndx) const noexcept;
+    ColumnBase& get_column_base(size_t column_ndx);
+
+    const ColumnBaseWithIndex& get_column_base_indexed(size_t ndx) const noexcept;
+    ColumnBaseWithIndex& get_column_base_indexed(size_t ndx);
+
+    template <class T, ColumnType col_type>
+    T& get_column(size_t ndx);
+
+    template <class T, ColumnType col_type>
+    const T& get_column(size_t ndx) const noexcept;
+
+    IntegerColumn& get_column(size_t column_ndx);
+    const IntegerColumn& get_column(size_t column_ndx) const noexcept;
+    IntNullColumn& get_column_int_null(size_t column_ndx);
+    const IntNullColumn& get_column_int_null(size_t column_ndx) const noexcept;
+    FloatColumn& get_column_float(size_t column_ndx);
+    const FloatColumn& get_column_float(size_t column_ndx) const noexcept;
+    DoubleColumn& get_column_double(size_t column_ndx);
+    const DoubleColumn& get_column_double(size_t column_ndx) const noexcept;
+    StringColumn& get_column_string(size_t column_ndx);
+    const StringColumn& get_column_string(size_t column_ndx) const noexcept;
+    BinaryColumn& get_column_binary(size_t column_ndx);
+    const BinaryColumn& get_column_binary(size_t column_ndx) const noexcept;
+    StringEnumColumn& get_column_string_enum(size_t column_ndx);
+    const StringEnumColumn& get_column_string_enum(size_t column_ndx) const noexcept;
+    SubtableColumn& get_column_table(size_t column_ndx);
+    const SubtableColumn& get_column_table(size_t column_ndx) const noexcept;
+    MixedColumn& get_column_mixed(size_t column_ndx);
+    const MixedColumn& get_column_mixed(size_t column_ndx) const noexcept;
+    TimestampColumn& get_column_timestamp(size_t column_ndx);
+    const TimestampColumn& get_column_timestamp(size_t column_ndx) const noexcept;
+    const LinkColumnBase& get_column_link_base(size_t ndx) const noexcept;
+    LinkColumnBase& get_column_link_base(size_t ndx);
+    const LinkColumn& get_column_link(size_t ndx) const noexcept;
+    LinkColumn& get_column_link(size_t ndx);
+    const LinkListColumn& get_column_link_list(size_t ndx) const noexcept;
+    LinkListColumn& get_column_link_list(size_t ndx);
+    const BacklinkColumn& get_column_backlink(size_t ndx) const noexcept;
+    BacklinkColumn& get_column_backlink(size_t ndx);
+
+    void verify_column(size_t col_ndx, const ColumnBase* col) const;
+
+    void instantiate_before_change();
+    void validate_column_type(const ColumnBase& col, ColumnType expected_type, size_t ndx) const;
+
+    static size_t get_size_from_ref(ref_type top_ref, Allocator&) noexcept;
+    static size_t get_size_from_ref(ref_type spec_ref, ref_type columns_ref, Allocator&) noexcept;
+
+    const Table* get_parent_table_ptr(size_t* column_ndx_out = nullptr) const noexcept;
+    Table* get_parent_table_ptr(size_t* column_ndx_out = nullptr) noexcept;
+
+    /// Create an empty table with independent spec and return just
+    /// the reference to the underlying memory.
+    static ref_type create_empty_table(Allocator&);
+
+    /// Create a column of the specified type, fill it with the
+    /// specified number of default values, and return just the
+    /// reference to the underlying memory.
+    static ref_type create_column(ColumnType column_type, size_t num_default_values, bool nullable, Allocator&);
+
+    /// Construct a copy of the columns array of this table using the
+    /// specified allocator and return just the ref to that array.
+    ///
+    /// In the clone, no string column will be of the enumeration
+    /// type.
+    ref_type clone_columns(Allocator&) const;
+
+    /// Construct a complete copy of this table (including its spec)
+    /// using the specified allocator and return just the ref to the
+    /// new top array.
+    ref_type clone(Allocator&) const;
+
+    /// True for `col_type_Link` and `col_type_LinkList`.
+    static bool is_link_type(ColumnType) noexcept;
+
+    void connect_opposite_link_columns(size_t link_col_ndx, Table& target_table, size_t backlink_col_ndx) noexcept;
+
+    //@{
+
+    /// Cascading removal of strong links.
+    ///
+    /// cascade_break_backlinks_to() removes all backlinks pointing to the row
+    /// at \a row_ndx. Additionally, if this causes the number of **strong**
+    /// backlinks originating from a particular opposite row (target row of
+    /// corresponding forward link) to drop to zero, and that row is not already
+    /// in \a state.rows, then that row is added to \a state.rows, and
+    /// cascade_break_backlinks_to() is called recursively for it. This
+    /// operation is the first half of the cascading row removal operation. The
+    /// second half is performed by passing the resulting contents of \a
+    /// state.rows to remove_backlink_broken_rows().
+    ///
+    /// Operations that trigger cascading row removal due to explicit removal of
+    /// one or more rows (the *initiating rows*), should add those rows to \a
+    /// rows initially, and then call cascade_break_backlinks_to() once for each
+    /// of them in turn. This is opposed to carrying out the explicit row
+    /// removals independently, which is also possible, but does require that
+    /// any initiating rows, that end up in \a state.rows due to link cycles,
+    /// are removed before passing \a state.rows to
+    /// remove_backlink_broken_rows(). In the case of clear(), where all rows of
+    /// a table are explicitly removed, it is better to use
+    /// cascade_break_backlinks_to_all_rows(), and then carry out the table
+    /// clearing as an independent step. For operations that trigger cascading
+    /// row removal for other reasons than explicit row removal, \a state.rows
+    /// must be empty initially, but cascade_break_backlinks_to() must still be
+    /// called for each of the initiating rows.
+    ///
+    /// When the last non-recursive invocation of cascade_break_backlinks_to()
+    /// returns, all forward links originating from a row in \a state.rows have
+    /// had their reciprocal backlinks removed, so remove_backlink_broken_rows()
+    /// does not perform reciprocal backlink removal at all. Additionally, all
+    /// remaining backlinks originating from rows in \a state.rows are
+    /// guaranteed to point to rows that are **not** in \a state.rows. This is
+    /// true because any backlink that was pointing to a row in \a state.rows
+    /// has been removed by one of the invocations of
+    /// cascade_break_backlinks_to(). The set of forward links, that correspond
+    /// to these remaining backlinks, is precisely the set of forward links that
+    /// need to be removed/nullified by remove_backlink_broken_rows(), which it
+    /// does by way of reciprocal forward link removal. Note also, that while
+    /// all the rows in \a state.rows can have remaining **weak** backlinks
+    /// originating from them, only the initiating rows in \a state.rows can
+    /// have remaining **strong** backlinks originating from them. This is true
+    /// because a non-initiating row is added to \a state.rows only when the
+    /// last backlink originating from it is lost.
+    ///
+    /// Each row removal is replicated individually (as opposed to one
+    /// replication instruction for the entire cascading operation). This is
+    /// done because it provides an easy way for Group::advance_transact() to
+    /// know which tables are affected by the cascade. Note that this has
+    /// several important consequences: First of all, the replication log
+    /// receiver must execute the row removal instructions in a non-cascading
+    /// fashion, meaning that there will be an asymmetry between the two sides
+    /// in how the effect of the cascade is brought about. While this is fine
+    /// for simple 1-to-1 replication, it may end up interfering badly with
+    /// *transaction merging*, when that feature is introduced. Imagine for
+    /// example that the cascade initiating operation gets canceled during
+    /// conflict resolution, but some, or all of the induced row removals get to
+    /// stay. That would break causal consistency. It is important, however, for
+    /// transaction merging that the cascaded row removals are explicitly
+    /// mentioned in the replication log, such that they can be used to adjust
+    /// row indexes during the *operational transform*.
+    ///
+    /// cascade_break_backlinks_to_all_rows() has the same affect as calling
+    /// cascade_break_backlinks_to() once for each row in the table. When
+    /// calling this function, \a state.stop_on_table must be set to the origin
+    /// table (origin table of corresponding forward links), and \a
+    /// state.stop_on_link_list_column must be null.
+    ///
+    /// It is immaterial which table remove_backlink_broken_rows() is called on,
+    /// as long it that table is in the same group as the removed rows.
+
+    void cascade_break_backlinks_to(size_t row_ndx, CascadeState& state);
+    void cascade_break_backlinks_to_all_rows(CascadeState& state);
+    void remove_backlink_broken_rows(const CascadeState&);
+
+    //@}
+
+    /// Used by query. Follows chain of link columns and returns final target table
+    const Table* get_link_chain_target(const std::vector<size_t>& link_chain) const;
+
+    /// Remove the specified row by the 'move last over' method.
+    void do_move_last_over(size_t row_ndx);
+
+    // Precondition: 1 <= end - begin
+    size_t* record_subtable_path(size_t* begin, size_t* end) const noexcept;
+
+    /// Check if an accessor exists for the specified subtable. If it does,
+    /// return a pointer to it, otherwise return null. This function assumes
+    /// that the specified column index in a valid index into `m_cols` but does
+    /// not otherwise assume more than minimal accessor consistency (see
+    /// AccessorConsistencyLevels.)
+    TableRef get_subtable_accessor(size_t col_ndx, size_t row_ndx) noexcept;
+
+    /// Unless the column accessor is missing, this function returns the
+    /// accessor for the target table of the specified link-type column. The
+    /// column accessor is said to be missing if `m_cols[col_ndx]` is null, and
+    /// this can happen only during certain operations such as the updating of
+    /// the accessor tree when a read transaction is advanced. Note that for
+    /// link type columns, the target table accessor exists when, and only when
+    /// the origin table accessor exists. This function assumes that the
+    /// specified column index in a valid index into `m_cols` and that the
+    /// column is a link-type column. Beyond that, it assume nothing more than
+    /// minimal accessor consistency (see AccessorConsistencyLevels.)
+    Table* get_link_target_table_accessor(size_t col_ndx) noexcept;
+
+    void discard_subtable_accessor(size_t col_ndx, size_t row_ndx) noexcept;
+
+    void adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept;
+    void adj_acc_erase_row(size_t row_ndx) noexcept;
+    void adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept;
+    void adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept;
+    void adj_acc_merge_rows(size_t old_row_ndx, size_t new_row_ndx) noexcept;
+
+    /// Adjust this table accessor and its subordinates after move_last_over()
+    /// (or its inverse).
+    ///
+    /// First, any row, subtable, or link list accessors registered as being at
+    /// \a to_row_ndx will be detached, as that row is assumed to have been
+    /// replaced. Next, any row, subtable, or link list accessors registered as
+    /// being at \a from_row_ndx, will be reregistered as being at \a
+    /// to_row_ndx, as the row at \a from_row_ndx is assumed to have been moved
+    /// to \a to_row_ndx.
+    ///
+    /// Crucially, if \a to_row_ndx is equal to \a from_row_ndx, then row,
+    /// subtable, or link list accessors at that row are **still detached**.
+    ///
+    /// Additionally, this function causes all link-adjacent tables to be marked
+    /// (dirty). Two tables are link-adjacent if one is the target table of a
+    /// link column of the other table. Note that this marking follows these
+    /// relations in both directions, but only to a depth of one.
+    ///
+    /// When this function is used in connection with move_last_over(), set \a
+    /// to_row_ndx to the index of the row to be removed, and set \a
+    /// from_row_ndx to the index of the last row in the table. As mentioned
+    /// earlier, this function can also be used in connection with the **inverse
+    /// of** move_last_over(), which is an operation that vacates a row by
+    /// moving its contents into a new last row of the table. In that case, set
+    /// \a to_row_ndx to one plus the index of the last row in the table, and
+    /// set \a from_row_ndx to the index of the row to be vacated.
+    ///
+    /// This function is used as part of Table::refresh_accessor_tree() to
+    /// promote the state of the accessors from Minimal Consistency into
+    /// Structural Correspondence, so it must be able to execute without
+    /// accessing the underlying array nodes.
+    void adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept;
+
+    void adj_acc_clear_root_table() noexcept;
+    void adj_acc_clear_nonroot_table() noexcept;
+    void adj_row_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept;
+    void adj_row_acc_erase_row(size_t row_ndx) noexcept;
+    void adj_row_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept;
+    void adj_row_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept;
+    void adj_row_acc_merge_rows(size_t old_row_ndx, size_t new_row_ndx) noexcept;
+
+    /// Called by adj_acc_move_over() to adjust row accessors.
+    void adj_row_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept;
+
+    void adj_insert_column(size_t col_ndx);
+    void adj_erase_column(size_t col_ndx) noexcept;
+
+    bool is_marked() const noexcept;
+    void mark() noexcept;
+    void unmark() noexcept;
+    void recursive_mark() noexcept;
+    void mark_link_target_tables(size_t col_ndx_begin) noexcept;
+    void mark_opposite_link_tables() noexcept;
+
+    Replication* get_repl() noexcept;
+
+    void set_ndx_in_parent(size_t ndx_in_parent) noexcept;
+
+    /// Refresh the part of the accessor tree that is rooted at this
+    /// table. Subtable accessors will be refreshed only if they are marked
+    /// (Table::m_mark), and this applies recursively to subtables of
+    /// subtables. All refreshed table accessors (including this one) will be
+    /// unmarked upon return.
+    ///
+    /// The following conditions are necessary and sufficient for the proper
+    /// operation of this function:
+    ///
+    ///  - This table must be a group-level table, or a subtable. It must not be
+    ///    a free-standing table (because a free-standing table has no parent).
+    ///
+    ///  - The `index in parent` property is correct. The `index in parent`
+    ///    property of the table is the `index in parent` property of
+    ///    `m_columns` for subtables with shared descriptor, and the `index in
+    ///    parent` property of `m_top` for all other tables.
+    ///
+    ///  - If this table has shared descriptor, then the `index in parent`
+    ///    property of the contained spec accessor is correct.
+    ///
+    ///  - The parent accessor is in a valid state (already refreshed). If the
+    ///    parent is a group, then the group accessor (excluding its table
+    ///    accessors) must be in a valid state. If the parent is a table, then
+    ///    the table accessor (excluding its subtable accessors) must be in a
+    ///    valid state.
+    ///
+    ///  - Every descendant subtable accessor is marked if it needs to be
+    ///    refreshed, or if it has a descendant accessor that needs to be
+    ///    refreshed.
+    ///
+    ///  - This table accessor, as well as all its descendant accessors, are in
+    ///    structural correspondence with the underlying node hierarchy whose
+    ///    root ref is stored in the parent (see AccessorConsistencyLevels).
+    void refresh_accessor_tree();
+
+    void refresh_spec_accessor();
+
+    void refresh_column_accessors(size_t col_ndx_begin = 0);
+
+    // Look for link columns starting from col_ndx_begin.
+    // If a link column is found, follow the link and update it's
+    // backlink column accessor if it is in different table.
+    void refresh_link_target_accessors(size_t col_ndx_begin = 0);
+
+    bool is_cross_table_link_target() const noexcept;
+    std::recursive_mutex* get_parent_accessor_management_lock() const;
+#ifdef REALM_DEBUG
+    void to_dot_internal(std::ostream&) const;
+#endif
+
+    friend class SubtableNode;
+    friend class _impl::TableFriend;
+    friend class Query;
+    friend class metrics::QueryInfo;
+    template <class>
+    friend class util::bind_ptr;
+    template <class>
+    friend class SimpleQuerySupport;
+    friend class LangBindHelper;
+    friend class TableViewBase;
+    template <class T>
+    friend class Columns;
+    friend class Columns<StringData>;
+    friend class ParentNode;
+    template <class>
+    friend class SequentialGetter;
+    friend class RowBase;
+    friend class LinksToNode;
+    friend class LinkMap;
+    friend class LinkView;
+    friend class Group;
+};
+
+class Table::Parent : public ArrayParent {
+public:
+    ~Parent() noexcept override
+    {
+    }
+
+protected:
+    virtual StringData get_child_name(size_t child_ndx) const noexcept;
+
+    /// If children are group-level tables, then this function returns the
+    /// group. Otherwise it returns null.
+    virtual Group* get_parent_group() noexcept;
+
+    /// If children are subtables, then this function returns the
+    /// parent table. Otherwise it returns null.
+    ///
+    /// If \a column_ndx_out is not null, this function must assign the index of
+    /// the column within the parent table to `*column_ndx_out` when , and only
+    /// when this table parent is a column in a parent table.
+    virtual Table* get_parent_table(size_t* column_ndx_out = nullptr) noexcept;
+
+    virtual Spec* get_subtable_spec() noexcept;
+
+    /// Must be called whenever a child table accessor is about to be destroyed.
+    ///
+    /// Note that the argument is a pointer to the child Table rather than its
+    /// `ndx_in_parent` property. This is because only minimal accessor
+    /// consistency can be assumed by this function.
+    virtual void child_accessor_destroyed(Table* child) noexcept = 0;
+
+
+    virtual size_t* record_subtable_path(size_t* begin, size_t* end) noexcept;
+    virtual std::recursive_mutex* get_accessor_management_lock() noexcept = 0;
+
+    friend class Table;
+};
+
+
+// Implementation:
+
+
+inline uint_fast64_t Table::get_version_counter() const noexcept
+{
+    return observe_version();
+}
+
+inline uint64_t Table::observe_version() const noexcept
+{
+    m_top.get_alloc().observe_version();
+    return m_version;
+}
+
+inline void Table::bump_version(bool bump_global) const noexcept
+{
+    if (bump_global) {
+        // This is only set on initial entry through an operation on the same
+        // table.  recursive calls (via parent or via backlinks) must be done
+        // with bump_global=false.
+        m_top.get_alloc().bump_global_version();
+    }
+    if (m_top.get_alloc().should_propagate_version(m_version)) {
+        if (const Table* parent = get_parent_table_ptr())
+            parent->bump_version(false);
+        // Recurse through linked tables, use m_mark to avoid infinite recursion
+        for (auto& column_ptr : m_cols) {
+            // We may meet a null pointer in place of a backlink column, pending
+            // replacement with a new one. This can happen ONLY when creation of
+            // the corresponding forward link column in the origin table is
+            // pending as well. In this case it is ok to just ignore the zeroed
+            // backlink column, because the origin table is guaranteed to also
+            // be refreshed/marked dirty and hence have it's version bumped.
+            if (column_ptr != nullptr)
+                column_ptr->bump_link_origin_table_version();
+        }
+    }
+}
+
+inline void Table::remove(size_t row_ndx)
+{
+    bool is_move_last_over = false;
+    erase_row(row_ndx, is_move_last_over); // Throws
+}
+
+inline void Table::move_last_over(size_t row_ndx)
+{
+    bool is_move_last_over = true;
+    erase_row(row_ndx, is_move_last_over); // Throws
+}
+
+inline void Table::remove_last()
+{
+    if (!is_empty())
+        remove(size() - 1);
+}
+
+// A good place to start if you want to understand the memory ordering
+// chosen for the operations below is http://preshing.com/20130922/acquire-and-release-fences/
+inline void Table::bind_ptr() const noexcept
+{
+    m_ref_count.fetch_add(1, std::memory_order_relaxed);
+}
+
+inline void Table::unbind_ptr() const noexcept
+{
+    // The delete operation runs the destructor, and the destructor
+    // must always see all changes to the object being deleted.
+    // Within each thread, we know that unbind_ptr will always happen after
+    // any changes, so it is a convenient place to do a release.
+    // The release will then be observed by the acquire fence in
+    // the case where delete is actually called (the count reaches 0)
+    if (m_ref_count.fetch_sub(1, std::memory_order_release) != 1) {
+        return;
+    }
+
+    std::atomic_thread_fence(std::memory_order_acquire);
+
+    std::recursive_mutex* lock = get_parent_accessor_management_lock();
+    if (lock) {
+        std::lock_guard<std::recursive_mutex> lg(*lock);
+        if (m_ref_count == 0)
+            delete this;
+    }
+    else {
+        delete this;
+    }
+}
+
+inline void Table::register_view(const TableViewBase* view)
+{
+    util::LockGuard lock(m_accessor_mutex);
+    // Casting away constness here - operations done on tableviews
+    // through m_views are all internal and preserving "some" kind
+    // of logical constness.
+    m_views.push_back(const_cast<TableViewBase*>(view));
+}
+
+inline bool Table::is_attached() const noexcept
+{
+    // Note that it is not possible to tie the state of attachment of a table to
+    // the state of attachment of m_top, because tables with shared spec do not
+    // have a 'top' array. Neither is it possible to tie it to the state of
+    // attachment of m_columns, because subtables with shared spec start out in
+    // a degenerate form where they do not have a 'columns' array. For these
+    // reasons, it is neccessary to define the notion of attachment for a table
+    // as follows: A table is attached if, and ony if m_column stores a non-null
+    // parent pointer. This works because even for degenerate subtables,
+    // m_columns is initialized with the correct parent pointer.
+    return m_columns.has_parent();
+}
+
+inline StringData Table::get_name() const noexcept
+{
+    REALM_ASSERT(is_attached());
+    const Array& real_top = m_top.is_attached() ? m_top : m_columns;
+    ArrayParent* parent = real_top.get_parent();
+    if (!parent)
+        return StringData("");
+    size_t index_in_parent = real_top.get_ndx_in_parent();
+    REALM_ASSERT(dynamic_cast<Parent*>(parent));
+    return static_cast<Parent*>(parent)->get_child_name(index_in_parent);
+}
+
+inline size_t Table::get_column_count() const noexcept
+{
+    REALM_ASSERT(is_attached());
+    return m_spec->get_public_column_count();
+}
+
+inline StringData Table::get_column_name(size_t ndx) const noexcept
+{
+    REALM_ASSERT_3(ndx, <, get_column_count());
+    return m_spec->get_column_name(ndx);
+}
+
+inline size_t Table::get_column_index(StringData name) const noexcept
+{
+    REALM_ASSERT(is_attached());
+    return m_spec->get_column_index(name);
+}
+
+inline ColumnType Table::get_real_column_type(size_t ndx) const noexcept
+{
+    REALM_ASSERT_3(ndx, <, m_spec->get_column_count());
+    return m_spec->get_column_type(ndx);
+}
+
+inline DataType Table::get_column_type(size_t ndx) const noexcept
+{
+    REALM_ASSERT_3(ndx, <, m_spec->get_column_count());
+    return m_spec->get_public_column_type(ndx);
+}
+
+template <class Col, ColumnType col_type>
+inline Col& Table::get_column(size_t ndx)
+{
+    ColumnBase& col = get_column_base(ndx);
+#ifdef REALM_DEBUG
+    validate_column_type(col, col_type, ndx);
+#endif
+    REALM_ASSERT(typeid(Col) == typeid(col));
+    return static_cast<Col&>(col);
+}
+
+template <class Col, ColumnType col_type>
+inline const Col& Table::get_column(size_t ndx) const noexcept
+{
+    const ColumnBase& col = get_column_base(ndx);
+#ifdef REALM_DEBUG
+    validate_column_type(col, col_type, ndx);
+#endif
+    REALM_ASSERT(typeid(Col) == typeid(col));
+    return static_cast<const Col&>(col);
+}
+
+inline bool Table::has_shared_type() const noexcept
+{
+    REALM_ASSERT(is_attached());
+    return !m_top.is_attached();
+}
+
+inline void Table::verify_column(size_t col_ndx, const ColumnBase* col) const
+{
+    // Check if the column exists at the expected location
+    if (REALM_LIKELY(col_ndx < m_cols.size() && m_cols[col_ndx] == col))
+        return;
+    // The column might be elsewhere in the list
+    for (auto c : m_cols) {
+        if (c == col)
+            return;
+    }
+    throw LogicError(LogicError::column_does_not_exist);
+}
+
+class Table::UnbindGuard {
+public:
+    UnbindGuard(Table* table) noexcept
+        : m_table(table)
+    {
+    }
+
+    ~UnbindGuard() noexcept
+    {
+        if (m_table)
+            m_table->unbind_ptr();
+    }
+
+    Table& operator*() const noexcept
+    {
+        return *m_table;
+    }
+
+    Table* operator->() const noexcept
+    {
+        return m_table;
+    }
+
+    Table* get() const noexcept
+    {
+        return m_table;
+    }
+
+    Table* release() noexcept
+    {
+        Table* table = m_table;
+        m_table = nullptr;
+        return table;
+    }
+
+private:
+    Table* m_table;
+};
+
+
+inline Table::Table(Allocator& alloc)
+    : m_top(alloc)
+    , m_columns(alloc)
+{
+    m_ref_count = 1; // Explicitly managed lifetime
+
+    ref_type ref = create_empty_table(alloc); // Throws
+    Parent* parent = nullptr;
+    size_t ndx_in_parent = 0;
+    init(ref, parent, ndx_in_parent);
+}
+
+inline Table::Table(const Table& t, Allocator& alloc)
+    : m_top(alloc)
+    , m_columns(alloc)
+{
+    m_ref_count = 1; // Explicitly managed lifetime
+
+    ref_type ref = t.clone(alloc); // Throws
+    Parent* parent = nullptr;
+    size_t ndx_in_parent = 0;
+    init(ref, parent, ndx_in_parent);
+}
+
+inline Table::Table(ref_count_tag, Allocator& alloc)
+    : m_top(alloc)
+    , m_columns(alloc)
+{
+    m_ref_count = 0; // Lifetime managed by reference counting
+}
+
+inline Allocator& Table::get_alloc() const
+{
+    return m_top.get_alloc();
+}
+
+inline TableRef Table::create(Allocator& alloc)
+{
+    std::unique_ptr<Table> table(new Table(ref_count_tag(), alloc)); // Throws
+    ref_type ref = create_empty_table(alloc);                        // Throws
+    Parent* parent = nullptr;
+    size_t ndx_in_parent = 0;
+    table->init(ref, parent, ndx_in_parent); // Throws
+    return table.release()->get_table_ref();
+}
+
+inline TableRef Table::copy(Allocator& alloc) const
+{
+    std::unique_ptr<Table> table(new Table(ref_count_tag(), alloc)); // Throws
+    ref_type ref = clone(alloc);                                     // Throws
+    Parent* parent = nullptr;
+    size_t ndx_in_parent = 0;
+    table->init(ref, parent, ndx_in_parent); // Throws
+    return table.release()->get_table_ref();
+}
+
+// For use by queries
+template <class T>
+inline Columns<T> Table::column(size_t column_ndx)
+{
+    std::vector<size_t> link_chain = std::move(m_link_chain);
+    m_link_chain.clear();
+
+    // Check if user-given template type equals Realm type. Todo, we should clean up and reuse all our
+    // type traits (all the is_same() cases below).
+    const Table* table = get_link_chain_target(link_chain);
+
+    realm::DataType ct = table->get_column_type(column_ndx);
+    if (std::is_same<T, int64_t>::value && ct != type_Int)
+        throw(LogicError::type_mismatch);
+    else if (std::is_same<T, bool>::value && ct != type_Bool)
+        throw(LogicError::type_mismatch);
+    else if (std::is_same<T, realm::OldDateTime>::value && ct != type_OldDateTime)
+        throw(LogicError::type_mismatch);
+    else if (std::is_same<T, float>::value && ct != type_Float)
+        throw(LogicError::type_mismatch);
+    else if (std::is_same<T, double>::value && ct != type_Double)
+        throw(LogicError::type_mismatch);
+
+    if (std::is_same<T, Link>::value || std::is_same<T, LinkList>::value || std::is_same<T, BackLink>::value) {
+        link_chain.push_back(column_ndx);
+    }
+
+    return Columns<T>(column_ndx, this, std::move(link_chain));
+}
+
+template <class T>
+inline Columns<T> Table::column(const Table& origin, size_t origin_col_ndx)
+{
+    static_assert(std::is_same<T, BackLink>::value, "");
+
+    size_t origin_table_ndx = origin.get_index_in_group();
+    const Table& current_target_table = *get_link_chain_target(m_link_chain);
+    size_t backlink_col_ndx = current_target_table.m_spec->find_backlink_column(origin_table_ndx, origin_col_ndx);
+
+    std::vector<size_t> link_chain = std::move(m_link_chain);
+    m_link_chain.clear();
+    link_chain.push_back(backlink_col_ndx);
+
+    return Columns<T>(backlink_col_ndx, this, std::move(link_chain));
+}
+
+template <class T>
+SubQuery<T> Table::column(size_t column_ndx, Query subquery)
+{
+    static_assert(std::is_same<T, LinkList>::value, "A subquery must involve a link list or backlink column");
+    return SubQuery<T>(column<T>(column_ndx), std::move(subquery));
+}
+
+template <class T>
+SubQuery<T> Table::column(const Table& origin, size_t origin_col_ndx, Query subquery)
+{
+    static_assert(std::is_same<T, BackLink>::value, "A subquery must involve a link list or backlink column");
+    return SubQuery<T>(column<T>(origin, origin_col_ndx), std::move(subquery));
+}
+
+// For use by queries
+inline Table& Table::link(size_t link_column)
+{
+    m_link_chain.push_back(link_column);
+    return *this;
+}
+
+inline Table& Table::backlink(const Table& origin, size_t origin_col_ndx)
+{
+    size_t origin_table_ndx = origin.get_index_in_group();
+    const Table& current_target_table = *get_link_chain_target(m_link_chain);
+    size_t backlink_col_ndx = current_target_table.m_spec->find_backlink_column(origin_table_ndx, origin_col_ndx);
+    return link(backlink_col_ndx);
+}
+
+inline bool Table::is_empty() const noexcept
+{
+    return m_size == 0;
+}
+
+inline size_t Table::size() const noexcept
+{
+    return m_size;
+}
+
+inline Table::RowExpr Table::get(size_t row_ndx) noexcept
+{
+    REALM_ASSERT_3(row_ndx, <, size());
+    return RowExpr(this, row_ndx);
+}
+
+inline Table::ConstRowExpr Table::get(size_t row_ndx) const noexcept
+{
+    REALM_ASSERT_3(row_ndx, <, size());
+    return ConstRowExpr(this, row_ndx);
+}
+
+inline Table::RowExpr Table::front() noexcept
+{
+    return get(0);
+}
+
+inline Table::ConstRowExpr Table::front() const noexcept
+{
+    return get(0);
+}
+
+inline Table::RowExpr Table::back() noexcept
+{
+    return get(m_size - 1);
+}
+
+inline Table::ConstRowExpr Table::back() const noexcept
+{
+    return get(m_size - 1);
+}
+
+inline Table::RowExpr Table::operator[](size_t row_ndx) noexcept
+{
+    return get(row_ndx);
+}
+
+inline Table::ConstRowExpr Table::operator[](size_t row_ndx) const noexcept
+{
+    return get(row_ndx);
+}
+
+inline size_t Table::add_empty_row(size_t num_rows)
+{
+    size_t row_ndx = m_size;
+    insert_empty_row(row_ndx, num_rows); // Throws
+    return row_ndx;                      // Return index of first new row
+}
+
+inline ConstTableRef Table::get_subtable_tableref(size_t col_ndx, size_t row_ndx) const
+{
+    return const_cast<Table*>(this)->get_subtable_tableref(col_ndx, row_ndx); // Throws
+}
+
+inline bool Table::is_null_link(size_t col_ndx, size_t row_ndx) const noexcept
+{
+    return get_link(col_ndx, row_ndx) == realm::npos;
+}
+
+inline ConstTableRef Table::get_link_target(size_t col_ndx) const noexcept
+{
+    return const_cast<Table*>(this)->get_link_target(col_ndx);
+}
+
+template <class E>
+inline void Table::set_enum(size_t column_ndx, size_t row_ndx, E value)
+{
+    set_int(column_ndx, row_ndx, value);
+}
+
+inline void Table::nullify_link(size_t col_ndx, size_t row_ndx)
+{
+    set_link(col_ndx, row_ndx, realm::npos);
+}
+
+inline TableRef Table::get_subtable(size_t column_ndx, size_t row_ndx)
+{
+    return get_subtable_tableref(column_ndx, row_ndx);
+}
+
+inline ConstTableRef Table::get_subtable(size_t column_ndx, size_t row_ndx) const
+{
+    return get_subtable_tableref(column_ndx, row_ndx);
+}
+
+inline ConstTableRef Table::get_parent_table(size_t* column_ndx_out) const noexcept
+{
+    return ConstTableRef(get_parent_table_ptr(column_ndx_out));
+}
+
+inline TableRef Table::get_parent_table(size_t* column_ndx_out) noexcept
+{
+    return TableRef(get_parent_table_ptr(column_ndx_out));
+}
+
+inline bool Table::is_group_level() const noexcept
+{
+    return bool(get_parent_group());
+}
+
+inline bool Table::operator==(const Table& t) const
+{
+    return *m_spec == *t.m_spec && compare_rows(t); // Throws
+}
+
+inline bool Table::operator!=(const Table& t) const
+{
+    return !(*this == t); // Throws
+}
+
+inline bool Table::is_degenerate() const
+{
+    if (!is_attached()) {
+        throw LogicError{LogicError::detached_accessor};
+    }
+
+    return !m_columns.is_attached();
+}
+
+inline void Table::set_into_mixed(Table* parent, size_t col_ndx, size_t row_ndx) const
+{
+    parent->set_mixed_subtable(col_ndx, row_ndx, this);
+}
+
+inline size_t Table::get_size_from_ref(ref_type top_ref, Allocator& alloc) noexcept
+{
+    const char* top_header = alloc.translate(top_ref);
+    std::pair<int_least64_t, int_least64_t> p = Array::get_two(top_header, 0);
+    ref_type spec_ref = to_ref(p.first), columns_ref = to_ref(p.second);
+    return get_size_from_ref(spec_ref, columns_ref, alloc);
+}
+
+inline Table* Table::get_parent_table_ptr(size_t* column_ndx_out) noexcept
+{
+    const Table* parent = const_cast<const Table*>(this)->get_parent_table_ptr(column_ndx_out);
+    return const_cast<Table*>(parent);
+}
+
+inline bool Table::is_link_type(ColumnType col_type) noexcept
+{
+    return col_type == col_type_Link || col_type == col_type_LinkList;
+}
+
+inline size_t* Table::record_subtable_path(size_t* begin, size_t* end) const noexcept
+{
+    const Array& real_top = m_top.is_attached() ? m_top : m_columns;
+    size_t index_in_parent = real_top.get_ndx_in_parent();
+    REALM_ASSERT_3(begin, <, end);
+    *begin++ = index_in_parent;
+    ArrayParent* parent = real_top.get_parent();
+    REALM_ASSERT(parent);
+    REALM_ASSERT(dynamic_cast<Parent*>(parent));
+    return static_cast<Parent*>(parent)->record_subtable_path(begin, end);
+}
+
+inline size_t* Table::Parent::record_subtable_path(size_t* begin, size_t*) noexcept
+{
+    return begin;
+}
+
+inline bool Table::is_marked() const noexcept
+{
+    return m_mark;
+}
+
+inline void Table::mark() noexcept
+{
+    m_mark = true;
+}
+
+inline void Table::unmark() noexcept
+{
+    m_mark = false;
+}
+
+inline Replication* Table::get_repl() noexcept
+{
+    return m_top.get_alloc().get_replication();
+}
+
+inline void Table::set_ndx_in_parent(size_t ndx_in_parent) noexcept
+{
+    if (m_top.is_attached()) {
+        // Root table (independent descriptor)
+        m_top.set_ndx_in_parent(ndx_in_parent);
+    }
+    else {
+        // Subtable with shared descriptor
+        m_columns.set_ndx_in_parent(ndx_in_parent);
+    }
+}
+
+// Declare our explicit specializations so that the inline wrappers don't try
+// to instantiate them
+template<> int64_t Table::get<int64_t>(size_t, size_t) const noexcept;
+template<> util::Optional<int64_t> Table::get<util::Optional<int64_t>>(size_t, size_t) const noexcept;
+template<> bool Table::get<bool>(size_t, size_t) const noexcept;
+template<> Optional<bool> Table::get<Optional<bool>>(size_t, size_t) const noexcept;
+template<> float Table::get<float>(size_t, size_t) const noexcept;
+template<> util::Optional<float> Table::get<util::Optional<float>>(size_t, size_t) const noexcept;
+template<> double Table::get<double>(size_t, size_t) const noexcept;
+template<> util::Optional<double> Table::get<util::Optional<double>>(size_t, size_t) const noexcept;
+template<> OldDateTime Table::get<OldDateTime>(size_t, size_t) const noexcept;
+template<> Timestamp Table::get<Timestamp>(size_t, size_t) const noexcept;
+template<> StringData Table::get<StringData>(size_t, size_t) const noexcept;
+template<> BinaryData Table::get<BinaryData>(size_t, size_t) const noexcept;
+template<> BinaryIterator Table::get<BinaryIterator>(size_t, size_t) const noexcept;
+template<> Mixed Table::get<Mixed>(size_t, size_t) const noexcept;
+
+template<> void Table::set<int64_t>(size_t, size_t, int64_t, bool);
+template<> void Table::set<bool>(size_t, size_t, bool, bool);
+template<> void Table::set<float>(size_t, size_t, float, bool);
+template<> void Table::set<double>(size_t, size_t, double, bool);
+template<> void Table::set<OldDateTime>(size_t, size_t, OldDateTime, bool);
+template<> void Table::set<Timestamp>(size_t, size_t, Timestamp, bool);
+template<> void Table::set<StringData>(size_t, size_t, StringData, bool);
+template<> void Table::set<BinaryData>(size_t, size_t, BinaryData, bool);
+template<> void Table::set<Mixed>(size_t, size_t, Mixed, bool);
+template<> void Table::set<null>(size_t, size_t, null, bool);
+
+template<> size_t Table::set_unique<int64_t>(size_t, size_t, int64_t);
+template<> size_t Table::set_unique<StringData>(size_t, size_t, StringData);
+template<> size_t Table::set_unique<null>(size_t, size_t, null);
+
+
+inline int64_t Table::get_int(size_t col_ndx, size_t ndx) const noexcept
+{
+    if (is_nullable(col_ndx))
+        return get<util::Optional<int64_t>>(col_ndx, ndx).value_or(0);
+    else
+        return get<int64_t>(col_ndx, ndx);
+}
+
+inline size_t Table::set_int_unique(size_t col_ndx, size_t ndx, int_fast64_t value)
+{
+    return set_unique(col_ndx, ndx, value);
+}
+
+inline void Table::set_int(size_t col_ndx, size_t ndx, int_fast64_t value, bool is_default)
+{
+    return set(col_ndx, ndx, value, is_default);
+}
+
+inline Timestamp Table::get_timestamp(size_t col_ndx, size_t ndx) const noexcept
+{
+    return get<Timestamp>(col_ndx, ndx);
+}
+
+inline void Table::set_timestamp(size_t col_ndx, size_t ndx, Timestamp value, bool is_default)
+{
+    return set(col_ndx, ndx, value, is_default);
+}
+
+inline bool Table::get_bool(size_t col_ndx, size_t ndx) const noexcept
+{
+    if (is_nullable(col_ndx))
+        return get<util::Optional<bool>>(col_ndx, ndx).value_or(false);
+    else
+        return get<bool>(col_ndx, ndx);
+}
+
+inline void Table::set_bool(size_t col_ndx, size_t ndx, bool value, bool is_default)
+{
+    return set(col_ndx, ndx, value, is_default);
+}
+
+inline OldDateTime Table::get_olddatetime(size_t col_ndx, size_t ndx) const noexcept
+{
+    return get<OldDateTime>(col_ndx, ndx);
+}
+
+inline void Table::set_olddatetime(size_t col_ndx, size_t ndx, OldDateTime value, bool is_default)
+{
+    return set(col_ndx, ndx, value, is_default);
+}
+
+inline float Table::get_float(size_t col_ndx, size_t ndx) const noexcept
+{
+    float f = get<float>(col_ndx, ndx);
+    return null::is_null_float(f) ? 0.0f : f;
+}
+
+inline void Table::set_float(size_t col_ndx, size_t ndx, float value, bool is_default)
+{
+    return set(col_ndx, ndx, value, is_default);
+}
+
+inline double Table::get_double(size_t col_ndx, size_t ndx) const noexcept
+{
+    double d = get<double>(col_ndx, ndx);
+    return null::is_null_float(d) ? 0.0 : d;
+}
+
+inline void Table::set_double(size_t col_ndx, size_t ndx, double value, bool is_default)
+{
+    return set(col_ndx, ndx, value, is_default);
+}
+
+inline StringData Table::get_string(size_t col_ndx, size_t ndx) const noexcept
+{
+    return get<StringData>(col_ndx, ndx);
+}
+
+inline void Table::set_string(size_t col_ndx, size_t ndx, StringData value, bool is_default)
+{
+    return set(col_ndx, ndx, value, is_default);
+}
+
+inline size_t Table::set_string_unique(size_t col_ndx, size_t ndx, StringData value)
+{
+    return set_unique(col_ndx, ndx, value);
+}
+
+inline BinaryData Table::get_binary(size_t col_ndx, size_t ndx) const noexcept
+{
+    return get<BinaryData>(col_ndx, ndx);
+}
+
+inline BinaryIterator Table::get_binary_iterator(size_t col_ndx, size_t ndx) const noexcept
+{
+    return get<BinaryIterator>(col_ndx, ndx);
+}
+
+inline void Table::set_binary(size_t col_ndx, size_t ndx, BinaryData value, bool is_default)
+{
+    set(col_ndx, ndx, value, is_default);
+}
+
+inline Mixed Table::get_mixed(size_t col_ndx, size_t ndx) const noexcept
+{
+    return get<Mixed>(col_ndx, ndx);
+}
+
+inline void Table::set_mixed(size_t col_ndx, size_t ndx, Mixed value, bool is_default)
+{
+    set(col_ndx, ndx, value, is_default);
+}
+
+inline void Table::set_null(size_t col_ndx, size_t ndx, bool is_default)
+{
+    set(col_ndx, ndx, null(), is_default);
+}
+
+inline void Table::set_null_unique(size_t col_ndx, size_t ndx)
+{
+    set_unique(col_ndx, ndx, null());
+}
+
+
+// This class groups together information about the target of a link column
+// This is not a valid link if the target table == nullptr
+struct LinkTargetInfo {
+    LinkTargetInfo(Table* target = nullptr, size_t backlink_ndx = realm::npos)
+        : m_target_table(target)
+        , m_backlink_col_ndx(backlink_ndx)
+    {
+    }
+    bool is_valid() const
+    {
+        return (m_target_table != nullptr);
+    }
+    Table* m_target_table;
+    size_t m_backlink_col_ndx; // a value of npos indicates the backlink should be appended
+};
+
+// The purpose of this class is to give internal access to some, but
+// not all of the non-public parts of the Table class.
+class _impl::TableFriend {
+public:
+    typedef Table::UnbindGuard UnbindGuard;
+
+    static ref_type create_empty_table(Allocator& alloc)
+    {
+        return Table::create_empty_table(alloc); // Throws
+    }
+
+    static ref_type clone(const Table& table, Allocator& alloc)
+    {
+        return table.clone(alloc); // Throws
+    }
+
+    static ref_type clone_columns(const Table& table, Allocator& alloc)
+    {
+        return table.clone_columns(alloc); // Throws
+    }
+
+    static Table* create_accessor(Allocator& alloc, ref_type top_ref, Table::Parent* parent, size_t ndx_in_parent)
+    {
+        std::unique_ptr<Table> table(new Table(Table::ref_count_tag(), alloc)); // Throws
+        table->init(top_ref, parent, ndx_in_parent);                            // Throws
+        return table.release();
+    }
+
+    static Table* create_accessor(Spec* shared_spec, Table::Parent* parent_column, size_t parent_row_ndx)
+    {
+        Allocator& alloc = shared_spec->get_alloc();
+        std::unique_ptr<Table> table(new Table(Table::ref_count_tag(), alloc)); // Throws
+        table->init(shared_spec, parent_column, parent_row_ndx);                // Throws
+        return table.release();
+    }
+
+    // Intended to be used only by Group::create_table_accessor()
+    static Table* create_incomplete_accessor(Allocator& alloc, ref_type top_ref, Table::Parent* parent,
+                                             size_t ndx_in_parent)
+    {
+        std::unique_ptr<Table> table(new Table(Table::ref_count_tag(), alloc)); // Throws
+        bool skip_create_column_accessors = true;
+        table->init(top_ref, parent, ndx_in_parent, skip_create_column_accessors); // Throws
+        return table.release();
+    }
+
+    // Intended to be used only by Group::create_table_accessor()
+    static void complete_accessor(Table& table)
+    {
+        table.refresh_column_accessors(); // Throws
+    }
+
+    static void set_top_parent(Table& table, ArrayParent* parent, size_t ndx_in_parent) noexcept
+    {
+        table.m_top.set_parent(parent, ndx_in_parent);
+    }
+
+    static void update_from_parent(Table& table, size_t old_baseline) noexcept
+    {
+        table.update_from_parent(old_baseline);
+    }
+
+    static void detach(Table& table) noexcept
+    {
+        table.detach();
+    }
+
+    static void discard_row_accessors(Table& table) noexcept
+    {
+        table.discard_row_accessors();
+    }
+
+    static void discard_child_accessors(Table& table) noexcept
+    {
+        table.discard_child_accessors();
+    }
+
+    static void discard_subtable_accessor(Table& table, size_t col_ndx, size_t row_ndx) noexcept
+    {
+        table.discard_subtable_accessor(col_ndx, row_ndx);
+    }
+
+    static void bind_ptr(Table& table) noexcept
+    {
+        table.bind_ptr();
+    }
+
+    static void unbind_ptr(Table& table) noexcept
+    {
+        table.unbind_ptr();
+    }
+
+    static bool compare_rows(const Table& a, const Table& b)
+    {
+        return a.compare_rows(b); // Throws
+    }
+
+    static size_t get_size_from_ref(ref_type ref, Allocator& alloc) noexcept
+    {
+        return Table::get_size_from_ref(ref, alloc);
+    }
+
+    static size_t get_size_from_ref(ref_type spec_ref, ref_type columns_ref, Allocator& alloc) noexcept
+    {
+        return Table::get_size_from_ref(spec_ref, columns_ref, alloc);
+    }
+
+    static Spec& get_spec(Table& table) noexcept
+    {
+        return *table.m_spec;
+    }
+
+    static const Spec& get_spec(const Table& table) noexcept
+    {
+        return *table.m_spec;
+    }
+
+    static ColumnBase& get_column(const Table& table, size_t col_ndx)
+    {
+        return *table.m_cols[col_ndx];
+    }
+
+    static void do_remove(Table& table, size_t row_ndx)
+    {
+        bool broken_reciprocal_backlinks = false;
+        table.do_remove(row_ndx, broken_reciprocal_backlinks); // Throws
+    }
+
+    static void do_move_last_over(Table& table, size_t row_ndx)
+    {
+        bool broken_reciprocal_backlinks = false;
+        table.do_move_last_over(row_ndx, broken_reciprocal_backlinks); // Throws
+    }
+
+    static void do_swap_rows(Table& table, size_t row_ndx_1, size_t row_ndx_2)
+    {
+        table.do_swap_rows(row_ndx_1, row_ndx_2); // Throws
+    }
+
+    static void do_move_row(Table& table, size_t from_ndx, size_t to_ndx)
+    {
+        table.do_move_row(from_ndx, to_ndx); // Throws
+    }
+
+    static void do_merge_rows(Table& table, size_t row_ndx, size_t new_row_ndx)
+    {
+        table.do_merge_rows(row_ndx, new_row_ndx); // Throws
+    }
+
+    static void do_clear(Table& table)
+    {
+        bool broken_reciprocal_backlinks = false;
+        table.do_clear(broken_reciprocal_backlinks); // Throws
+    }
+
+    static void do_set_link(Table& table, size_t col_ndx, size_t row_ndx, size_t target_row_ndx)
+    {
+        table.do_set_link(col_ndx, row_ndx, target_row_ndx); // Throws
+    }
+
+    static size_t get_backlink_count(const Table& table, size_t row_ndx, bool only_strong_links) noexcept
+    {
+        return table.get_backlink_count(row_ndx, only_strong_links);
+    }
+
+    static void cascade_break_backlinks_to(Table& table, size_t row_ndx, CascadeState& state)
+    {
+        table.cascade_break_backlinks_to(row_ndx, state); // Throws
+    }
+
+    static void remove_backlink_broken_rows(Table& table, const CascadeState& rows)
+    {
+        table.remove_backlink_broken_rows(rows); // Throws
+    }
+
+    static size_t* record_subtable_path(const Table& table, size_t* begin, size_t* end) noexcept
+    {
+        return table.record_subtable_path(begin, end);
+    }
+
+    static void insert_column(Descriptor& desc, size_t column_ndx, DataType type, StringData name,
+                              LinkTargetInfo& link, bool nullable = false)
+    {
+        Table::do_insert_column(desc, column_ndx, type, name, link, nullable); // Throws
+    }
+
+    static void insert_column_unless_exists(Descriptor& desc, size_t column_ndx, DataType type, StringData name,
+                                            LinkTargetInfo link, bool nullable = false, bool* was_inserted = nullptr)
+    {
+        Table::do_insert_column_unless_exists(desc, column_ndx, type, name, link, nullable, was_inserted); // Throws
+    }
+
+    static void erase_column(Descriptor& desc, size_t column_ndx)
+    {
+        Table::do_erase_column(desc, column_ndx); // Throws
+    }
+
+    static void rename_column(Descriptor& desc, size_t column_ndx, StringData name)
+    {
+        Table::do_rename_column(desc, column_ndx, name); // Throws
+    }
+
+    static void add_search_index(Descriptor& desc, size_t column_ndx)
+    {
+        Table::do_add_search_index(desc, column_ndx); // Throws
+    }
+
+    static void remove_search_index(Descriptor& desc, size_t column_ndx)
+    {
+        Table::do_remove_search_index(desc, column_ndx); // Throws
+    }
+
+    static void set_link_type(Table& table, size_t column_ndx, LinkType link_type)
+    {
+        table.do_set_link_type(column_ndx, link_type); // Throws
+    }
+
+    static void erase_row(Table& table, size_t row_ndx, bool is_move_last_over)
+    {
+        table.erase_row(row_ndx, is_move_last_over); // Throws
+    }
+
+    static void batch_erase_rows(Table& table, const IntegerColumn& row_indexes, bool is_move_last_over)
+    {
+        table.batch_erase_rows(row_indexes, is_move_last_over); // Throws
+    }
+
+    static TableRef get_subtable_accessor(Table& table, size_t col_ndx, size_t row_ndx) noexcept
+    {
+        return table.get_subtable_accessor(col_ndx, row_ndx);
+    }
+
+    static const Table* get_link_target_table_accessor(const Table& table, size_t col_ndx) noexcept
+    {
+        return const_cast<Table&>(table).get_link_target_table_accessor(col_ndx);
+    }
+
+    static Table* get_link_target_table_accessor(Table& table, size_t col_ndx) noexcept
+    {
+        return table.get_link_target_table_accessor(col_ndx);
+    }
+
+    static void adj_acc_insert_rows(Table& table, size_t row_ndx, size_t num_rows) noexcept
+    {
+        table.adj_acc_insert_rows(row_ndx, num_rows);
+    }
+
+    static void adj_acc_erase_row(Table& table, size_t row_ndx) noexcept
+    {
+        table.adj_acc_erase_row(row_ndx);
+    }
+
+    static void adj_acc_swap_rows(Table& table, size_t row_ndx_1, size_t row_ndx_2) noexcept
+    {
+        table.adj_acc_swap_rows(row_ndx_1, row_ndx_2);
+    }
+
+    static void adj_acc_move_row(Table& table, size_t from_ndx, size_t to_ndx) noexcept
+    {
+        table.adj_acc_move_row(from_ndx, to_ndx);
+    }
+
+    static void adj_acc_merge_rows(Table& table, size_t row_ndx_1, size_t row_ndx_2) noexcept
+    {
+        table.adj_acc_merge_rows(row_ndx_1, row_ndx_2);
+    }
+
+    static void adj_acc_move_over(Table& table, size_t from_row_ndx, size_t to_row_ndx) noexcept
+    {
+        table.adj_acc_move_over(from_row_ndx, to_row_ndx);
+    }
+
+    static void adj_acc_clear_root_table(Table& table) noexcept
+    {
+        table.adj_acc_clear_root_table();
+    }
+
+    static void adj_acc_clear_nonroot_table(Table& table) noexcept
+    {
+        table.adj_acc_clear_nonroot_table();
+    }
+
+    static void adj_insert_column(Table& table, size_t col_ndx)
+    {
+        table.adj_insert_column(col_ndx); // Throws
+    }
+
+    static void adj_add_column(Table& table)
+    {
+        size_t num_cols = table.m_cols.size();
+        table.adj_insert_column(num_cols); // Throws
+    }
+
+    static void adj_erase_column(Table& table, size_t col_ndx) noexcept
+    {
+        table.adj_erase_column(col_ndx);
+    }
+
+    static bool is_marked(const Table& table) noexcept
+    {
+        return table.is_marked();
+    }
+
+    static void mark(Table& table) noexcept
+    {
+        table.mark();
+    }
+
+    static void unmark(Table& table) noexcept
+    {
+        table.unmark();
+    }
+
+    static void recursive_mark(Table& table) noexcept
+    {
+        table.recursive_mark();
+    }
+
+    static void mark_link_target_tables(Table& table, size_t col_ndx_begin) noexcept
+    {
+        table.mark_link_target_tables(col_ndx_begin);
+    }
+
+    static void mark_opposite_link_tables(Table& table) noexcept
+    {
+        table.mark_opposite_link_tables();
+    }
+
+    static DescriptorRef get_root_table_desc_accessor(Table& root_table) noexcept
+    {
+        return root_table.m_descriptor.lock();
+    }
+
+    typedef Table::AccessorUpdater AccessorUpdater;
+    static void update_accessors(Table& table, const size_t* col_path_begin, const size_t* col_path_end,
+                                 AccessorUpdater& updater)
+    {
+        table.update_accessors(col_path_begin, col_path_end, updater); // Throws
+    }
+
+    static void refresh_accessor_tree(Table& table)
+    {
+        table.refresh_accessor_tree(); // Throws
+    }
+
+    static void refresh_spec_accessor(Table& table)
+    {
+        table.refresh_spec_accessor(); // Throws
+    }
+
+    static void set_ndx_in_parent(Table& table, size_t ndx_in_parent) noexcept
+    {
+        table.set_ndx_in_parent(ndx_in_parent);
+    }
+
+    static bool is_link_type(ColumnType type) noexcept
+    {
+        return Table::is_link_type(type);
+    }
+
+    static void bump_version(Table& table, bool bump_global = true) noexcept
+    {
+        table.bump_version(bump_global);
+    }
+
+    static bool is_cross_table_link_target(const Table& table)
+    {
+        return table.is_cross_table_link_target();
+    }
+
+    static Group* get_parent_group(const Table& table) noexcept
+    {
+        return table.get_parent_group();
+    }
+
+    static Replication* get_repl(Table& table) noexcept
+    {
+        return table.get_repl();
+    }
+
+    static void register_view(Table& table, const TableViewBase* view)
+    {
+        table.register_view(view); // Throws
+    }
+
+    static void unregister_view(Table& table, const TableViewBase* view) noexcept
+    {
+        table.unregister_view(view);
+    }
+};
+
+
+} // namespace realm
+
+#endif // REALM_TABLE_HPP