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