1 /*************************************************************************
 
   3  * Copyright 2016 Realm Inc.
 
   5  * Licensed under the Apache License, Version 2.0 (the "License");
 
   6  * you may not use this file except in compliance with the License.
 
   7  * You may obtain a copy of the License at
 
   9  * http://www.apache.org/licenses/LICENSE-2.0
 
  11  * Unless required by applicable law or agreed to in writing, software
 
  12  * distributed under the License is distributed on an "AS IS" BASIS,
 
  13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  14  * See the License for the specific language governing permissions and
 
  15  * limitations under the License.
 
  17  **************************************************************************/
 
  19 #ifndef REALM_SPEC_HPP
 
  20 #define REALM_SPEC_HPP
 
  22 #include <realm/util/features.h>
 
  23 #include <realm/array.hpp>
 
  24 #include <realm/array_string.hpp>
 
  25 #include <realm/array_integer.hpp>
 
  26 #include <realm/data_type.hpp>
 
  27 #include <realm/column_type.hpp>
 
  37     Allocator& get_alloc() const noexcept;
 
  39     bool has_strong_link_columns() noexcept;
 
  41     void insert_column(size_t column_ndx, ColumnType type, StringData name, ColumnAttr attr = col_attr_None);
 
  42     void rename_column(size_t column_ndx, StringData new_name);
 
  44     /// Erase the column at the specified index, and move columns at
 
  45     /// succeeding indexes to the next lower index.
 
  47     /// This function is guaranteed to *never* throw if the spec is
 
  48     /// used in a non-transactional context, or if the spec has
 
  49     /// already been successfully modified within the current write
 
  51     void erase_column(size_t column_ndx);
 
  54     // If a new Spec is constructed from the returned subspec
 
  55     // reference, it is the responsibility of the application that the
 
  56     // parent Spec object (this) is kept alive for at least as long as
 
  57     // the new Spec object.
 
  58     Spec* get_subtable_spec(size_t column_ndx) noexcept;
 
  62     size_t get_column_count() const noexcept;
 
  63     size_t get_public_column_count() const noexcept;
 
  64     DataType get_public_column_type(size_t column_ndx) const noexcept;
 
  65     ColumnType get_column_type(size_t column_ndx) const noexcept;
 
  66     StringData get_column_name(size_t column_ndx) const noexcept;
 
  68     /// Returns size_t(-1) if the specified column is not found.
 
  69     size_t get_column_index(StringData name) const noexcept;
 
  72     ColumnAttr get_column_attr(size_t column_ndx) const noexcept;
 
  74     size_t get_subspec_ndx(size_t column_ndx) const noexcept;
 
  75     ref_type get_subspec_ref(size_t subspec_ndx) const noexcept;
 
  76     Spec* get_subspec_by_ndx(size_t subspec_ndx) noexcept;
 
  77     const Spec* get_subspec_by_ndx(size_t subspec_ndx) const noexcept;
 
  79     // Auto Enumerated string columns
 
  80     void upgrade_string_to_enum(size_t column_ndx, ref_type keys_ref, ArrayParent*& keys_parent, size_t& keys_ndx);
 
  81     size_t get_enumkeys_ndx(size_t column_ndx) const noexcept;
 
  82     ref_type get_enumkeys_ref(size_t column_ndx, ArrayParent** keys_parent = nullptr,
 
  83                               size_t* keys_ndx = nullptr) noexcept;
 
  86     size_t get_opposite_link_table_ndx(size_t column_ndx) const noexcept;
 
  87     void set_opposite_link_table_ndx(size_t column_ndx, size_t table_ndx);
 
  90     bool has_backlinks() const noexcept;
 
  91     size_t first_backlink_column_index() const noexcept;
 
  92     size_t backlink_column_count() const noexcept;
 
  93     void set_backlink_origin_column(size_t backlink_col_ndx, size_t origin_col_ndx);
 
  94     size_t get_origin_column_ndx(size_t backlink_col_ndx) const noexcept;
 
  95     size_t find_backlink_column(size_t origin_table_ndx, size_t origin_col_ndx) const noexcept;
 
  97     /// Get position in `Table::m_columns` of the specified column. It may be
 
  98     /// different from the specified logical column index due to the presence of
 
  99     /// search indexes, since their top refs are stored in Table::m_columns as
 
 101     size_t get_column_ndx_in_parent(size_t column_ndx) const;
 
 104     /// Compare two table specs for equality.
 
 105     bool operator==(const Spec&) const noexcept;
 
 106     bool operator!=(const Spec&) const noexcept;
 
 109     void detach() noexcept;
 
 110     void destroy() noexcept;
 
 112     size_t get_ndx_in_parent() const noexcept;
 
 113     void set_ndx_in_parent(size_t) noexcept;
 
 117     void to_dot(std::ostream&, StringData title = StringData()) const;
 
 121     // Underlying array structure.
 
 123     // `m_subspecs` contains one entry for each subtable column, one entry for
 
 124     // each link or link list columns, two entries for each backlink column, and
 
 125     // zero entries for all other column types. For subtable columns the entry
 
 126     // is a ref pointing to the subtable spec, for link and link list columns it
 
 127     // is the group-level table index of the target table, and for backlink
 
 128     // columns the first entry is the group-level table index of the origin
 
 129     // table, and the second entry is the index of the origin column in the
 
 132     ArrayInteger m_types; // 1st slot in m_top
 
 133     ArrayString m_names;  // 2nd slot in m_top
 
 134     ArrayInteger m_attr;  // 3rd slot in m_top
 
 135     Array m_subspecs;     // 4th slot in m_top (optional)
 
 136     Array m_enumkeys;     // 5th slot in m_top (optional)
 
 138         SubspecPtr(bool is_spec_ptr = false)
 
 139             : m_is_spec_ptr(is_spec_ptr)
 
 142         std::unique_ptr<Spec> m_spec;
 
 145     using SubspecPtrs = std::vector<SubspecPtr>;
 
 146     SubspecPtrs m_subspec_ptrs;
 
 147     bool m_has_strong_link_columns;
 
 149     Spec(Allocator&) noexcept; // Unattached
 
 151     bool init(ref_type) noexcept;
 
 152     void init(MemRef) noexcept;
 
 153     void update_has_strong_link_columns() noexcept;
 
 154     void reset_subspec_ptrs();
 
 155     void adj_subspec_ptrs();
 
 157     // Returns true in case the ref has changed.
 
 158     bool init_from_parent() noexcept;
 
 160     ref_type get_ref() const noexcept;
 
 162     /// Called in the context of Group::commit() to ensure that
 
 163     /// attached table accessors stay valid across a commit. Please
 
 164     /// note that this works only for non-transactional commits. Table
 
 165     /// accessors obtained during a transaction are always detached
 
 166     /// when the transaction ends.
 
 167     bool update_from_parent(size_t old_baseline) noexcept;
 
 169     void set_parent(ArrayParent*, size_t ndx_in_parent) noexcept;
 
 171     void set_column_type(size_t column_ndx, ColumnType type);
 
 172     void set_column_attr(size_t column_ndx, ColumnAttr attr);
 
 174     /// Construct an empty spec and return just the reference to the
 
 175     /// underlying memory.
 
 176     static MemRef create_empty_spec(Allocator&);
 
 179         size_t m_column_ref_ndx = 0; ///< Index within Table::m_columns
 
 180         bool m_has_search_index = false;
 
 183     ColumnInfo get_column_info(size_t column_ndx) const noexcept;
 
 185     size_t get_subspec_ndx_after(size_t column_ndx, size_t skip_column_ndx) const noexcept;
 
 186     size_t get_subspec_entries_for_col_type(ColumnType type) const noexcept;
 
 187     bool has_subspec() const noexcept;
 
 189     // Returns false if the spec has no columns, otherwise it returns
 
 190     // true and sets `type` to the type of the first column.
 
 191     static bool get_first_column_type_from_ref(ref_type, Allocator&, ColumnType& type) noexcept;
 
 193     friend class Replication;
 
 200 inline Allocator& Spec::get_alloc() const noexcept
 
 202     return m_top.get_alloc();
 
 205 inline bool Spec::has_strong_link_columns() noexcept
 
 207     return m_has_strong_link_columns;
 
 210 inline ref_type Spec::get_subspec_ref(size_t subspec_ndx) const noexcept
 
 212     REALM_ASSERT(subspec_ndx < m_subspecs.size());
 
 214     // Note that this addresses subspecs directly, indexing
 
 215     // by number of sub-table columns
 
 216     return m_subspecs.get_as_ref(subspec_ndx);
 
 219 // Uninitialized Spec (call init() to init)
 
 220 inline Spec::Spec(Allocator& alloc) noexcept
 
 230 inline Spec* Spec::get_subtable_spec(size_t column_ndx) noexcept
 
 232     REALM_ASSERT(column_ndx < get_column_count());
 
 233     REALM_ASSERT(get_column_type(column_ndx) == col_type_Table);
 
 234     size_t subspec_ndx = get_subspec_ndx(column_ndx);
 
 235     return get_subspec_by_ndx(subspec_ndx);
 
 238 inline const Spec* Spec::get_subspec_by_ndx(size_t subspec_ndx) const noexcept
 
 240     return const_cast<Spec*>(this)->get_subspec_by_ndx(subspec_ndx);
 
 243 inline bool Spec::init_from_parent() noexcept
 
 245     ref_type ref = m_top.get_ref_from_parent();
 
 249 inline void Spec::destroy() noexcept
 
 251     m_top.destroy_deep();
 
 254 inline size_t Spec::get_ndx_in_parent() const noexcept
 
 256     return m_top.get_ndx_in_parent();
 
 259 inline void Spec::set_ndx_in_parent(size_t ndx) noexcept
 
 261     m_top.set_ndx_in_parent(ndx);
 
 264 inline ref_type Spec::get_ref() const noexcept
 
 266     return m_top.get_ref();
 
 269 inline void Spec::set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept
 
 271     m_top.set_parent(parent, ndx_in_parent);
 
 274 inline void Spec::rename_column(size_t column_ndx, StringData new_name)
 
 276     REALM_ASSERT(column_ndx < m_types.size());
 
 277     m_names.set(column_ndx, new_name);
 
 280 inline size_t Spec::get_column_count() const noexcept
 
 282     // This is the total count of columns, including backlinks (not public)
 
 283     return m_types.size();
 
 286 inline size_t Spec::get_public_column_count() const noexcept
 
 288     // Backlinks are the last columns, and do not have names, so getting
 
 289     // the number of names gives us the count of user facing columns
 
 290     return m_names.size();
 
 293 inline ColumnType Spec::get_column_type(size_t ndx) const noexcept
 
 295     REALM_ASSERT(ndx < get_column_count());
 
 296     return ColumnType(m_types.get(ndx));
 
 299 inline void Spec::set_column_type(size_t column_ndx, ColumnType type)
 
 301     REALM_ASSERT(column_ndx < get_column_count());
 
 303     // At this point we only support upgrading to string enum
 
 304     REALM_ASSERT(ColumnType(m_types.get(column_ndx)) == col_type_String);
 
 305     REALM_ASSERT(type == col_type_StringEnum);
 
 307     m_types.set(column_ndx, type); // Throws
 
 309     update_has_strong_link_columns();
 
 312 inline ColumnAttr Spec::get_column_attr(size_t ndx) const noexcept
 
 314     REALM_ASSERT(ndx < get_column_count());
 
 315     return ColumnAttr(m_attr.get(ndx));
 
 318 inline void Spec::set_column_attr(size_t column_ndx, ColumnAttr attr)
 
 320     REALM_ASSERT(column_ndx < get_column_count());
 
 322     // At this point we only allow one attr at a time
 
 323     // so setting it will overwrite existing. In the future
 
 324     // we will allow combinations.
 
 325     m_attr.set(column_ndx, attr);
 
 327     update_has_strong_link_columns();
 
 330 inline StringData Spec::get_column_name(size_t ndx) const noexcept
 
 332     REALM_ASSERT(ndx < get_column_count());
 
 333     return m_names.get(ndx);
 
 336 inline size_t Spec::get_column_index(StringData name) const noexcept
 
 338     return m_names.find_first(name);
 
 341 inline bool Spec::get_first_column_type_from_ref(ref_type top_ref, Allocator& alloc, ColumnType& type) noexcept
 
 343     const char* top_header = alloc.translate(top_ref);
 
 344     ref_type types_ref = to_ref(Array::get(top_header, 0));
 
 345     const char* types_header = alloc.translate(types_ref);
 
 346     if (Array::get_size_from_header(types_header) == 0)
 
 348     type = ColumnType(Array::get(types_header, 0));
 
 352 inline bool Spec::has_backlinks() const noexcept
 
 354     // backlinks are always last and do not have names.
 
 355     return m_names.size() < m_types.size();
 
 357     // Fixme: It's bad design that backlinks are stored and recognized like this. Backlink columns
 
 358     // should be a column type like any other, and we should find another way to hide them away from
 
 362 inline size_t Spec::first_backlink_column_index() const noexcept
 
 364     return m_names.size();
 
 367 inline size_t Spec::backlink_column_count() const noexcept
 
 369     return m_types.size() - m_names.size();
 
 372 // Spec will have a subspec when it contains a column which is one of:
 
 373 // link, linklist, backlink, or subtable. It is possible for m_top.size()
 
 374 // to contain an entry for m_subspecs (at index 3) but this reference
 
 375 // may be empty if the spec contains enumkeys (at index 4) but no subspec types.
 
 376 inline bool Spec::has_subspec() const noexcept
 
 378     return (m_top.size() >= 4) && (m_top.get_as_ref(3) != 0);
 
 381 inline bool Spec::operator!=(const Spec& s) const noexcept
 
 383     return !(*this == s);
 
 388 #endif // REALM_SPEC_HPP