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_COLUMN_TABLE_HPP
20 #define REALM_COLUMN_TABLE_HPP
25 #include <realm/util/features.h>
27 #include <realm/column.hpp>
28 #include <realm/table.hpp>
33 /// Base class for any type of column that can contain subtables.
34 // FIXME: Don't derive from IntegerColumn, but define a BpTree<ref_type> specialization.
35 class SubtableColumnBase : public IntegerColumn, public Table::Parent {
37 void discard_child_accessors() noexcept;
39 ~SubtableColumnBase() noexcept override;
41 static ref_type create(Allocator&, size_t size = 0);
43 TableRef get_subtable_accessor(size_t) const noexcept override;
45 void insert_rows(size_t, size_t, size_t, bool) override;
46 void erase_rows(size_t, size_t, size_t, bool) override;
47 void move_last_row_over(size_t, size_t, bool) override;
48 void clear(size_t, bool) override;
49 void swap_rows(size_t, size_t) override;
50 void discard_subtable_accessor(size_t) noexcept override;
51 void update_from_parent(size_t) noexcept override;
52 void adj_acc_insert_rows(size_t, size_t) noexcept override;
53 void adj_acc_erase_row(size_t) noexcept override;
54 void adj_acc_move_over(size_t, size_t) noexcept override;
55 void adj_acc_clear_root_table() noexcept override;
56 void adj_acc_swap_rows(size_t, size_t) noexcept override;
57 void adj_acc_move_row(size_t, size_t) noexcept override;
58 void mark(int) noexcept override;
59 bool supports_search_index() const noexcept override
63 StringIndex* create_search_index() override
67 bool is_null(size_t ndx) const noexcept override
69 return get_as_ref(ndx) == 0;
72 void verify() const override;
73 void verify(const Table&, size_t) const override;
76 /// A pointer to the table that this column is part of. For a free-standing
77 /// column, this pointer is null.
81 bool empty() const noexcept
83 return m_entries.empty();
85 Table* find(size_t subtable_ndx) const noexcept;
86 void add(size_t subtable_ndx, Table*);
87 // Returns true if, and only if at least one entry was detached and
88 // removed from the map.
89 bool detach_and_remove_all() noexcept;
90 // Returns true if, and only if the entry was found and removed, and it
91 // was the last entry in the map.
92 bool detach_and_remove(size_t subtable_ndx) noexcept;
93 // Returns true if, and only if the entry was found and removed, and it
94 // was the last entry in the map.
95 bool remove(Table*) noexcept;
96 void update_from_parent(size_t old_baseline) const noexcept;
97 template <bool fix_ndx_in_parent>
98 void adj_insert_rows(size_t row_ndx, size_t num_rows_inserted) noexcept;
99 // Returns true if, and only if an entry was found and removed, and it
100 // was the last entry in the map.
101 template <bool fix_ndx_in_parent>
102 bool adj_erase_rows(size_t row_ndx, size_t num_rows_erased) noexcept;
103 // Returns true if, and only if an entry was found and removed, and it
104 // was the last entry in the map.
105 template <bool fix_ndx_in_parent>
106 bool adj_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept;
107 template <bool fix_ndx_in_parent>
108 void adj_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept;
109 template <bool fix_ndx_in_parent>
110 void adj_move_row(size_t from_ndx, size_t to_ndx) noexcept;
111 void adj_set_null(size_t row_ndx) noexcept;
113 void update_accessors(const size_t* col_path_begin, const size_t* col_path_end,
114 _impl::TableFriend::AccessorUpdater&);
115 void recursive_mark() noexcept;
116 void refresh_accessor_tree();
117 void verify(const SubtableColumn& parent);
120 struct SubtableEntry {
121 size_t m_subtable_ndx;
124 typedef std::vector<SubtableEntry> entries;
128 /// Contains all existing accessors that are attached to a subtable in this
129 /// column. It can map a row index into a pointer to the corresponding
130 /// accessor when it exists.
132 /// There is an invariant in force: Either `m_table` is null, or there is an
133 /// additional referece count on `*m_table` when, and only when the map is
135 mutable SubtableMap m_subtable_map;
136 mutable std::recursive_mutex m_subtable_map_lock;
138 SubtableColumnBase(Allocator&, ref_type, Table*, size_t column_ndx);
140 /// Get a TableRef to the accessor of the specified subtable. The
141 /// accessor will be created if it does not already exist.
143 /// NOTE: This method must be used only for subtables with
144 /// independent specs, i.e. for elements of a MixedColumn.
145 TableRef get_subtable_tableref(size_t subtable_ndx);
147 // Overriding method in ArrayParent
148 void update_child_ref(size_t, ref_type) override;
150 // Overriding method in ArrayParent
151 ref_type get_child_ref(size_t) const noexcept override;
153 // Overriding method in Table::Parent
154 Table* get_parent_table(size_t*) noexcept override;
156 // Overriding method in Table::Parent
157 void child_accessor_destroyed(Table*) noexcept override;
159 // Overriding method in Table::Parent
160 std::recursive_mutex* get_accessor_management_lock() noexcept override
161 { return &m_subtable_map_lock; }
163 /// Assumes that the two tables have the same spec.
164 static bool compare_subtable_rows(const Table&, const Table&);
166 /// Construct a copy of the columns array of the specified table
167 /// and return just the ref to that array.
169 /// In the clone, no string column will be of the enumeration
171 ref_type clone_table_columns(const Table*);
173 size_t* record_subtable_path(size_t* begin, size_t* end) noexcept override;
175 void update_table_accessors(const size_t* col_path_begin, const size_t* col_path_end,
176 _impl::TableFriend::AccessorUpdater&);
178 /// \param row_ndx Must be `realm::npos` if appending.
179 /// \param value The value to place in any newly created rows.
180 /// \param num_rows The number of rows to insert.
181 void do_insert(size_t row_ndx, int_fast64_t value, size_t num_rows);
183 std::pair<ref_type, size_t> get_to_dot_parent(size_t ndx_in_parent) const override;
189 class SubtableColumn : public SubtableColumnBase {
191 using value_type = ConstTableRef;
192 /// Create a subtable column accessor and attach it to a
193 /// preexisting underlying structure of arrays.
195 /// \param alloc The allocator to provide new memory.
197 /// \param ref The memory reference of the underlying subtable that
198 /// we are creating an accessor for.
200 /// \param table If this column is used as part of a table you must
201 /// pass a pointer to that table. Otherwise you must pass null.
203 /// \param column_ndx If this column is used as part of a table
204 /// you must pass the logical index of the column within that
205 /// table. Otherwise you should pass zero.
206 SubtableColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx);
208 ~SubtableColumn() noexcept override
212 // Overriding method in Table::Parent
213 Spec* get_subtable_spec() noexcept override;
215 size_t get_subtable_size(size_t ndx) const noexcept;
217 /// Get a TableRef to the accessor of the specified subtable. The
218 /// accessor will be created if it does not already exist.
219 TableRef get_subtable_tableref(size_t subtable_ndx);
221 ConstTableRef get_subtable_tableref(size_t subtable_ndx) const;
223 /// This is to be used by the query system that does not need to
224 /// modify the subtable. Will return a ref object containing a
225 /// nullptr if there is no table object yet.
226 ConstTableRef get(size_t subtable_ndx) const
228 int64_t ref = IntegerColumn::get(subtable_ndx);
230 return get_subtable_tableref(subtable_ndx);
235 // When passing a table to add() or insert() it is assumed that
236 // the table spec is compatible with this column. The number of
237 // columns must be the same, and the corresponding columns must
238 // have the same data type (as returned by
239 // Table::get_column_type()).
241 void add(const Table* value = nullptr);
242 void insert(size_t ndx, const Table* value = nullptr);
243 void set(size_t ndx, const Table*);
244 void clear_table(size_t ndx);
245 void set_null(size_t ndx) override;
247 using SubtableColumnBase::insert;
249 void erase_rows(size_t, size_t, size_t, bool) override;
250 void move_last_row_over(size_t, size_t, bool) override;
252 /// Compare two subtable columns for equality.
253 bool compare_table(const SubtableColumn&) const;
255 void refresh_accessor_tree(size_t, const Spec&) override;
256 void refresh_subtable_map();
259 void verify(const Table&, size_t) const override;
260 void do_dump_node_structure(std::ostream&, int) const override;
261 void to_dot(std::ostream&, StringData title) const override;
265 mutable size_t m_subspec_ndx; // Unknown if equal to `npos`
267 size_t get_subspec_ndx() const noexcept;
269 void destroy_subtable(size_t ndx) noexcept;
271 void do_discard_child_accessors() noexcept override;
277 // Overriding virtual method of Column.
278 inline void SubtableColumnBase::insert_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool)
280 REALM_ASSERT_DEBUG(prior_num_rows == size());
281 REALM_ASSERT(row_ndx <= prior_num_rows);
283 size_t row_ndx_2 = (row_ndx == prior_num_rows ? realm::npos : row_ndx);
284 int_fast64_t value = 0;
285 do_insert(row_ndx_2, value, num_rows_to_insert); // Throws
288 // Overriding virtual method of Column.
289 inline void SubtableColumnBase::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows,
290 bool broken_reciprocal_backlinks)
292 IntegerColumn::erase_rows(row_ndx, num_rows_to_erase, prior_num_rows, broken_reciprocal_backlinks); // Throws
294 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
295 const bool fix_ndx_in_parent = true;
296 bool last_entry_removed = m_subtable_map.adj_erase_rows<fix_ndx_in_parent>(row_ndx, num_rows_to_erase);
297 typedef _impl::TableFriend tf;
298 if (last_entry_removed)
299 tf::unbind_ptr(*m_table);
302 // Overriding virtual method of Column.
303 inline void SubtableColumnBase::move_last_row_over(size_t row_ndx, size_t prior_num_rows,
304 bool broken_reciprocal_backlinks)
306 IntegerColumn::move_last_row_over(row_ndx, prior_num_rows, broken_reciprocal_backlinks); // Throws
308 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
309 const bool fix_ndx_in_parent = true;
310 size_t last_row_ndx = prior_num_rows - 1;
311 bool last_entry_removed = m_subtable_map.adj_move_over<fix_ndx_in_parent>(last_row_ndx, row_ndx);
312 typedef _impl::TableFriend tf;
313 if (last_entry_removed)
314 tf::unbind_ptr(*m_table);
317 inline void SubtableColumnBase::clear(size_t, bool)
319 discard_child_accessors();
320 clear_without_updating_index(); // Throws
321 // FIXME: This one is needed because
322 // IntegerColumn::clear_without_updating_index() forgets about the
323 // leaf type. A better solution should probably be sought after.
324 get_root_array()->set_type(Array::type_HasRefs);
327 inline void SubtableColumnBase::swap_rows(size_t row_ndx_1, size_t row_ndx_2)
329 IntegerColumn::swap_rows(row_ndx_1, row_ndx_2); // Throws
331 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
332 const bool fix_ndx_in_parent = true;
333 m_subtable_map.adj_swap_rows<fix_ndx_in_parent>(row_ndx_1, row_ndx_2);
336 inline void SubtableColumnBase::mark(int type) noexcept
338 if (type & mark_Recursive) {
339 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
340 m_subtable_map.recursive_mark();
344 inline void SubtableColumnBase::adj_acc_insert_rows(size_t row_ndx, size_t num_rows) noexcept
346 // This function must assume no more than minimal consistency of the
347 // accessor hierarchy. This means in particular that it cannot access the
348 // underlying node structure. See AccessorConsistencyLevels.
350 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
351 const bool fix_ndx_in_parent = false;
352 m_subtable_map.adj_insert_rows<fix_ndx_in_parent>(row_ndx, num_rows);
355 inline void SubtableColumnBase::adj_acc_erase_row(size_t row_ndx) noexcept
357 // This function must assume no more than minimal consistency of the
358 // accessor hierarchy. This means in particular that it cannot access the
359 // underlying node structure. See AccessorConsistencyLevels.
361 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
362 const bool fix_ndx_in_parent = false;
363 size_t num_rows_erased = 1;
364 bool last_entry_removed = m_subtable_map.adj_erase_rows<fix_ndx_in_parent>(row_ndx, num_rows_erased);
365 typedef _impl::TableFriend tf;
366 if (last_entry_removed)
367 tf::unbind_ptr(*m_table);
370 inline void SubtableColumnBase::adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept
372 // This function must assume no more than minimal consistency of the
373 // accessor hierarchy. This means in particular that it cannot access the
374 // underlying node structure. See AccessorConsistencyLevels.
376 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
377 const bool fix_ndx_in_parent = false;
378 bool last_entry_removed = m_subtable_map.adj_move_over<fix_ndx_in_parent>(from_row_ndx, to_row_ndx);
379 typedef _impl::TableFriend tf;
380 if (last_entry_removed)
381 tf::unbind_ptr(*m_table);
384 inline void SubtableColumnBase::adj_acc_clear_root_table() noexcept
386 // This function must assume no more than minimal consistency of the
387 // accessor hierarchy. This means in particular that it cannot access the
388 // underlying node structure. See AccessorConsistencyLevels.
390 IntegerColumn::adj_acc_clear_root_table();
391 discard_child_accessors();
394 inline void SubtableColumnBase::adj_acc_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept
396 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
397 const bool fix_ndx_in_parent = false;
398 m_subtable_map.adj_swap_rows<fix_ndx_in_parent>(row_ndx_1, row_ndx_2);
401 inline void SubtableColumnBase::adj_acc_move_row(size_t from_ndx, size_t to_ndx) noexcept
403 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
404 const bool fix_ndx_in_parent = false;
405 m_subtable_map.adj_move_row<fix_ndx_in_parent>(from_ndx, to_ndx);
408 inline TableRef SubtableColumnBase::get_subtable_accessor(size_t row_ndx) const noexcept
410 // This function must assume no more than minimal consistency of the
411 // accessor hierarchy. This means in particular that it cannot access the
412 // underlying node structure. See AccessorConsistencyLevels.
413 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
414 TableRef subtable(m_subtable_map.find(row_ndx));
418 inline void SubtableColumnBase::discard_subtable_accessor(size_t row_ndx) noexcept
420 // This function must assume no more than minimal consistency of the
421 // accessor hierarchy. This means in particular that it cannot access the
422 // underlying node structure. See AccessorConsistencyLevels.
424 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
425 bool last_entry_removed = m_subtable_map.detach_and_remove(row_ndx);
426 typedef _impl::TableFriend tf;
427 if (last_entry_removed)
428 tf::unbind_ptr(*m_table);
431 inline void SubtableColumnBase::SubtableMap::add(size_t subtable_ndx, Table* table)
434 e.m_subtable_ndx = subtable_ndx;
436 m_entries.push_back(e);
439 template <bool fix_ndx_in_parent>
440 void SubtableColumnBase::SubtableMap::adj_insert_rows(size_t row_ndx, size_t num_rows_inserted) noexcept
442 for (auto& entry : m_entries) {
443 if (entry.m_subtable_ndx >= row_ndx) {
444 entry.m_subtable_ndx += num_rows_inserted;
445 typedef _impl::TableFriend tf;
446 if (fix_ndx_in_parent)
447 tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx);
452 template <bool fix_ndx_in_parent>
453 bool SubtableColumnBase::SubtableMap::adj_erase_rows(size_t row_ndx, size_t num_rows_erased) noexcept
455 if (m_entries.empty())
457 typedef _impl::TableFriend tf;
458 auto end = m_entries.end();
459 auto i = m_entries.begin();
461 if (i->m_subtable_ndx >= row_ndx + num_rows_erased) {
462 i->m_subtable_ndx -= num_rows_erased;
463 if (fix_ndx_in_parent)
464 tf::set_ndx_in_parent(*(i->m_table), i->m_subtable_ndx);
466 else if (i->m_subtable_ndx >= row_ndx) {
467 // Must hold a counted reference while detaching
468 TableRef table(i->m_table);
476 m_entries.erase(end, m_entries.end());
477 return m_entries.empty();
481 template <bool fix_ndx_in_parent>
482 bool SubtableColumnBase::SubtableMap::adj_move_over(size_t from_row_ndx, size_t to_row_ndx) noexcept
484 typedef _impl::TableFriend tf;
486 size_t i = 0, n = m_entries.size();
487 // We return true if, and only if we remove the last entry in the map. We
488 // need special handling for the case, where the set of entries are already
489 // empty, otherwise the final return statement would return true in this
490 // case, even though we didn't actually remove an entry.
495 SubtableEntry& e = m_entries[i];
496 if (REALM_UNLIKELY(e.m_subtable_ndx == to_row_ndx)) {
497 // Must hold a counted reference while detaching
498 TableRef table(e.m_table);
500 // Delete entry by moving last over (faster and avoids invalidating
503 m_entries.pop_back();
506 if (REALM_UNLIKELY(e.m_subtable_ndx == from_row_ndx)) {
507 e.m_subtable_ndx = to_row_ndx;
508 if (fix_ndx_in_parent)
509 tf::set_ndx_in_parent(*(e.m_table), e.m_subtable_ndx);
514 return m_entries.empty();
517 template <bool fix_ndx_in_parent>
518 void SubtableColumnBase::SubtableMap::adj_swap_rows(size_t row_ndx_1, size_t row_ndx_2) noexcept
520 using tf = _impl::TableFriend;
521 for (auto& entry : m_entries) {
522 if (REALM_UNLIKELY(entry.m_subtable_ndx == row_ndx_1)) {
523 entry.m_subtable_ndx = row_ndx_2;
524 if (fix_ndx_in_parent)
525 tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx);
527 else if (REALM_UNLIKELY(entry.m_subtable_ndx == row_ndx_2)) {
528 entry.m_subtable_ndx = row_ndx_1;
529 if (fix_ndx_in_parent)
530 tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx);
536 template <bool fix_ndx_in_parent>
537 void SubtableColumnBase::SubtableMap::adj_move_row(size_t from_ndx, size_t to_ndx) noexcept
539 using tf = _impl::TableFriend;
540 for (auto& entry : m_entries) {
541 if (entry.m_subtable_ndx == from_ndx) {
542 entry.m_subtable_ndx = to_ndx;
543 if (fix_ndx_in_parent)
544 tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx);
547 if (from_ndx < to_ndx) {
548 // shift the range (from, to] down one
549 if (entry.m_subtable_ndx <= to_ndx && entry.m_subtable_ndx > from_ndx) {
550 entry.m_subtable_ndx--;
551 if (fix_ndx_in_parent) {
552 tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx);
555 } else if (from_ndx > to_ndx) {
556 // shift the range (from, to] up one
557 if (entry.m_subtable_ndx >= to_ndx && entry.m_subtable_ndx < from_ndx) {
558 entry.m_subtable_ndx++;
559 if (fix_ndx_in_parent) {
560 tf::set_ndx_in_parent(*(entry.m_table), entry.m_subtable_ndx);
568 inline void SubtableColumnBase::SubtableMap::adj_set_null(size_t row_ndx) noexcept
570 Table* table = find(row_ndx);
572 _impl::TableFriend::refresh_accessor_tree(*table);
575 inline SubtableColumnBase::SubtableColumnBase(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx)
576 : IntegerColumn(alloc, ref, column_ndx) // Throws
581 inline void SubtableColumnBase::update_child_ref(size_t child_ndx, ref_type new_ref)
583 set_as_ref(child_ndx, new_ref);
586 inline ref_type SubtableColumnBase::get_child_ref(size_t child_ndx) const noexcept
588 return get_as_ref(child_ndx);
591 inline void SubtableColumnBase::discard_child_accessors() noexcept
593 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
594 bool last_entry_removed = m_subtable_map.detach_and_remove_all();
595 if (last_entry_removed && m_table)
596 _impl::TableFriend::unbind_ptr(*m_table);
599 inline SubtableColumnBase::~SubtableColumnBase() noexcept
601 discard_child_accessors();
604 inline bool SubtableColumnBase::compare_subtable_rows(const Table& a, const Table& b)
606 return _impl::TableFriend::compare_rows(a, b);
609 inline ref_type SubtableColumnBase::clone_table_columns(const Table* t)
611 return _impl::TableFriend::clone_columns(*t, get_root_array()->get_alloc());
614 inline ref_type SubtableColumnBase::create(Allocator& alloc, size_t size)
616 return IntegerColumn::create(alloc, Array::type_HasRefs, size); // Throws
619 inline size_t* SubtableColumnBase::record_subtable_path(size_t* begin, size_t* end) noexcept
622 return 0; // Error, not enough space in buffer
623 *begin++ = get_column_index();
625 return 0; // Error, not enough space in buffer
626 return _impl::TableFriend::record_subtable_path(*m_table, begin, end);
629 inline void SubtableColumnBase::update_table_accessors(const size_t* col_path_begin, const size_t* col_path_end,
630 _impl::TableFriend::AccessorUpdater& updater)
632 // This function must assume no more than minimal consistency of the
633 // accessor hierarchy. This means in particular that it cannot access the
634 // underlying node structure. See AccessorConsistencyLevels.
636 m_subtable_map.update_accessors(col_path_begin, col_path_end, updater); // Throws
639 inline void SubtableColumnBase::do_insert(size_t row_ndx, int_fast64_t value, size_t num_rows)
641 IntegerColumn::insert_without_updating_index(row_ndx, value, num_rows); // Throws
642 bool is_append = row_ndx == realm::npos;
644 const bool fix_ndx_in_parent = true;
645 m_subtable_map.adj_insert_rows<fix_ndx_in_parent>(row_ndx, num_rows);
650 inline SubtableColumn::SubtableColumn(Allocator& alloc, ref_type ref, Table* table, size_t column_ndx)
651 : SubtableColumnBase(alloc, ref, table, column_ndx)
652 , m_subspec_ndx(realm::npos)
656 inline ConstTableRef SubtableColumn::get_subtable_tableref(size_t subtable_ndx) const
658 return const_cast<SubtableColumn*>(this)->get_subtable_tableref(subtable_ndx);
661 inline void SubtableColumn::refresh_accessor_tree(size_t col_ndx, const Spec& spec)
663 SubtableColumnBase::refresh_accessor_tree(col_ndx, spec); // Throws
664 m_subspec_ndx = spec.get_subspec_ndx(col_ndx);
665 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
666 m_subtable_map.refresh_accessor_tree(); // Throws
669 inline void SubtableColumn::refresh_subtable_map()
671 std::lock_guard<std::recursive_mutex> lg(m_subtable_map_lock);
672 m_subtable_map.refresh_accessor_tree(); // Throws
675 inline size_t SubtableColumn::get_subspec_ndx() const noexcept
677 if (REALM_UNLIKELY(m_subspec_ndx == realm::npos)) {
678 typedef _impl::TableFriend tf;
679 m_subspec_ndx = tf::get_spec(*m_table).get_subspec_ndx(get_column_index());
681 return m_subspec_ndx;
684 inline Spec* SubtableColumn::get_subtable_spec() noexcept
686 typedef _impl::TableFriend tf;
687 return tf::get_spec(*m_table).get_subtable_spec(get_column_index());
693 #endif // REALM_COLUMN_TABLE_HPP