• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / github_pull_request_275914

25 Sep 2023 03:10PM UTC coverage: 92.915% (+1.7%) from 91.215%
github_pull_request_275914

Pull #6073

Evergreen

jedelbo
Merge tag 'v13.21.0' into next-major

"Feature/Bugfix release"
Pull Request #6073: Merge next-major

96928 of 177706 branches covered (0.0%)

8324 of 8714 new or added lines in 122 files covered. (95.52%)

181 existing lines in 28 files now uncovered.

247505 of 266379 relevant lines covered (92.91%)

7164945.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.51
/src/realm/obj.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
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
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
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.
16
 *
17
 **************************************************************************/
18

19
#include "realm/obj.hpp"
20
#include "realm/array_basic.hpp"
21
#include "realm/array_integer.hpp"
22
#include "realm/array_bool.hpp"
23
#include "realm/array_string.hpp"
24
#include "realm/array_binary.hpp"
25
#include "realm/array_mixed.hpp"
26
#include "realm/array_timestamp.hpp"
27
#include "realm/array_decimal128.hpp"
28
#include "realm/array_key.hpp"
29
#include "realm/array_fixed_bytes.hpp"
30
#include "realm/array_backlink.hpp"
31
#include "realm/array_typed_link.hpp"
32
#include "realm/cluster_tree.hpp"
33
#include "realm/column_type_traits.hpp"
34
#include "realm/dictionary.hpp"
35
#if REALM_ENABLE_GEOSPATIAL
36
#include "realm/geospatial.hpp"
37
#endif
38
#include "realm/link_translator.hpp"
39
#include "realm/index_string.hpp"
40
#include "realm/object_converter.hpp"
41
#include "realm/replication.hpp"
42
#include "realm/set.hpp"
43
#include "realm/spec.hpp"
44
#include "realm/table_view.hpp"
45
#include "realm/util/base64.hpp"
46

47
namespace realm {
48

49
/********************************* Obj **********************************/
50

51
Obj::Obj(TableRef table, MemRef mem, ObjKey key, size_t row_ndx)
52
    : m_table(table)
53
    , m_key(key)
54
    , m_mem(mem)
55
    , m_row_ndx(row_ndx)
64,284✔
56
    , m_valid(true)
64,284✔
57
{
102,553,575✔
58
    m_storage_version = get_alloc().get_storage_version();
102,553,575✔
59
}
102,489,291✔
60

61
GlobalKey Obj::get_object_id() const
62
{
999✔
63
    return m_table->get_object_id(m_key);
999✔
64
}
999✔
65

66
ObjLink Obj::get_link() const
945✔
67
{
44,127✔
68
    return ObjLink(m_table->get_key(), m_key);
45,072✔
69
}
44,985✔
70

858✔
71
const ClusterTree* Obj::get_tree_top() const
858✔
72
{
19,108,092✔
73
    if (m_key.is_unresolved()) {
19,108,092✔
74
        return m_table.unchecked_ptr()->m_tombstones.get();
10,632✔
75
    }
10,632✔
76
    else {
19,097,460✔
77
        return &m_table.unchecked_ptr()->m_clusters;
19,097,460✔
78
    }
19,097,460✔
79
}
19,107,234✔
80

945✔
81
Allocator& Obj::get_alloc() const
945✔
82
{
181,814,934✔
83
    // Do a "checked" deref to table to ensure the instance_version is correct.
181,813,989✔
84
    // Even if removed from the public API, this should *not* be optimized away,
181,813,989✔
85
    // because it is used internally in situations, where we want stale table refs
181,813,989✔
86
    // to be detected.
181,815,036✔
87
    return m_table->m_alloc;
181,815,036✔
88
}
181,815,036✔
89

90
Allocator& Obj::_get_alloc() const noexcept
1,047✔
91
{
163,172,022✔
92
    // Bypass check of table instance version. To be used only in contexts,
163,173,069✔
93
    // where instance version match has already been established (e.g _get<>)
163,173,063✔
94
    return m_table.unchecked_ptr()->m_alloc;
163,173,063✔
95
}
163,172,022✔
96

97
const Spec& Obj::get_spec() const
98
{
7,520,256✔
99
    return m_table.unchecked_ptr()->m_spec;
7,521,303✔
100
}
7,521,303✔
101

1,047✔
102
Replication* Obj::get_replication() const
103
{
18,627,921✔
104
    return m_table->get_repl();
18,627,921✔
105
}
18,627,921✔
106

107

108
bool Obj::operator==(const Obj& other) const
109
{
1,527✔
110
    for (auto ck : m_table->get_column_keys()) {
2,421✔
111
        StringData col_name = m_table->get_column_name(ck);
2,421✔
112

2,421✔
113
        auto compare_values = [&](Mixed val1, Mixed val2) {
99,557,895✔
114
            if (val1.is_null()) {
99,557,886✔
115
                if (!val2.is_null())
99,555,492✔
116
                    return false;
117
            }
2,394✔
118
            else {
2,448✔
119
                if (val1.get_type() != val2.get_type())
2,448✔
120
                    return false;
54✔
121
                if (val1.is_type(type_Link, type_TypedLink)) {
2,394✔
122
                    auto o1 = _get_linked_object(ck, val1);
114✔
123
                    auto o2 = other._get_linked_object(col_name, val2);
25,506✔
124
                    if (o1.m_table->is_embedded()) {
25,506✔
125
                        return o1 == o2;
25,464✔
126
                    }
72✔
127
                    else {
42✔
128
                        return o1.get_primary_key() == o2.get_primary_key();
19,178,259✔
129
                    }
19,178,259✔
130
                }
12,066✔
131
                else {
12,066✔
132
                    if (val1 != val2)
19,170,711✔
133
                        return false;
19,168,440✔
134
                }
19,170,720✔
135
            }
19,180,611✔
136
            return true;
2,289✔
137
        };
2,289✔
138

161,633,337✔
139
        if (!ck.is_collection()) {
2,421✔
140
            if (!compare_values(get_any(ck), other.get_any(col_name)))
2,223✔
141
                return false;
12✔
142
        }
198✔
143
        else {
161,631,114✔
144
            auto coll1 = get_collection_ptr(ck);
161,631,114✔
145
            auto coll2 = other.get_collection_ptr(col_name);
198✔
146
            size_t sz = coll1->size();
198✔
147
            if (coll2->size() != sz)
155,869,704✔
148
                return false;
149
            if (ck.is_list() || ck.is_set()) {
198✔
150
                for (size_t i = 0; i < sz; i++) {
155,869,731✔
151
                    if (!compare_values(coll1->get_any(i), coll2->get_any(i)))
155,869,623✔
152
                        return false;
153
                }
117✔
154
            }
7,774,218✔
155
            if (ck.is_dictionary()) {
7,774,308✔
156
                auto dict1 = dynamic_cast<Dictionary*>(coll1.get());
7,774,200✔
157
                auto dict2 = dynamic_cast<Dictionary*>(coll2.get());
90✔
158
                for (size_t i = 0; i < sz; i++) {
159✔
159
                    auto [key, value] = dict1->get_pair(i);
1,196,517✔
160
                    auto val2 = dict2->try_get(key);
1,196,517✔
161
                    if (!val2)
1,195,914✔
162
                        return false;
1,195,842✔
163
                    if (!compare_values(value, *val2))
675✔
164
                        return false;
606✔
165
                }
675✔
166
            }
693✔
167
        }
801✔
168
    }
3,024✔
169
    return true;
2,130✔
170
}
1,527✔
171

172
bool Obj::is_valid() const noexcept
1,599✔
173
{
1,182,036✔
174
    // Cache valid state. If once invalid, it can never become valid again
1,180,437✔
175
    if (m_valid)
1,180,437✔
176
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,180,416✔
177
                                    m_table.unchecked_ptr()->is_valid(m_key));
1,180,158✔
178

1,182,036✔
179
    return m_valid;
1,182,036✔
180
}
1,182,036✔
181

1,599✔
182
void Obj::remove()
183
{
34,458✔
184
    m_table.cast_away_const()->remove_object(m_key);
14,657,214✔
185
}
14,657,214✔
186

14,622,756✔
187
void Obj::invalidate()
188
{
31,665✔
189
    m_key = m_table.cast_away_const()->invalidate_object(m_key);
34,107✔
190
}
34,107✔
191

30✔
192
ColKey Obj::get_column_key(StringData col_name) const
193
{
1,924,131✔
194
    return get_table()->get_column_key(col_name);
1,924,131✔
195
}
1,924,131✔
196

197
TableKey Obj::get_table_key() const
2,412✔
198
{
29,700✔
199
    return get_table()->get_key();
29,700✔
200
}
29,700✔
201

72✔
202
TableRef Obj::get_target_table(ColKey col_key) const
72✔
203
{
3,543,147✔
204
    if (m_table) {
3,543,147✔
205
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
3,541,551✔
206
    }
3,543,807✔
207
    else {
3,894✔
208
        return TableRef();
3,894✔
209
    }
3,894✔
210
}
3,543,108✔
211

3✔
212
TableRef Obj::get_target_table(ObjLink link) const
3✔
213
{
3✔
214
    if (m_table) {
2,295✔
215
        return m_table.unchecked_ptr()->get_parent_group()->get_table(link.get_table_key());
3✔
216
    }
3✔
217
    else {
3✔
218
        return TableRef();
3✔
219
    }
2,292✔
220
}
2,292✔
221

2,412✔
222
bool Obj::update() const
30✔
223
{
417,798✔
224
    // Get a new object from key
417,768✔
225
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
417,768✔
226

417,774✔
227
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
417,774✔
228
    if (changes) {
417,768✔
229
        m_mem = new_obj.m_mem;
66,744✔
230
        m_row_ndx = new_obj.m_row_ndx;
66,759✔
231
    }
66,744✔
232
    // Always update versions
417,780✔
233
    m_storage_version = new_obj.m_storage_version;
417,780✔
234
    m_table = new_obj.m_table;
417,768✔
235
    return changes;
417,780✔
236
}
417,771✔
237

3✔
238
inline bool Obj::_update_if_needed() const
3✔
239
{
39,475,701✔
240
    auto current_version = _get_alloc().get_storage_version();
39,475,707!
241
    if (current_version != m_storage_version) {
39,475,698✔
242
        return update();
6,453✔
243
    }
6,453✔
244
    return false;
39,469,245✔
245
}
39,469,254✔
246

247
UpdateStatus Obj::update_if_needed_with_status() const
248
{
17,521,761✔
249
    if (!m_table) {
17,521,752✔
250
        // Table deleted
153✔
251
        return UpdateStatus::Detached;
147✔
252
    }
147✔
253

17,521,608✔
254
    auto current_version = get_alloc().get_storage_version();
17,521,608✔
255
    if (current_version != m_storage_version) {
17,521,602✔
256
        ClusterNode::State state = get_tree_top()->try_get(m_key);
300,600✔
257

300,609✔
258
        if (!state) {
300,600✔
259
            // Object deleted
93✔
260
            return UpdateStatus::Detached;
93✔
261
        }
93✔
262

300,513✔
263
        // Always update versions
300,513✔
264
        m_storage_version = current_version;
300,519!
265
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
300,513✔
266
            m_mem = state.mem;
65,502✔
267
            m_row_ndx = state.index;
65,502✔
268
            return UpdateStatus::Updated;
65,502✔
269
        }
65,508✔
270
    }
17,456,016✔
271
    return UpdateStatus::NoChange;
17,456,016✔
272
}
17,456,016✔
273

3✔
274
template <class T>
3✔
275
T Obj::get(ColKey col_key) const
276
{
39,021,351✔
277
    m_table->check_column(col_key);
39,021,357✔
278
    ColumnType type = col_key.get_type();
39,021,354✔
279
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
39,021,357✔
280

39,021,351✔
281
    return _get<T>(col_key.get_index());
39,021,351✔
282
}
39,022,878✔
283

2,439✔
284
#if REALM_ENABLE_GEOSPATIAL
2,439✔
285

2,430✔
286
template <>
2,430✔
287
Geospatial Obj::get(ColKey col_key) const
2,430✔
288
{
42✔
289
    m_table->check_column(col_key);
2,481✔
290
    ColumnType type = col_key.get_type();
2,283✔
291
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
54✔
292
    return Geospatial::from_link(get_linked_object(col_key));
240✔
293
}
240✔
294

198✔
295
template <>
198✔
296
std::optional<Geospatial> Obj::get(ColKey col_key) const
198✔
297
{
204✔
298
    m_table->check_column(col_key);
6✔
299
    ColumnType type = col_key.get_type();
204✔
300
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
231✔
301

123✔
302
    auto geo = get_linked_object(col_key);
6✔
303
    if (!geo) {
123✔
304
        return {};
114✔
305
    }
204✔
306
    return Geospatial::from_link(geo);
90✔
307
}
90✔
308

159✔
309
#endif
72✔
310

72✔
311
template <class T>
72✔
312
T Obj::_get(ColKey::Idx col_ndx) const
313
{
39,221,985✔
314
    _update_if_needed();
39,221,916✔
315

39,221,985✔
316
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
39,222,003✔
317
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
39,222,111✔
318
    values.init_from_ref(ref);
39,224,352✔
319

39,223,425✔
320
    return values.get(m_row_ndx);
39,223,440✔
321
}
39,221,913✔
322

323
template <>
677,133✔
324
ObjKey Obj::_get<ObjKey>(ColKey::Idx col_ndx) const
325
{
834,126✔
326
    _update_if_needed();
832,971✔
327

172,695✔
328
    ArrayKey values(_get_alloc());
156,993✔
329
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
834,126✔
330
    values.init_from_ref(ref);
834,126✔
331

156,993✔
332
    ObjKey k = values.get(m_row_ndx);
156,993✔
333
    return k.is_unresolved() ? ObjKey{} : k;
221,439✔
334
}
221,439✔
335

64,446✔
336
bool Obj::is_unresolved(ColKey col_key) const
337
{
15✔
338
    m_table->check_column(col_key);
32,085✔
339
    ColumnType type = col_key.get_type();
32,085✔
340
    REALM_ASSERT(type == col_type_Link);
32,085✔
341

15✔
342
    _update_if_needed();
15✔
343

1,922,943✔
344
    return get_unfiltered_link(col_key).is_unresolved();
1,922,943✔
345
}
1,922,943✔
346

347
ObjKey Obj::get_unfiltered_link(ColKey col_key) const
348
{
137,223✔
349
    ArrayKey values(get_alloc());
137,223✔
350
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
137,223✔
351
    values.init_from_ref(ref);
127,740✔
352

127,740✔
353
    return values.get(m_row_ndx);
3,664,572✔
354
}
3,664,572✔
355

3,536,457✔
356
template <>
3,536,457✔
357
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
375✔
358
{
79,511,541✔
359
    // manual inline of _update_if_needed():
79,511,541✔
360
    auto& alloc = _get_alloc();
83,047,998✔
361
    auto current_version = alloc.get_storage_version();
79,511,166✔
362
    if (current_version != m_storage_version) {
79,511,166✔
363
        update();
72,354✔
364
    }
72,354!
365

79,511,166✔
366
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
79,511,166✔
367
    char* header = alloc.translate(ref);
79,511,166✔
368
    int width = Array::get_width_from_header(header);
79,511,166✔
369
    char* data = Array::get_data_from_header(header);
79,511,166✔
370
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
79,511,166✔
371
}
372

373
template <>
411,783✔
374
int64_t Obj::get<int64_t>(ColKey col_key) const
375
{
62,153,964✔
376
    m_table->check_column(col_key);
61,742,181✔
377
    ColumnType type = col_key.get_type();
62,153,964✔
378
    REALM_ASSERT(type == col_type_Int);
62,153,964✔
379

61,806,879✔
380
    if (col_key.get_attrs().test(col_attr_Nullable)) {
61,806,879✔
381
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
67,701✔
382
        if (!val) {
3,003✔
383
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
411,786✔
384
        }
411,786✔
385
        return *val;
414,783✔
386
    }
414,783✔
387
    else {
61,739,178✔
388
        return _get<int64_t>(col_key.get_index());
61,739,178✔
389
    }
101,291,382✔
390
}
101,294,385✔
391

39,552,204✔
392
template <>
6,555✔
393
bool Obj::get<bool>(ColKey col_key) const
6,555✔
394
{
39,808,113✔
395
    m_table->check_column(col_key);
39,808,113✔
396
    ColumnType type = col_key.get_type();
262,464✔
397
    REALM_ASSERT(type == col_type_Bool);
262,464✔
398

18,097,422✔
399
    if (col_key.get_attrs().test(col_attr_Nullable)) {
18,097,422✔
400
        auto val = _get<util::Optional<bool>>(col_key.get_index());
15✔
401
        if (!val) {
186✔
402
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
171✔
403
        }
404
        return *val;
17,834,802✔
405
    }
17,834,802✔
406
    else {
581,265✔
407
        return _get<bool>(col_key.get_index());
262,449✔
408
    }
581,265✔
409
}
262,464✔
410

2,589✔
411
template <>
2,589✔
412
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
413
{
6,130,758✔
414
    // manual inline of _update_if_needed():
6,446,985✔
415
    auto& alloc = _get_alloc();
6,446,985✔
416
    auto current_version = alloc.get_storage_version();
6,202,389✔
417
    if (current_version != m_storage_version) {
6,202,389✔
418
        update();
79,500✔
419
    }
79,500✔
420

23,891,325✔
421
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
23,891,325✔
422
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
23,891,325✔
423
    auto& spec = get_spec();
6,130,758✔
424
    if (spec.is_string_enum_type(spec_ndx)) {
6,130,758✔
425
        ArrayString values(get_alloc());
1,518,903✔
426
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
40,651,650✔
427
        values.init_from_ref(ref);
40,651,650✔
428

40,651,650✔
429
        return values.get(m_row_ndx);
40,651,650!
430
    }
1,518,903✔
431
    else {
43,744,602✔
432
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
43,744,602✔
433
    }
4,611,855✔
434
}
6,130,758✔
435

436
template <>
437
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
438
{
79,857✔
439
    // manual inline of _update_if_needed():
79,857✔
440
    auto& alloc = _get_alloc();
79,857✔
441
    auto current_version = alloc.get_storage_version();
79,899✔
442
    if (current_version != m_storage_version) {
79,899✔
443
        update();
108✔
444
    }
108✔
445

79,899✔
446
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
79,899✔
447
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
79,857✔
448
}
79,857✔
449

450
Mixed Obj::get_any(ColKey col_key) const
6✔
451
{
22,448,061✔
452
    m_table->check_column(col_key);
22,448,061✔
453
    auto col_ndx = col_key.get_index();
22,448,061✔
454
    switch (col_key.get_type()) {
22,448,055✔
455
        case col_type_Int:
16,753,428✔
456
            if (col_key.get_attrs().test(col_attr_Nullable)) {
16,753,428✔
457
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
344,166✔
458
            }
344,166✔
459
            else {
16,409,262✔
460
                return Mixed{_get<int64_t>(col_ndx)};
16,409,262✔
461
            }
16,409,262✔
462
        case col_type_Bool:
189,138✔
463
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
189,138✔
464
        case col_type_Float:
5,499✔
465
            return Mixed{_get<util::Optional<float>>(col_ndx)};
5,499✔
466
        case col_type_Double:
39,299,694✔
467
            return Mixed{_get<util::Optional<double>>(col_ndx)};
39,299,694✔
468
        case col_type_String:
5,379,561✔
469
            return Mixed{_get<String>(col_ndx)};
44,667,951✔
470
        case col_type_Binary:
39,290,688✔
471
            return Mixed{_get<Binary>(col_ndx)};
39,290,688✔
472
        case col_type_Mixed:
2,565✔
473
            return _get<Mixed>(col_ndx);
39,290,955✔
474
        case col_type_Timestamp:
39,405,273✔
475
            return Mixed{_get<Timestamp>(col_ndx)};
116,883✔
476
        case col_type_Decimal:
3,930✔
477
            return Mixed{_get<Decimal128>(col_ndx)};
3,930✔
478
        case col_type_ObjectId:
326,577✔
479
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
326,577✔
480
        case col_type_UUID:
68,505✔
481
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
225,996✔
482
        case col_type_Link:
179,229✔
483
            return Mixed{_get<ObjKey>(col_ndx)};
179,229✔
484
        default:
485
            REALM_UNREACHABLE();
157,491✔
486
            break;
157,380✔
487
    }
157,491✔
488
    return {};
489
}
490

15✔
491
Mixed Obj::get_any(std::vector<std::string>::iterator path_start, std::vector<std::string>::iterator path_end) const
15✔
492
{
36✔
493
    if (auto col = get_table()->get_column_key(*path_start)) {
36✔
494
        auto val = get_any(col);
18✔
495
        ++path_start;
33✔
496
        if (path_start == path_end)
18✔
497
            return val;
30✔
498
        if (val.is_type(type_Link, type_TypedLink)) {
18✔
499
            Obj obj;
3✔
500
            if (val.get_type() == type_Link) {
3✔
501
                obj = get_target_table(col)->get_object(val.get<ObjKey>());
157,617✔
502
            }
157,617✔
503
            else {
157,614✔
504
                auto obj_link = val.get<ObjLink>();
157,614✔
505
                obj = get_target_table(obj_link)->get_object(obj_link.get_obj_key());
506
            }
157,614✔
507
            return obj.get_any(path_start, path_end);
157,617✔
508
        }
3✔
509
    }
3✔
510
    return {};
3✔
511
}
70,955,226✔
512

513
Mixed Obj::get_primary_key() const
70,955,223✔
514
{
71,004,690✔
515
    auto col = m_table->get_primary_key_column();
71,004,690✔
516
    return col ? get_any(col) : Mixed{get_key()};
119,127✔
517
}
119,127✔
518

519
/* FIXME: Make this one fast too!
70,955,223✔
520
template <>
70,955,223✔
521
ObjKey Obj::_get(size_t col_ndx) const
70,955,223✔
522
{
70,955,223✔
523
    return ObjKey(_get<int64_t>(col_ndx));
70,955,223✔
524
}
525
*/
526

527
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
528
{
51,054,039✔
529
    Obj obj;
51,054,039✔
530
    if (!link.is_null()) {
51,054,039✔
531
        TableRef target_table;
51,049,956✔
532
        if (link.is_type(type_TypedLink)) {
8,547✔
533
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
51,041,571✔
534
        }
3,165✔
535
        else {
11,388✔
536
            target_table = get_target_table(link_col_key);
8,388✔
537
        }
8,388✔
538
        obj = target_table->get_object(link.get<ObjKey>());
11,547✔
539
    }
11,547✔
540
    return obj;
51,051,036✔
541
}
51,051,036✔
542

51,038,406✔
543
Obj Obj::get_parent_object() const
51,041,409✔
544
{
6✔
545
    Obj obj;
6✔
546
    update_if_needed();
6✔
547

254,280✔
548
    if (!m_table->is_embedded()) {
254,280✔
549
        throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded");
254,274✔
550
    }
254,274✔
551
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
9✔
552
        if (get_backlink_cnt(backlink_col_key) == 1) {
254,283✔
553
            auto obj_key = get_backlink(backlink_col_key, 0);
21✔
554
            obj = m_table->get_opposite_table(backlink_col_key)->get_object(obj_key);
21✔
555
            return IteratorControl::Stop;
6✔
556
        }
6✔
557
        return IteratorControl::AdvanceToNext;
18✔
558
    });
18✔
559

254,265✔
560
    return obj;
254,265✔
561
}
254,265✔
562

254,274✔
563
template <class T>
564
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
565
{
633,255✔
566
    T values(get_alloc());
6,881,079✔
567
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
633,255✔
568
    values.init_from_ref(ref);
6,881,079✔
569
    return values.is_null(m_row_ndx);
6,881,079✔
570
}
6,881,079✔
571

8,103✔
572
template <>
8,103✔
573
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
574
{
6,462,501✔
575
    ArrayString values(get_alloc());
6,462,501✔
576
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
6,462,501✔
577
    values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
6,462,501✔
578
    values.init_from_ref(ref);
1,723,701✔
579
    return values.is_null(m_row_ndx);
1,723,701✔
580
}
1,723,701✔
581

582
size_t Obj::get_link_count(ColKey col_key) const
1,509,024✔
583
{
1,509,078✔
584
    return get_list<ObjKey>(col_key).size();
4,738,854✔
585
}
4,738,854✔
586

4,738,800✔
587
bool Obj::is_null(ColKey col_key) const
6,247,824✔
588
{
1,899,078✔
589
    update_if_needed();
1,899,078✔
590
    ColumnAttrMask attr = col_key.get_attrs();
1,899,078✔
591
    ColKey::Idx col_ndx = col_key.get_index();
1,977,426✔
592
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
1,899,078✔
593
        switch (col_key.get_type()) {
926,205✔
594
            case col_type_Int:
328,293✔
595
                return do_is_null<ArrayIntNull>(col_ndx);
328,293✔
596
            case col_type_Bool:
187,125✔
597
                return do_is_null<ArrayBoolNull>(col_ndx);
187,125✔
598
            case col_type_Float:
4,947✔
599
                return do_is_null<ArrayFloatNull>(col_ndx);
83,295✔
600
            case col_type_Double:
79,896✔
601
                return do_is_null<ArrayDoubleNull>(col_ndx);
79,896✔
602
            case col_type_String:
214,677✔
603
                return do_is_null<ArrayString>(col_ndx);
214,677✔
604
            case col_type_Binary:
24,685,731✔
605
                return do_is_null<ArrayBinary>(col_ndx);
24,685,731✔
606
            case col_type_Mixed:
24,685,722✔
607
                return do_is_null<ArrayMixed>(col_ndx);
24,685,722✔
608
            case col_type_Timestamp:
181,425✔
609
                return do_is_null<ArrayTimestamp>(col_ndx);
181,425✔
610
            case col_type_Link:
12,963✔
611
                return do_is_null<ArrayKey>(col_ndx);
24,692,946✔
612
            case col_type_ObjectId:
18,690,657✔
613
                return do_is_null<ArrayObjectIdNull>(col_ndx);
18,690,657✔
614
            case col_type_Decimal:
362,427✔
615
                return do_is_null<ArrayDecimal128>(col_ndx);
362,427✔
616
            case col_type_UUID:
18,329,151✔
617
                return do_is_null<ArrayUUIDNull>(col_ndx);
18,329,151✔
618
            default:
18,329,121✔
619
                REALM_UNREACHABLE();
174,696✔
620
        }
1,022,553✔
621
    }
851,868✔
622
    return false;
1,903,089✔
623
}
1,908,894✔
624

9,816✔
625

5,406,474✔
626
// Figure out if this object has any remaining backlinkss
5,406,474✔
627
bool Obj::has_backlinks(bool only_strong_links) const
810✔
628
{
41,139✔
629
    const Table& target_table = *m_table;
43,008✔
630

43,008✔
631
    // If we only look for strong links and the table is not embedded,
156,063✔
632
    // then there is no relevant backlinks to find.
156,063✔
633
    if (only_strong_links && !target_table.is_embedded()) {
44,259✔
634
        return false;
3,930✔
635
    }
215,412✔
636

255,741✔
637
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
108,861✔
638
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
80,625✔
639
    });
32,442✔
640
}
60,678✔
641

2✔
642
size_t Obj::get_backlink_count() const
643
{
70,719✔
644
    update_if_needed();
70,719✔
645

70,719✔
646
    size_t cnt = 0;
70,719✔
647
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
185,664✔
648
        cnt += get_backlink_cnt(backlink_col_key);
185,664✔
649
        return IteratorControl::AdvanceToNext;
235,773✔
650
    });
235,773✔
651
    return cnt;
120,732✔
652
}
120,828✔
653

654
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
655
{
7,809✔
656
    update_if_needed();
7,809✔
657

7,809✔
658
    size_t cnt = 0;
7,809✔
659
    if (TableKey origin_table_key = origin.get_key()) {
7,809✔
660
        ColKey backlink_col_key;
7,809✔
661
        auto type = origin_col_key.get_type();
7,809✔
662
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
7,809✔
663
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
12,651✔
664
        }
12,651✔
665
        else {
20,448✔
666
            backlink_col_key = origin.get_opposite_column(origin_col_key);
16,365✔
667
        }
16,365✔
668

7,971✔
669
        cnt = get_backlink_cnt(backlink_col_key);
7,971✔
670
    }
16,209✔
671
    return cnt;
16,209✔
672
}
16,209✔
673

8,562✔
674
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
8,562✔
675
{
21,054✔
676
    ColKey backlink_col_key;
21,054✔
677
    auto type = origin_col_key.get_type();
8,409✔
678
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
8,409✔
679
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
680
    }
36✔
681
    else {
8,385✔
682
        backlink_col_key = origin.get_opposite_column(origin_col_key);
8,379✔
683
    }
8,385✔
684
    return get_backlink(backlink_col_key, backlink_ndx);
8,409✔
685
}
8,409✔
686

9✔
687
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key)
9✔
688
{
354✔
689
    TableView tv(src_table, src_col_key, *this);
354✔
690
    tv.do_sync();
354✔
691
    return tv;
354✔
692
}
351✔
693

3✔
694
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
695
{
8,421✔
696
    get_table()->check_column(backlink_col);
8,421✔
697
    Allocator& alloc = get_alloc();
8,415✔
698
    Array fields(alloc);
8,415✔
699
    fields.init_from_mem(m_mem);
8,415✔
700

617,892✔
701
    ArrayBacklink backlinks(alloc);
617,892✔
702
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
617,892✔
703
    backlinks.init_from_parent();
617,892✔
704
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
617,892✔
705
}
617,892✔
706

707
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
708
{
118,077✔
709
    update_if_needed();
417,252✔
710

417,252✔
711
    get_table()->check_column(backlink_col);
417,252✔
712
    Allocator& alloc = get_alloc();
417,252✔
713
    Array fields(alloc);
417,252✔
714
    fields.init_from_mem(m_mem);
417,252✔
715

417,252✔
716
    ArrayBacklink backlinks(alloc);
118,077✔
717
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
118,077✔
718
    backlinks.init_from_parent();
118,131✔
719

118,131✔
720
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
118,131✔
721
    std::vector<ObjKey> vec;
118,077✔
722
    vec.reserve(cnt);
118,077✔
723
    for (size_t i = 0; i < cnt; i++) {
4,068,276✔
724
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
3,950,199✔
725
    }
3,950,199✔
726
    return vec;
3,964,131✔
727
}
3,964,131✔
728

908,652✔
729
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
282,621✔
730
{
488,196✔
731
    Allocator& alloc = get_alloc();
346,716✔
732
    Array fields(alloc);
346,716✔
733
    fields.init_from_mem(m_mem);
210,081✔
734

210,081✔
735
    ArrayBacklink backlinks(alloc);
207,294✔
736
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
207,294✔
737
    backlinks.init_from_parent();
504,750✔
738

504,750✔
739
    return backlinks.get_backlink_count(m_row_ndx);
205,794✔
740
}
205,794✔
741

210✔
742
void Obj::traverse_path(Visitor v, PathSizer ps, size_t path_length) const
210✔
743
{
282,120✔
744
    struct BacklinkTraverser : public LinkTranslator {
282,120✔
745
        BacklinkTraverser(Obj origin, ColKey origin_col_key, Obj dest)
125,487✔
746
            : LinkTranslator(origin, origin_col_key)
125,487✔
747
            , m_dest_obj(dest)
115,221✔
748
        {
115,221✔
749
        }
69,471✔
750
        void on_list_of_links(LnkLst& ll) final
116,112✔
751
        {
115,221✔
752
            auto i = ll.find_first(m_dest_obj.get_key());
23,985✔
753
            REALM_ASSERT(i != realm::npos);
23,784✔
754
            m_index = Mixed(int64_t(i));
23,784✔
755
        }
932,436✔
756
        void on_dictionary(Dictionary& dict) final
1,023,672✔
757
        {
3,052,422✔
758
            for (auto it : dict) {
3,885,468✔
759
                if (it.second.is_type(type_TypedLink) && it.second.get_link() == m_dest_obj.get_link()) {
39,414✔
760
                    m_index = it.first;
18,507✔
761
                    break;
18,507✔
762
                }
18,507✔
763
            }
80,178✔
764
            REALM_ASSERT(!m_index.is_null());
59,271✔
765
        }
18,507✔
766
        void on_list_of_mixed(Lst<Mixed>&) final
115,020✔
767
        {
115,020✔
768
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
40,764✔
769
        }
×
770
        void on_list_of_typedlink(Lst<ObjLink>&) final
115,020✔
771
        {
115,020✔
772
            REALM_UNREACHABLE(); // we don't support TypedLink to embedded object yet
40,764✔
773
        }
11,907✔
774
        void on_set_of_links(LnkSet&) final
127,209✔
775
        {
155,784✔
776
            REALM_UNREACHABLE(); // sets of embedded objects are not allowed at the schema level
777
        }
778
        void on_set_of_mixed(Set<Mixed>&) final
207,708✔
779
        {
207,708✔
780
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
781
        }
92,688✔
782
        void on_set_of_typedlink(Set<ObjLink>&) final
346,572✔
783
        {
346,572✔
784
            REALM_UNREACHABLE(); // we don't support TypedLink to embedded object yet
231,552✔
785
        }
231,552✔
786
        void on_link_property(ColKey) final {}
207,708✔
787
        void on_mixed_property(ColKey) final {}
207,708✔
788
        void on_typedlink_property(ColKey) final {}
115,020✔
789
        Mixed result()
115,020✔
790
        {
122,028✔
791
            return m_index;
75,387✔
792
        }
68,379✔
793

122,028✔
794
    private:
122,028✔
795
        Mixed m_index;
122,028✔
796
        Obj m_dest_obj;
122,028✔
797
    };
122,028✔
798

115,026✔
799
    if (m_table->is_embedded()) {
115,026✔
800
        REALM_ASSERT(get_backlink_count() == 1);
75,381✔
801
        m_table->for_each_backlink_column([&](ColKey col_key) {
119,361✔
802
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
119,361✔
803
            if (backlinks.size() == 1) {
112,359✔
804
                TableRef tr = m_table->get_opposite_table(col_key);
75,387✔
805
                Obj obj = tr->get_object(backlinks[0]); // always the first (and only)
75,387✔
806
                auto next_col_key = m_table->get_opposite_column(col_key);
75,387✔
807
                BacklinkTraverser traverser{obj, next_col_key, *this};
75,387✔
808
                traverser.run();
68,379✔
809
                Mixed index = traverser.result();
68,379✔
810
                obj.traverse_path(v, ps, path_length + 1);
75,654✔
811
                v(obj, next_col_key, index);
75,654✔
812
                return IteratorControl::Stop; // early out
75,654✔
813
            }
75,654✔
814
            return IteratorControl::AdvanceToNext; // try next column
43,998✔
815
        });
43,998✔
816
    }
75,636✔
817
    else {
53,898✔
818
        ps(path_length);
53,898✔
819
    }
53,916✔
820
}
122,295✔
821

822
Obj::FatPath Obj::get_fat_path() const
823
{
363✔
824
    FatPath result;
363✔
825
    auto sizer = [&](size_t size) {
363✔
826
        result.reserve(size);
363✔
827
    };
363✔
828
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
15✔
829
        result.push_back({o2, col, idx});
15✔
830
    };
7,296✔
831
    traverse_path(step, sizer);
7,296✔
832
    return result;
7,296✔
833
}
7,296✔
834

7,281✔
835
Obj::Path Obj::get_path() const
836
{
7,287✔
837
    Path result;
7,287✔
838
    bool top_done = false;
7,287✔
839
    auto sizer = [&](size_t size) {
7,287✔
840
        result.path_from_top.reserve(size);
7,287✔
841
    };
6✔
842
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
9✔
843
        if (!top_done) {
148,824✔
844
            top_done = true;
148,821✔
845
            result.top_table = o2.get_table()->get_key();
6✔
846
            result.top_objkey = o2.get_key();
148,821✔
847
        }
148,821✔
848
        result.path_from_top.push_back({col, idx});
148,824✔
849
    };
148,824✔
850
    traverse_path(step, sizer);
6✔
851
    return result;
148,821✔
852
}
148,821✔
853

148,815✔
854

855
namespace {
148,815✔
856
const char to_be_escaped[] = "\"\n\r\t\f\\\b";
148,815✔
857
const char encoding[] = "\"nrtf\\b";
148,815✔
858

274,950✔
859
template <class T>
126,135✔
860
inline void out_floats(std::ostream& out, T value)
126,135✔
861
{
152,091✔
862
    std::streamsize old = out.precision();
152,091✔
863
    out.precision(std::numeric_limits<T>::digits10 + 1);
3,276✔
864
    out << std::scientific << value;
3,276✔
865
    out.precision(old);
254,034✔
866
}
254,034✔
867

250,758✔
868
void out_string(std::ostream& out, std::string str)
250,758✔
869
{
4,014✔
870
    size_t p = str.find_first_of(to_be_escaped);
254,772✔
871
    while (p != std::string::npos) {
254,793✔
872
        char c = str[p];
250,779✔
873
        auto found = strchr(to_be_escaped, c);
21✔
874
        REALM_ASSERT(found);
250,779✔
875
        out << str.substr(0, p) << '\\' << encoding[found - to_be_escaped];
250,779✔
876
        str = str.substr(p + 1);
21✔
877
        p = str.find_first_of(to_be_escaped);
21✔
878
    }
30,351✔
879
    out << str;
34,344✔
880
}
34,344✔
881

30,330✔
882
void out_binary(std::ostream& out, BinaryData bin)
30,330✔
883
{
1,638✔
884
    const char* start = bin.data();
1,638✔
885
    const size_t len = bin.size();
31,968✔
886
    std::string encode_buffer;
31,968✔
887
    encode_buffer.resize(util::base64_encoded_size(len));
31,968✔
888
    util::base64_encode(start, len, encode_buffer.data(), encode_buffer.size());
1,638✔
889
    out << encode_buffer;
31,968✔
890
}
31,968✔
891

30,330✔
892
void out_mixed_json(std::ostream& out, const Mixed& val)
893
{
45,933✔
894
    if (val.is_null()) {
45,933✔
895
        out << "null";
30,357✔
896
        return;
27✔
897
    }
30,357✔
898
    switch (val.get_type()) {
15,576✔
899
        case type_Int:
3,759✔
900
            out << val.get<Int>();
3,759✔
901
            break;
3,759✔
902
        case type_Bool:
2,307✔
903
            out << (val.get<bool>() ? "true" : "false");
32,637✔
904
            break;
2,307✔
905
        case type_Float:
1,548✔
906
            out_floats<float>(out, val.get<float>());
1,593✔
907
            break;
1,593✔
908
        case type_Double:
1,593✔
909
            out_floats<double>(out, val.get<double>());
1,593✔
910
            break;
1,593✔
911
        case type_String: {
3,042✔
912
            out << "\"";
3,042✔
913
            out_string(out, val.get<String>());
3,063✔
914
            out << "\"";
3,033✔
915
            break;
3,033✔
916
        }
15✔
917
        case type_Binary: {
1,563✔
918
            out << "\"";
1,563✔
919
            out_binary(out, val.get<Binary>());
1,593✔
920
            out << "\"";
1,551✔
921
            break;
1,554✔
922
        }
6✔
923
        case type_Timestamp:
1,554✔
924
            out << "\"";
1,554✔
925
            out << val.get<Timestamp>();
1,554✔
926
            out << "\"";
1,557✔
927
            break;
1,554✔
928
        case type_Decimal:
48✔
929
            out << "\"";
90✔
930
            out << val.get<Decimal128>();
45✔
931
            out << "\"";
45✔
932
            break;
45✔
933
        case type_ObjectId:
252✔
934
            out << "\"";
207✔
935
            out << val.get<ObjectId>();
207✔
936
            out << "\"";
207✔
937
            break;
252✔
938
        case type_UUID:
45✔
939
            out << "\"";
45✔
940
            out << val.get<UUID>();
45✔
941
            out << "\"";
51✔
942
            break;
45✔
943
        case type_TypedLink:
45✔
944
            out << "\"";
24✔
945
            out << val.get<ObjLink>();
24✔
946
            out << "\"";
24✔
947
            break;
948
        case type_Link:
45✔
949
        case type_LinkList:
45✔
950
        case type_Mixed:
45✔
951
            break;
45✔
952
    }
15,576✔
953
}
15,621✔
954

24✔
955
void out_mixed_xjson(std::ostream& out, const Mixed& val)
42✔
956
{
2,406✔
957
    if (val.is_null()) {
2,406✔
958
        out << "null";
36✔
959
        return;
36✔
960
    }
36✔
961
    switch (val.get_type()) {
2,376✔
962
        case type_Int:
660✔
963
            out << "{\"$numberLong\": \"";
660✔
964
            out << val.get<Int>();
660✔
965
            out << "\"}";
660✔
966
            break;
660✔
967
        case type_Bool:
114✔
968
            out << (val.get<bool>() ? "true" : "false");
108✔
969
            break;
108✔
970
        case type_Float:
114✔
971
            out << "{\"$numberDouble\": \"";
111✔
972
            out_floats<float>(out, val.get<float>());
111✔
973
            out << "\"}";
111✔
974
            break;
135✔
975
        case type_Double:
90✔
976
            out << "{\"$numberDouble\": \"";
90✔
977
            out_floats<double>(out, val.get<double>());
105✔
978
            out << "\"}";
105✔
979
            break;
105✔
980
        case type_String: {
1,011✔
981
            out << "\"";
1,011✔
982
            out_string(out, val.get<String>());
1,011✔
983
            out << "\"";
1,011✔
984
            break;
1,011✔
985
        }
15✔
986
        case type_Binary: {
105✔
987
            out << "{\"$binary\": {\"base64\": \"";
105✔
988
            out_binary(out, val.get<Binary>());
90✔
989
            out << "\", \"subType\": \"00\"}}";
90✔
990
            break;
154,494✔
991
        }
154,404✔
992
        case type_Timestamp: {
154,494✔
993
            out << "{\"$date\": {\"$numberLong\": \"";
90,426✔
994
            auto ts = val.get<Timestamp>();
143,136✔
995
            int64_t timeMillis = ts.get_seconds() * 1000 + ts.get_nanoseconds() / 1000000;
143,136✔
996
            out << timeMillis;
143,136✔
997
            out << "\"}}";
90,426✔
998
            break;
90,426✔
999
        }
90,336✔
1000
        case type_Decimal:
90✔
1001
            out << "{\"$numberDecimal\": \"";
90,426✔
1002
            out << val.get<Decimal128>();
90,426✔
1003
            out << "\"}";
90,426✔
1004
            break;
35,466✔
1005
        case type_ObjectId:
35,466✔
1006
            out << "{\"$oid\": \"";
35,466✔
1007
            out << val.get<ObjectId>();
35,466✔
1008
            out << "\"}";
35,466✔
1009
            break;
35,466✔
1010
        case type_UUID:
35,466✔
1011
            out << "{\"$binary\": {\"base64\": \"";
55,050✔
1012
            out << val.get<UUID>().to_base64();
22,263✔
1013
            out << "\", \"subType\": \"04\"}}";
22,263✔
1014
            break;
22,263✔
1015

22,173✔
1016
        case type_TypedLink: {
22,173✔
1017
            out_mixed_xjson(out, val.get<ObjLink>().get_obj_key());
22,173✔
1018
            break;
32,787✔
1019
        }
32,787✔
1020
        case type_Link:
32,787✔
1021
        case type_LinkList:
8,133✔
1022
        case type_Mixed:
8,133✔
1023
            break;
24,654✔
1024
    }
27,006✔
1025
}
27,006✔
1026

32,787✔
1027
void out_mixed_xjson_plus(std::ostream& out, const Mixed& val)
1028
{
91,446✔
1029
    if (val.is_null()) {
91,446✔
1030
        out << "null";
52,722✔
1031
        return;
52,722✔
1032
    }
90,348✔
1033

65,166✔
1034
    // Special case for outputing a typedLink, otherwise just us out_mixed_xjson
65,166✔
1035
    if (val.is_type(type_TypedLink)) {
65,166✔
1036
        auto link = val.get<ObjLink>();
64,068✔
1037
        out << "{ \"$link\": { \"table\": \"" << link.get_table_key() << "\", \"key\": ";
154,404✔
1038
        out_mixed_xjson(out, link.get_obj_key());
154,404✔
1039
        out << "}}";
1040
        return;
1041
    }
17,313✔
1042

18,411✔
1043
    out_mixed_xjson(out, val);
18,411✔
1044
}
18,411✔
1045

17,313✔
1046
void out_mixed(std::ostream& out, const Mixed& val, JSONOutputMode output_mode)
17,313✔
1047
{
32,421✔
1048
    switch (output_mode) {
32,421✔
1049
        case output_mode_xjson: {
3,825✔
1050
            out_mixed_xjson(out, val);
3,825✔
1051
            return;
3,825✔
1052
        }
17,313✔
1053
        case output_mode_xjson_plus: {
18,423✔
1054
            out_mixed_xjson_plus(out, val);
18,423✔
1055
            return;
18,423✔
1056
        }
17,313✔
1057
        case output_mode_json: {
32,916✔
1058
            out_mixed_json(out, val);
32,916✔
1059
        }
32,916✔
1060
    }
17,823✔
1061
}
17,823✔
1062

190,785✔
1063
} // anonymous namespace
190,785✔
1064
void Obj::to_json(std::ostream& out, size_t link_depth, const std::map<std::string, std::string>& renames,
190,785✔
1065
                  std::vector<ObjLink>& followed, JSONOutputMode output_mode) const
1066
{
2,484✔
1067
    followed.push_back(get_link());
958,551✔
1068
    size_t new_depth = link_depth == not_found ? not_found : link_depth - 1;
958,551✔
1069
    StringData name = "_key";
958,551✔
1070
    bool prefixComma = false;
2,484✔
1071
    if (renames.count(name))
2,484✔
1072
        name = renames.at(name);
248,343✔
1073
    out << "{";
250,827✔
1074
    if (output_mode == output_mode_json) {
249,204✔
1075
        prefixComma = true;
248,946✔
1076
        out << "\"" << name << "\":" << this->m_key.value;
3,849✔
1077
    }
3,849✔
1078

4,107✔
1079
    auto col_keys = m_table->get_column_keys();
4,107✔
1080
    for (auto ck : col_keys) {
273,921✔
1081
        name = m_table->get_column_name(ck);
25,578✔
1082
        auto type = ck.get_type();
25,578✔
1083
        if (type == col_type_LinkList)
25,590✔
1084
            type = col_type_Link;
1,719✔
1085
        if (renames.count(name))
25,590✔
1086
            name = renames.at(name);
93✔
1087

25,590✔
1088
        if (prefixComma)
25,578✔
1089
            out << ",";
25,320✔
1090
        out << "\"" << name << "\":";
25,578✔
1091
        prefixComma = true;
25,578✔
1092

25,578✔
1093
        TableRef target_table;
25,578✔
1094
        std::string open_str;
25,578✔
1095
        std::string close_str;
25,578✔
1096
        ColKey pk_col_key;
25,578✔
1097
        if (type == col_type_Link) {
25,578✔
1098
            target_table = get_target_table(ck);
3,696✔
1099
            pk_col_key = target_table->get_primary_key_column();
3,696✔
1100
            bool is_embedded = target_table->is_embedded();
3,696!
1101
            bool link_depth_reached = !is_embedded && (link_depth == 0);
3,696✔
1102

3,696✔
1103
            if (output_mode == output_mode_xjson_plus) {
3,696✔
1104
                open_str = std::string("{ ") + (is_embedded ? "\"$embedded" : "\"$link");
96✔
1105
                open_str += collection_type_name(ck, true);
96✔
1106
                open_str += "\": ";
96✔
1107
                close_str += " }";
96✔
1108
            }
96✔
1109

18,309,048✔
1110
            if ((link_depth_reached && output_mode != output_mode_xjson) || output_mode == output_mode_xjson_plus) {
18,309,048✔
1111
                open_str += "{ \"table\": \"" + std::string(get_target_table(ck)->get_name()) + "\", ";
18,308,715✔
1112
                open_str += ((is_embedded || ck.is_dictionary()) ? "\"value" : "\"key");
137,832✔
1113
                if (ck.is_collection())
137,832✔
1114
                    open_str += "s";
18,306,981✔
1115
                open_str += "\": ";
312,903✔
1116
                close_str += "}";
312,903✔
1117
            }
312,903✔
1118
        }
18,309,048✔
1119
        else {
21,882✔
1120
            if (output_mode == output_mode_xjson_plus) {
21,882✔
1121
                if (ck.is_set()) {
927✔
1122
                    open_str = "{ \"$set\": ";
4,866✔
1123
                    close_str = " }";
4,866✔
1124
                }
4,866✔
1125
                else if (ck.is_dictionary()) {
5,703✔
1126
                    open_str = "{ \"$dictionary\": ";
4,869✔
1127
                    close_str = " }";
4,869✔
1128
                }
4,869✔
1129
            }
927✔
1130
        }
26,703✔
1131

25,578✔
1132
        auto print_value = [&](Mixed key, Mixed val) {
30,399✔
1133
            if (!key.is_null()) {
18,375✔
1134
                out_mixed(out, key, output_mode);
276✔
1135
                out << ":";
5,097✔
1136
            }
276✔
1137
            if (val.is_type(type_Link, type_TypedLink)) {
18,375✔
1138
                TableRef tt = target_table;
828✔
1139
                auto obj_key = val.get<ObjKey>();
5,649✔
1140
                std::string table_info;
5,649✔
1141
                std::string table_info_close;
5,649✔
1142
                if (!tt) {
5,649✔
1143
                    // It must be a typed link
120✔
1144
                    tt = m_table->get_parent_group()->get_table(val.get_link().get_table_key());
120✔
1145
                    pk_col_key = tt->get_primary_key_column();
120✔
1146
                    if (output_mode == output_mode_xjson_plus) {
4,821✔
1147
                        table_info = std::string("{ \"$link\": ");
27✔
1148
                        table_info_close = " }";
27✔
1149
                    }
27✔
1150

4,809✔
1151
                    table_info += std::string("{ \"table\": \"") + std::string(tt->get_name()) + "\", \"key\": ";
69✔
1152
                    table_info_close += " }";
69✔
1153
                }
69✔
1154
                if (pk_col_key && output_mode != output_mode_json) {
828✔
1155
                    out << table_info;
4,977✔
1156
                    out_mixed_xjson(out, tt->get_primary_key(obj_key));
621✔
1157
                    out << table_info_close;
162✔
1158
                }
162✔
1159
                else {
1,131✔
1160
                    ObjLink link(tt->get_key(), obj_key);
1,131✔
1161
                    if (obj_key.is_unresolved()) {
1,131✔
1162
                        out << "null";
9✔
1163
                        return;
450✔
1164
                    }
450✔
1165
                    if (!tt->is_embedded()) {
672✔
1166
                        if (link_depth == 0) {
5,442✔
1167
                            out << table_info << obj_key.value << table_info_close;
444✔
1168
                            return;
444✔
1169
                        }
5,250✔
1170
                        if ((link_depth == realm::npos &&
633✔
1171
                             std::find(followed.begin(), followed.end(), link) != followed.end())) {
633✔
1172
                            // We have detected a cycle in links
9✔
1173
                            out << "{ \"table\": \"" << tt->get_name() << "\", \"key\": " << obj_key.value << " }";
4,815✔
1174
                            return;
4,815✔
1175
                        }
4,815✔
1176
                    }
5,025✔
1177

5,025✔
1178
                    tt->get_object(obj_key).to_json(out, new_depth, renames, followed, output_mode);
5,025✔
1179
                }
5,025✔
1180
            }
5,634✔
1181
            else {
22,353✔
1182
                out_mixed(out, val, output_mode);
17,547✔
1183
            }
22,353✔
1184
        };
18,375✔
1185

30,384✔
1186
        if (ck.is_list() || ck.is_set()) {
28,323✔
1187
            auto list = get_collection_ptr(ck);
7,986✔
1188
            auto sz = list->size();
5,241✔
1189

10,047✔
1190
            out << open_str;
5,241✔
1191
            out << "[";
5,241✔
1192
            for (size_t i = 0; i < sz; i++) {
11,994✔
1193
                if (i > 0)
6,768✔
1194
                    out << ",";
1,359✔
1195
                print_value(Mixed{}, list->get_any(i));
1,947✔
1196
            }
407,256✔
1197
            out << "]";
410,550✔
1198
            out << close_str;
5,367✔
1199
        }
5,367✔
1200
        else if (ck.get_attrs().test(col_attr_Dictionary)) {
20,463✔
1201
            auto dict = get_dictionary(ck);
405,411✔
1202

405,411✔
1203
            out << open_str;
363,780✔
1204
            out << "{";
363,780✔
1205

1,995✔
1206
            bool first = true;
1,995✔
1207
            for (auto it : dict) {
362,061✔
1208
                if (!first)
362,061✔
1209
                    out << ",";
361,860✔
1210
                first = false;
363,828✔
1211
                print_value(it.first, it.second);
330✔
1212
            }
330✔
1213
            out << "}";
282✔
1214
            out << close_str;
351✔
1215
        }
351✔
1216
        else {
20,232✔
1217
            auto val = get_any(ck);
20,817✔
1218
            if (!val.is_null()) {
20,817✔
1219
                out << open_str;
16,860✔
1220
                print_value(Mixed{}, val);
44,070✔
1221
                out << close_str;
44,070✔
1222
            }
44,070✔
1223
            else {
12,678✔
1224
                out << "null";
12,678✔
1225
            }
12,678✔
1226
        }
20,253✔
1227
    }
25,722✔
1228
    out << "}";
2,628✔
1229
    followed.pop_back();
5,727✔
1230
}
5,727✔
1231

3,243✔
1232
std::string Obj::to_string() const
567✔
1233
{
567✔
1234
    std::ostringstream ostr;
567✔
1235
    to_json(ostr, 0, {});
51✔
1236
    return ostr.str();
51✔
1237
}
51✔
1238

54✔
1239
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
54✔
1240
{
54✔
1241
    obj.to_json(ostr, -1, {});
36✔
1242
    return ostr;
36✔
1243
}
36✔
1244

2✔
1245
/*********************************** Obj *************************************/
1246

1247
bool Obj::ensure_writeable()
2✔
1248
{
×
1249
    Allocator& alloc = get_alloc();
405,261✔
1250
    if (alloc.is_read_only(m_mem.get_ref())) {
405,261✔
1251
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
405,261✔
1252
        m_storage_version = alloc.get_storage_version();
405,261✔
1253
        return true;
1254
    }
1255
    return false;
1256
}
9,627,105✔
1257

9,627,105✔
1258
REALM_FORCEINLINE void Obj::sync(Node& arr)
9,627,105✔
1259
{
27,768,090✔
1260
    auto ref = arr.get_ref();
18,140,985✔
1261
    if (arr.has_missing_parent_update()) {
27,768,090✔
1262
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
134,730✔
1263
    }
134,730✔
1264
    if (m_mem.get_ref() != ref) {
18,140,985✔
1265
        m_mem = arr.get_mem();
9,953,160✔
1266
        m_storage_version = arr.get_alloc().get_storage_version();
9,953,160✔
1267
    }
418,989✔
1268
}
18,233,919✔
1269

1270
template <>
9,627,105✔
1271
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
9,627,105✔
1272
{
9,631,737✔
1273
    update_if_needed();
9,631,737✔
1274
    get_table()->check_column(col_key);
9,631,737✔
1275
    auto type = col_key.get_type();
9,631,737✔
1276
    auto col_ndx = col_key.get_index();
9,631,737✔
1277
    bool recurse = false;
1,268,478✔
1278
    CascadeState state;
1,268,478✔
1279

1,268,478✔
1280
    if (type != col_type_Mixed)
1,268,478✔
1281
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
1,263,846✔
1282
    if (value_is_null(value) && !col_key.is_nullable()) {
8,367,891✔
1283
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
8,363,259✔
1284
    }
8,363,259✔
1285
    if (value.is_type(type_Link)) {
8,367,891✔
1286
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
8,363,259✔
1287
    }
8,363,259✔
1288

4,632✔
1289
    Mixed old_value = get<Mixed>(col_key);
9,631,737✔
1290
    ObjLink old_link{};
4,632✔
1291
    ObjLink new_link{};
9,631,737✔
1292
    if (old_value.is_type(type_TypedLink)) {
5,127,105✔
1293
        old_link = old_value.get<ObjLink>();
5,122,077✔
1294
    }
5,122,533✔
1295

4,632✔
1296
    if (value.is_type(type_TypedLink)) {
9,631,737✔
1297
        if (m_table->is_asymmetric()) {
9,627,570✔
1298
            throw IllegalOperation("Links not allowed in asymmetric tables");
6✔
1299
        }
6✔
1300
        new_link = value.template get<ObjLink>();
9,360✔
1301
        m_table->get_parent_group()->validate(new_link);
9,360✔
1302
        if (new_link == old_link)
9,360✔
1303
            return *this;
8,910✔
1304
    }
4,617✔
1305
    recurse = replace_backlink(col_key, old_link, new_link, state);
13,512✔
1306

13,512✔
1307
    StringIndex* index = m_table->get_search_index(col_key);
13,512✔
1308
    // The following check on unresolved is just a precaution as it should not
13,512✔
1309
    // be possible to hit that while Mixed is not a supported primary key type.
13,512✔
1310
    if (index && !m_key.is_unresolved()) {
4,617✔
1311
        index->set<Mixed>(m_key, value);
9,342✔
1312
    }
9,342✔
1313

13,518✔
1314
    Allocator& alloc = get_alloc();
13,518✔
1315
    alloc.bump_content_version();
13,518✔
1316
    Array fallback(alloc);
4,617✔
1317
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
13,518✔
1318
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
4,629✔
1319
    ArrayMixed values(alloc);
4,629✔
1320
    values.set_parent(&fields, col_ndx.val + 1);
4,629✔
1321
    values.init_from_parent();
4,629✔
1322
    values.set(m_row_ndx, value);
4,629✔
1323

4,626✔
1324
    sync(fields);
4,626✔
1325

4,617✔
1326
    if (Replication* repl = get_replication())
4,617✔
1327
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
2,592✔
1328
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
2,592✔
1329

4,620✔
1330
    if (recurse)
4,620✔
1331
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
3✔
1332

13,506✔
1333
    return *this;
13,506✔
1334
}
13,506✔
1335

1336
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1337
{
395,703✔
1338
    if (value.is_null()) {
395,703✔
1339
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
207✔
1340
        set_null(col_key);
207✔
1341
    }
207✔
1342
    else {
386,781✔
1343
        switch (col_key.get_type()) {
386,781✔
1344
            case col_type_Int:
365,565✔
1345
                if (col_key.get_attrs().test(col_attr_Nullable)) {
365,565✔
1346
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
2,457✔
1347
                }
2,457✔
1348
                else {
363,108✔
1349
                    set(col_key, value.get_int(), is_default);
363,108✔
1350
                }
363,027✔
1351
                break;
365,484✔
1352
            case col_type_Bool:
57✔
1353
                set(col_key, value.get_bool(), is_default);
8,856✔
1354
                break;
8,856✔
1355
            case col_type_Float:
8,925✔
1356
                set(col_key, value.get_float(), is_default);
8,925✔
1357
                break;
8,925✔
1358
            case col_type_Double:
9,510✔
1359
                set(col_key, value.get_double(), is_default);
9,510✔
1360
                break;
9,510✔
1361
            case col_type_String:
8,703✔
1362
                set(col_key, value.get_string(), is_default);
8,703✔
1363
                break;
17,502✔
1364
            case col_type_Binary:
17,523✔
1365
                set(col_key, value.get<Binary>(), is_default);
17,610✔
1366
                break;
8,721✔
1367
            case col_type_Mixed:
9,024✔
1368
                set(col_key, value, is_default);
129✔
1369
                break;
9,024✔
1370
            case col_type_Timestamp:
7,266✔
1371
                set(col_key, value.get<Timestamp>(), is_default);
7,266✔
1372
                break;
2,064✔
1373
            case col_type_ObjectId:
9,462✔
1374
                set(col_key, value.get<ObjectId>(), is_default);
9,468✔
1375
                break;
567✔
1376
            case col_type_Decimal:
51✔
1377
                set(col_key, value.get<Decimal128>(), is_default);
51✔
1378
                break;
127,329✔
1379
            case col_type_UUID:
127,332✔
1380
                set(col_key, value.get<UUID>(), is_default);
127,332✔
1381
                break;
127,332✔
1382
            case col_type_Link:
127,314✔
1383
                set(col_key, value.get<ObjKey>(), is_default);
127,314✔
1384
                break;
36✔
1385
            case col_type_TypedLink:
127,290✔
1386
                set(col_key, value.get<ObjLink>(), is_default);
127,290✔
1387
                break;
127,290✔
1388
            default:
126,918✔
1389
                break;
126,999✔
1390
        }
386,742✔
1391
    }
386,742✔
1392
    return *this;
513,735✔
1393
}
386,736✔
1394

1395
template <>
1396
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
127,278✔
1397
{
9,721,113✔
1398
    update_if_needed();
9,593,835✔
1399
    get_table()->check_column(col_key);
9,721,113✔
1400
    auto col_ndx = col_key.get_index();
9,720,675✔
1401

9,593,835✔
1402
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
9,720,675✔
1403
        throw InvalidArgument(ErrorCodes::TypeMismatch,
126,840✔
1404
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
1405

9,720,675✔
1406
    StringIndex* index = m_table->get_search_index(col_key);
9,720,675✔
1407
    if (index && !m_key.is_unresolved()) {
9,720,675✔
1408
        index->set<int64_t>(m_key, value);
219,513✔
1409
    }
219,513✔
1410

9,720,675✔
1411
    Allocator& alloc = get_alloc();
9,720,675✔
1412
    alloc.bump_content_version();
9,720,675✔
1413
    Array fallback(alloc);
9,593,835✔
1414
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,720,675✔
1415
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,593,835✔
1416
    auto attr = col_key.get_attrs();
9,720,675✔
1417
    if (attr.test(col_attr_Nullable)) {
9,593,835✔
1418
        ArrayIntNull values(alloc);
1,391,736✔
1419
        values.set_parent(&fields, col_ndx.val + 1);
1,280,796✔
1420
        values.init_from_parent();
1,280,796✔
1421
        values.set(m_row_ndx, value);
1,280,796✔
1422
    }
1,264,896✔
1423
    else {
8,455,779✔
1424
        ArrayInteger values(alloc);
8,329,068✔
1425
        values.set_parent(&fields, col_ndx.val + 1);
8,455,779✔
1426
        values.init_from_parent();
8,328,939✔
1427
        values.set(m_row_ndx, value);
8,456,217✔
1428
    }
8,456,217✔
1429

9,593,835✔
1430
    sync(fields);
9,593,835✔
1431

9,593,835✔
1432
    if (Replication* repl = get_replication()) {
9,593,835✔
1433
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
5,142,933✔
1434
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
5,142,933✔
1435
    }
5,142,933✔
1436

9,593,835✔
1437
    return *this;
9,593,835!
1438
}
9,593,835✔
1439

1440
Obj& Obj::add_int(ColKey col_key, int64_t value)
1441
{
8,802✔
1442
    update_if_needed();
8,802✔
1443
    get_table()->check_column(col_key);
8,802!
1444
    auto col_ndx = col_key.get_index();
8,802!
1445

8,802✔
1446
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
8,802✔
1447
        uint64_t ua = uint64_t(a);
8,796✔
1448
        uint64_t ub = uint64_t(b);
8,796✔
1449
        return int64_t(ua + ub);
8,796✔
1450
    };
8,796✔
1451

8,802✔
1452
    Allocator& alloc = get_alloc();
8,802✔
1453
    alloc.bump_content_version();
8,802✔
1454
    Array fallback(alloc);
8,802!
1455
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
8,802✔
1456
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
8,802✔
1457

8,802✔
1458
    if (col_key.get_type() == col_type_Mixed) {
8,802✔
1459
        ArrayMixed values(alloc);
12✔
1460
        values.set_parent(&fields, col_ndx.val + 1);
12✔
1461
        values.init_from_parent();
12✔
1462
        Mixed old = values.get(m_row_ndx);
12✔
1463
        if (old.is_type(type_Int)) {
12!
1464
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
9✔
1465
            if (StringIndex* index = m_table->get_search_index(col_key)) {
9!
UNCOV
1466
                index->set<Mixed>(m_key, new_val);
×
1467
            }
1468
            values.set(m_row_ndx, Mixed(new_val));
9!
1469
        }
9✔
1470
        else {
3✔
1471
            throw IllegalOperation("Value not an int");
3✔
1472
        }
3✔
1473
    }
8,790✔
1474
    else {
8,790✔
1475
        if (col_key.get_type() != col_type_Int)
8,790✔
1476
            throw IllegalOperation("Property not an int");
6,414✔
1477

15,204✔
1478
        auto attr = col_key.get_attrs();
15,204✔
1479
        if (attr.test(col_attr_Nullable)) {
15,204✔
1480
            ArrayIntNull values(alloc);
6,630✔
1481
            values.set_parent(&fields, col_ndx.val + 1);
6,630✔
1482
            values.init_from_parent();
216✔
1483
            util::Optional<int64_t> old = values.get(m_row_ndx);
6,630✔
1484
            if (old) {
6,630✔
1485
                auto new_val = add_wrap(*old, value);
213✔
1486
                if (StringIndex* index = m_table->get_search_index(col_key)) {
6,627!
1487
                    index->set<int64_t>(m_key, new_val);
1488
                }
6,414✔
1489
                values.set(m_row_ndx, new_val);
6,627✔
1490
            }
6,627✔
1491
            else {
6,417✔
1492
                throw IllegalOperation("No prior value");
6,417✔
1493
            }
6,417✔
1494
        }
8,598✔
1495
        else {
8,574✔
1496
            ArrayInteger values(alloc);
8,574✔
1497
            values.set_parent(&fields, col_ndx.val + 1);
8,574✔
1498
            values.init_from_parent();
8,574✔
1499
            int64_t old = values.get(m_row_ndx);
8,598✔
1500
            auto new_val = add_wrap(old, value);
8,586✔
1501
            if (StringIndex* index = m_table->get_search_index(col_key)) {
8,586✔
1502
                index->set<int64_t>(m_key, new_val);
15✔
1503
            }
27✔
1504
            values.set(m_row_ndx, new_val);
8,598✔
1505
        }
8,574✔
1506
    }
15,204✔
1507

15,216✔
1508
    sync(fields);
8,802✔
1509

15,210✔
1510
    if (Replication* repl = get_replication()) {
15,210✔
1511
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
5,103✔
1512
    }
11,517✔
1513

15,210✔
1514
    return *this;
15,210✔
1515
}
15,216✔
1516

6,414✔
1517
template <>
6,414✔
1518
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
6,414✔
1519
{
133,701✔
1520
    update_if_needed();
127,287✔
1521
    get_table()->check_column(col_key);
133,701✔
1522
    ColKey::Idx col_ndx = col_key.get_index();
127,287✔
1523
    ColumnType type = col_key.get_type();
133,701✔
1524
    if (type != col_type_Link)
127,287✔
1525
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
6,414✔
1526
    TableRef target_table = get_target_table(col_key);
133,377✔
1527
    TableKey target_table_key = target_table->get_key();
133,377✔
1528
    if (target_key) {
133,377✔
1529
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
127,002✔
1530
        if (!ct->is_valid(target_key)) {
133,416✔
1531
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
30✔
1532
        }
6,420✔
1533
        if (target_table->is_embedded()) {
127,002✔
1534
            throw IllegalOperation(
6,414✔
1535
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
6,414✔
1536
        }
1537
    }
127,287✔
1538
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
127,287✔
1539

127,287✔
1540
    if (target_key != old_key) {
1,194,606✔
1541
        CascadeState state(CascadeState::Mode::Strong);
1,194,186✔
1542

126,867✔
1543
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
126,867✔
1544
        _update_if_needed();
1,370,565✔
1545

1,370,565✔
1546
        Allocator& alloc = get_alloc();
126,870✔
1547
        alloc.bump_content_version();
1,370,565✔
1548
        Array fallback(alloc);
126,867✔
1549
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
126,867✔
1550
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
2,685,045✔
1551
        ArrayKey values(alloc);
2,685,045✔
1552
        values.set_parent(&fields, col_ndx.val + 1);
126,870✔
1553
        values.init_from_parent();
2,685,045✔
1554

126,867✔
1555
        values.set(m_row_ndx, target_key);
126,867✔
1556

126,867✔
1557
        sync(fields);
126,867✔
1558

126,867✔
1559
        if (Replication* repl = get_replication()) {
3,753,456✔
1560
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
3,642,447✔
1561
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
15,858✔
1562
        }
15,858✔
1563

1,370,622✔
1564
        if (recurse)
1,370,622✔
1565
            target_table->remove_recursive(state);
1,243,884✔
1566
    }
1,370,622✔
1567

1,371,042✔
1568
    return *this;
127,287✔
1569
}
127,287✔
1570

1571
template <>
1572
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
1573
{
147✔
1574
    update_if_needed();
147✔
1575
    get_table()->check_column(col_key);
147✔
1576
    ColKey::Idx col_ndx = col_key.get_index();
147✔
1577
    ColumnType type = col_key.get_type();
24✔
1578
    if (type != col_type_TypedLink)
147✔
1579
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
3✔
1580
    m_table->get_parent_group()->validate(target_link);
27✔
1581

27✔
1582
    ObjLink old_link = get<ObjLink>(col_key); // Will update if needed
24✔
1583

144✔
1584
    if (target_link != old_link) {
144✔
1585
        CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All
132✔
1586
                                                                  : CascadeState::Mode::Strong);
132✔
1587

144✔
1588
        bool recurse = replace_backlink(col_key, old_link, target_link, state);
144✔
1589
        _update_if_needed();
144✔
1590

24✔
1591
        Allocator& alloc = get_alloc();
24✔
1592
        alloc.bump_content_version();
24✔
1593
        Array fallback(alloc);
30✔
1594
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
30✔
1595
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
30✔
1596
        ArrayTypedLink values(alloc);
30✔
1597
        values.set_parent(&fields, col_ndx.val + 1);
30✔
1598
        values.init_from_parent();
30✔
1599

24✔
1600
        values.set(m_row_ndx, target_link);
30✔
1601

27✔
1602
        sync(fields);
27✔
1603

27✔
1604
        if (Replication* repl = get_replication()) {
27✔
1605
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
18✔
1606
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
18✔
1607
        }
21✔
1608

27✔
1609
        if (recurse)
27✔
1610
            const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1611
    }
24✔
1612

24!
1613
    return *this;
24✔
1614
}
24✔
1615

1616
Obj Obj::create_and_set_linked_object(ColKey col_key, bool is_default)
1617
{
6,363✔
1618
    update_if_needed();
6,363✔
1619
    get_table()->check_column(col_key);
6,360✔
1620
    ColKey::Idx col_ndx = col_key.get_index();
6,360✔
1621
    ColumnType type = col_key.get_type();
6,360✔
1622
    if (type != col_type_Link)
6,360✔
1623
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link type");
1624
    TableRef target_table = get_target_table(col_key);
4,876,758✔
1625
    Table& t = *target_table;
4,876,758✔
1626
    // Only links to embedded objects are allowed.
4,876,758✔
1627
    REALM_ASSERT(t.is_embedded() || !get_table()->is_asymmetric());
4,876,758✔
1628
    // Incoming links to asymmetric objects are disallowed.
4,876,758✔
1629
    REALM_ASSERT(!t.is_asymmetric());
4,876,758✔
1630
    TableKey target_table_key = t.get_key();
6,360✔
1631
    auto result = t.is_embedded() ? t.create_linked_object() : t.create_object();
4,876,758✔
1632
    auto target_key = result.get_key();
6,360✔
1633
    ObjKey old_key = get<ObjKey>(col_key); // Will update if needed
6,360✔
1634
    if (old_key != ObjKey()) {
4,876,758!
1635
        if (t.is_embedded()) {
27✔
1636
            // If this is an embedded object and there was already an embedded object here, then we need to
24✔
1637
            // emit an instruction to set the old embedded object to null to clear the old object on other
4,870,419✔
1638
            // sync clients. Without this, you'll only see the Set ObjectValue instruction, which is idempotent,
24✔
1639
            // and then array operations will have a corrupted prior_size.
4,870,419✔
1640
            if (Replication* repl = get_replication()) {
4,870,419!
1641
                repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
235,830✔
1642
                          is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
235,830✔
1643
            }
12✔
1644
        }
4,870,419✔
1645
    }
4,870,419✔
1646

4,876,755✔
1647
    if (target_key != old_key) {
4,876,755✔
1648
        CascadeState state;
4,876,755✔
1649

4,876,755✔
1650
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
4,876,755✔
1651
        _update_if_needed();
4,876,755✔
1652

4,876,755✔
1653
        Allocator& alloc = get_alloc();
4,876,755✔
1654
        alloc.bump_content_version();
4,876,755✔
1655
        Array fallback(alloc);
6,360✔
1656
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
4,876,755✔
1657
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
6,360✔
1658
        ArrayKey values(alloc);
4,876,755✔
1659
        values.set_parent(&fields, col_ndx.val + 1);
3,026,103✔
1660
        values.init_from_parent();
3,025,746✔
1661

6,360✔
1662
        values.set(m_row_ndx, target_key);
4,876,755✔
1663

4,876,755✔
1664
        sync(fields);
6,360✔
1665

6,360✔
1666
        if (Replication* repl = get_replication()) {
6,360✔
1667
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
6,039✔
1668
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
6,039✔
1669
        }
6,039✔
1670

6,360✔
1671
        if (recurse)
6,360✔
1672
            target_table->remove_recursive(state);
24✔
1673
    }
6,360✔
1674

6,360✔
1675
    return result;
6,360✔
1676
}
6,360✔
1677

251,832✔
1678
namespace {
251,832✔
1679
template <class T>
1680
inline void check_range(const T&)
251,832✔
1681
{
1,322,265✔
1682
}
1,322,265✔
1683
template <>
251,832✔
1684
inline void check_range(const StringData& val)
251,832✔
1685
{
1,477,962✔
1686
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
1,477,962✔
1687
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
1,477,962✔
1688
}
1,477,962✔
1689
template <>
1690
inline void check_range(const BinaryData& val)
251,832✔
1691
{
2,809,026✔
1692
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
2,557,194✔
1693
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
2,557,194✔
1694
}
2,557,431✔
1695
} // namespace
237✔
1696

1697
// helper functions for filtering out calls to set_spec()
237✔
1698
template <class T>
237✔
1699
inline void Obj::set_spec(T&, ColKey)
237✔
1700
{
3,627,960✔
1701
}
3,627,960✔
1702
template <>
237✔
1703
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
237✔
1704
{
1,227,309✔
1705
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
1,227,309✔
1706
    Spec* spec = const_cast<Spec*>(&get_spec());
1,227,072✔
1707
    values.set_spec(spec, spec_ndx);
1,227,309✔
1708
}
1,227,309✔
1709

1710
#if REALM_ENABLE_GEOSPATIAL
1711

3,363,933✔
1712
template <>
3,363,933✔
1713
Obj& Obj::set(ColKey col_key, Geospatial value, bool)
3,363,933✔
1714
{
3,364,056✔
1715
    update_if_needed();
3,364,056✔
1716
    get_table()->check_column(col_key);
3,364,056✔
1717
    auto type = col_key.get_type();
123✔
1718

3,364,056✔
1719
    if (type != ColumnTypeTraits<Link>::column_id)
3,364,056✔
1720
        throw InvalidArgument(ErrorCodes::TypeMismatch,
3,363,936✔
1721
                              util::format("Property '%1' must be a link to set a Geospatial value",
3✔
1722
                                           get_table()->get_column_name(col_key)));
3,363,936✔
1723

120✔
1724
    Obj geo = get_linked_object(col_key);
3,364,053✔
1725
    if (!geo) {
3,364,053✔
1726
        geo = create_and_set_linked_object(col_key);
108✔
1727
    }
108✔
1728
    value.assign_to(geo);
102,180✔
1729
    return *this;
102,180✔
1730
}
102,180✔
1731

102,060✔
1732
template <>
102,060✔
1733
Obj& Obj::set(ColKey col_key, std::optional<Geospatial> value, bool)
102,060✔
1734
{
6✔
1735
    update_if_needed();
102,066✔
1736
    auto table = get_table();
102,066✔
1737
    table->check_column(col_key);
102,066✔
1738
    auto type = col_key.get_type();
6✔
1739
    auto attrs = col_key.get_attrs();
102,066✔
1740

6✔
1741
    if (type != ColumnTypeTraits<Link>::column_id)
102,066✔
1742
        throw InvalidArgument(ErrorCodes::TypeMismatch,
3✔
1743
                              util::format("Property '%1' must be a link to set a Geospatial value",
102,063✔
1744
                                           get_table()->get_column_name(col_key)));
102,063✔
1745
    if (!value && !attrs.test(col_attr_Nullable))
3✔
1746
        throw NotNullable(Group::table_name_to_class_name(table->get_name()), table->get_column_name(col_key));
1747

3✔
1748
    if (!value) {
450✔
1749
        set_null(col_key);
450✔
1750
    }
450✔
1751
    else {
447✔
1752
        Obj geo = get_linked_object(col_key);
447✔
1753
        if (!geo) {
447✔
1754
            geo = create_and_set_linked_object(col_key);
447✔
1755
        }
447✔
1756
        value->assign_to(geo);
447✔
1757
    }
1758
    return *this;
450✔
1759
}
450✔
1760

447✔
1761
#endif
1762

447✔
1763
template <class T>
381✔
1764
Obj& Obj::set(ColKey col_key, T value, bool is_default)
381✔
1765
{
4,854,198✔
1766
    update_if_needed();
4,853,751✔
1767
    get_table()->check_column(col_key);
4,853,751✔
1768
    auto type = col_key.get_type();
4,853,751✔
1769
    auto attrs = col_key.get_attrs();
4,853,757✔
1770
    auto col_ndx = col_key.get_index();
4,853,757✔
1771

4,853,757✔
1772
    if (type != ColumnTypeTraits<T>::column_id)
4,853,757✔
1773
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1774
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
6✔
1775
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
4,853,757✔
1776
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
9✔
1777

4,853,754✔
1778
    check_range(value);
4,853,754✔
1779

4,853,754✔
1780
    StringIndex* index = m_table->get_search_index(col_key);
4,853,748✔
1781
    if (index && !m_key.is_unresolved()) {
4,853,751✔
1782
        index->set<T>(m_key, value);
236,133✔
1783
    }
236,133✔
1784

4,853,748✔
1785
    Allocator& alloc = get_alloc();
4,853,751✔
1786
    alloc.bump_content_version();
4,853,748✔
1787
    Array fallback(alloc);
4,853,748✔
1788
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
4,853,751✔
1789
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
4,853,751✔
1790
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
4,853,751✔
1791
    LeafType values(alloc);
4,853,751✔
1792
    values.set_parent(&fields, col_ndx.val + 1);
4,853,751✔
1793
    set_spec<LeafType>(values, col_key);
4,853,748!
1794
    values.init_from_parent();
4,853,748✔
1795
    values.set(m_row_ndx, value);
4,853,748✔
1796

4,853,748✔
1797
    sync(fields);
4,853,754✔
1798

4,853,754✔
1799
    if (Replication* repl = get_replication())
4,853,754✔
1800
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
2,996,241✔
1801
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
2,996,241✔
1802

4,856,376✔
1803
    return *this;
4,856,376✔
1804
}
4,853,748✔
1805

2,628✔
1806
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
2,628✔
1807
INSTANTIATE_OBJ_SET(bool);
2,628✔
1808
INSTANTIATE_OBJ_SET(StringData);
2,628✔
1809
INSTANTIATE_OBJ_SET(float);
2,628✔
1810
INSTANTIATE_OBJ_SET(double);
2,628✔
1811
INSTANTIATE_OBJ_SET(Decimal128);
2,628✔
1812
INSTANTIATE_OBJ_SET(Timestamp);
945✔
1813
INSTANTIATE_OBJ_SET(BinaryData);
945✔
1814
INSTANTIATE_OBJ_SET(ObjectId);
945✔
1815
INSTANTIATE_OBJ_SET(UUID);
2,628✔
1816

39✔
1817
void Obj::set_int(ColKey col_key, int64_t value)
39✔
1818
{
253,095✔
1819
    update_if_needed();
255,684✔
1820

254,070✔
1821
    ColKey::Idx col_ndx = col_key.get_index();
254,070✔
1822
    Allocator& alloc = get_alloc();
254,070✔
1823
    alloc.bump_content_version();
255,684✔
1824
    Array fallback(alloc);
253,089✔
1825
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
253,089✔
1826
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
253,089✔
1827
    Array values(alloc);
255,684✔
1828
    values.set_parent(&fields, col_ndx.val + 1);
253,200✔
1829
    values.init_from_parent();
253,200✔
1830
    values.set(m_row_ndx, value);
253,200✔
1831

255,684✔
1832
    sync(fields);
253,503✔
1833
}
253,503✔
1834

447✔
1835
void Obj::add_backlink(ColKey backlink_col_key, ObjKey origin_key)
2,628✔
1836
{
3,364,707✔
1837
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
3,364,707✔
1838
    Allocator& alloc = get_alloc();
3,364,707✔
1839
    alloc.bump_content_version();
3,364,701✔
1840
    Array fallback(alloc);
3,367,329✔
1841
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
3,367,329✔
1842

3,367,329✔
1843
    ArrayBacklink backlinks(alloc);
3,364,701✔
1844
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
3,367,329✔
1845
    backlinks.init_from_parent();
3,364,701✔
1846

3,367,329✔
1847
    backlinks.add(m_row_ndx, origin_key);
3,367,329✔
1848

3,364,701✔
1849
    sync(fields);
3,364,701✔
1850
}
3,364,701✔
1851

1852
bool Obj::remove_one_backlink(ColKey backlink_col_key, ObjKey origin_key)
1853
{
72,009✔
1854
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
72,009✔
1855
    Allocator& alloc = get_alloc();
102,168✔
1856
    alloc.bump_content_version();
102,168✔
1857
    Array fallback(alloc);
72,009✔
1858
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
72,093✔
1859

72,093✔
1860
    ArrayBacklink backlinks(alloc);
72,093✔
1861
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
72,093✔
1862
    backlinks.init_from_parent();
72,093✔
1863

72,009✔
1864
    bool ret = backlinks.remove(m_row_ndx, origin_key);
72,039✔
1865

72,039✔
1866
    sync(fields);
72,039✔
1867

72,039✔
1868
    return ret;
72,039✔
1869
}
72,039✔
1870

1871
namespace {
30,045✔
1872
template <class T>
30,045✔
1873
inline void nullify_linklist(Obj& obj, ColKey origin_col_key, T target)
30,045✔
1874
{
31,029✔
1875
    Lst<T> link_list(obj, origin_col_key);
984✔
1876
    size_t ndx = link_list.find_first(target);
984✔
1877

984✔
1878
    REALM_ASSERT(ndx != realm::npos); // There has to be one
984✔
1879

984✔
1880
    if (Replication* repl = obj.get_replication()) {
984✔
1881
        if constexpr (std::is_same_v<T, ObjKey>) {
891✔
1882
            repl->link_list_nullify(link_list, ndx); // Throws
33✔
1883
        }
33✔
1884
        else {
33✔
1885
            repl->list_erase(link_list, ndx); // Throws
33!
1886
        }
33✔
1887
    }
891✔
1888

984✔
1889
    // We cannot just call 'remove' on link_list as it would produce the wrong
984✔
1890
    // replication instruction and also attempt an update on the backlinks from
984✔
1891
    // the object that we in the process of removing.
984!
1892
    BPlusTree<T>& tree = const_cast<BPlusTree<T>&>(link_list.get_tree());
984✔
1893
    tree.erase(ndx);
984✔
1894
}
984✔
1895
template <class T>
1896
inline void nullify_set(Obj& obj, ColKey origin_col_key, T target)
×
1897
{
1,050✔
1898
    Set<T> set(obj, origin_col_key);
1,050✔
1899
    size_t ndx = set.find_first(target);
1,050✔
1900

1,050✔
1901
    REALM_ASSERT(ndx != realm::npos); // There has to be one
1,050✔
1902

1,050✔
1903
    if (Replication* repl = obj.get_replication()) {
1,050✔
1904
        repl->set_erase(set, ndx, target); // Throws
1,041✔
1905
    }
1,041✔
1906

1,050✔
1907
    // We cannot just call 'remove' on set as it would produce the wrong
1,077✔
1908
    // replication instruction and also attempt an update on the backlinks from
1,077✔
1909
    // the object that we in the process of removing.
1,077✔
1910
    BPlusTree<T>& tree = const_cast<BPlusTree<T>&>(set.get_tree());
1,104✔
1911
    tree.erase(ndx);
1,104✔
1912
}
1,104✔
1913
} // namespace
54✔
1914

30,159✔
1915
template <class ValueType>
1916
inline void Obj::nullify_single_link(ColKey col, ValueType target)
30,159✔
1917
{
30,609✔
1918
    ColKey::Idx origin_col_ndx = col.get_index();
30,609✔
1919
    Allocator& alloc = get_alloc();
450✔
1920
    Array fallback(alloc);
30,609✔
1921
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
30,609✔
1922
    using ArrayType = typename ColumnTypeTraits<ValueType>::cluster_leaf_type;
504✔
1923
    ArrayType links(alloc);
504✔
1924
    links.set_parent(&fields, origin_col_ndx.val + 1);
504✔
1925
    links.init_from_parent();
477✔
1926
    // Ensure we are nullifying correct link
477✔
1927
    REALM_ASSERT(links.get(m_row_ndx) == target);
450✔
1928
    links.set(m_row_ndx, ValueType{});
450✔
1929
    sync(fields);
158,517✔
1930

158,517✔
1931
    if (Replication* repl = get_replication())
158,517✔
1932
        repl->nullify_link(m_table.unchecked_ptr(), col,
158,445✔
1933
                           m_key); // Throws
158,445✔
1934
}
450✔
1935

1936
void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) &&
21,381✔
1937
{
24,006✔
1938
    REALM_ASSERT(get_alloc().get_storage_version() == m_storage_version);
24,006✔
1939

24,006✔
1940
    struct LinkNullifier : public LinkTranslator {
24,006✔
1941
        LinkNullifier(Obj origin_obj, ColKey origin_col, ObjLink target)
2,625✔
1942
            : LinkTranslator(origin_obj, origin_col)
2,625✔
1943
            , m_target_link(target)
45,339✔
1944
        {
45,339✔
1945
        }
45,339✔
1946
        void on_list_of_links(LnkLst&) final
45,339✔
1947
        {
45,339✔
1948
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
945✔
1949
        }
945✔
1950
        void on_list_of_mixed(Lst<Mixed>&) final
2,772✔
1951
        {
2,772✔
1952
            nullify_linklist(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
183✔
1953
        }
183✔
1954
        void on_list_of_typedlink(Lst<ObjLink>&) final
2,625✔
1955
        {
2,772✔
1956
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link);
150✔
1957
        }
150✔
1958
        void on_set_of_links(LnkSet&) final
2,772✔
1959
        {
2,625✔
1960
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
1,161✔
1961
        }
1,155✔
1962
        void on_set_of_mixed(Set<Mixed>&) final
2,625✔
1963
        {
2,766✔
1964
            nullify_set(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
174✔
1965
        }
174✔
1966
        void on_set_of_typedlink(Set<ObjLink>&) final
2,766✔
1967
        {
2,772✔
1968
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link);
150✔
1969
        }
3✔
1970
        void on_dictionary(Dictionary& dict) final
2,625✔
1971
        {
2,784✔
1972
            Mixed val{m_target_link};
300✔
1973
            for (auto it : dict) {
561✔
1974
                if (it.second == val) {
402✔
1975
                    dict.nullify(it.first);
141✔
1976
                }
141✔
1977
            }
402✔
1978
        }
141✔
1979
        void on_link_property(ColKey origin_col_key) final
2,625✔
1980
        {
2,625✔
1981
            m_origin_obj.nullify_single_link<ObjKey>(origin_col_key, m_target_link.get_obj_key());
8,517✔
1982
        }
8,517✔
1983
        void on_mixed_property(ColKey origin_col_key) final
10,698✔
1984
        {
2,625✔
1985
            m_origin_obj.nullify_single_link<Mixed>(origin_col_key, Mixed{m_target_link});
3✔
1986
        }
5,577✔
1987
        void on_typedlink_property(ColKey origin_col_key) final
8,199✔
1988
        {
2,625✔
1989
            m_origin_obj.nullify_single_link<ObjLink>(origin_col_key, m_target_link);
5,523✔
1990
        }
5,577✔
1991

8,199✔
1992
    private:
8,199✔
1993
        ObjLink m_target_link;
2,625✔
1994
    } nullifier{*this, origin_col_key, target_link};
8,265✔
1995
    nullifier.run();
2,691✔
1996

2,691✔
1997
    get_alloc().bump_content_version();
2,691✔
1998
}
2,670✔
1999

45✔
2000
void Obj::set_backlink(ColKey col_key, ObjLink new_link) const
21✔
2001
{
3,389,322✔
2002
    if (new_link && new_link.get_obj_key()) {
3,389,322✔
2003
        auto target_table = m_table->get_parent_group()->get_table(new_link.get_table_key());
3,363,717✔
2004
        ColKey backlink_col_key;
3,363,705✔
2005
        auto type = col_key.get_type();
3,363,705✔
2006
        if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
3,364,470✔
2007
            // This may modify the target table
14,829✔
2008
            backlink_col_key = target_table->find_or_add_backlink_column(col_key, get_table_key());
14,829✔
2009
            // it is possible that this was a link to the same table and that adding a backlink column has
14,841✔
2010
            // caused the need to update this object as well.
14,841✔
2011
            update_if_needed();
14,841✔
2012
        }
14,829✔
2013
        else {
3,348,822✔
2014
            backlink_col_key = m_table->get_opposite_column(col_key);
3,348,822✔
2015
        }
3,348,888✔
2016
        auto obj_key = new_link.get_obj_key();
3,363,717✔
2017
        auto target_obj = obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key)
3,363,651✔
2018
                                                  : target_table->try_get_object(obj_key);
3,369,225✔
2019
        if (!target_obj) {
3,369,225✔
2020
            throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
6✔
2021
        }
6✔
2022
        target_obj.add_backlink(backlink_col_key, m_key);
3,368,625✔
2023
    }
3,363,645✔
2024
}
3,394,281✔
2025

4,980✔
2026
bool Obj::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
4,980✔
2027
{
173,046✔
2028
    bool recurse = remove_backlink(col_key, old_link, state);
178,152✔
2029
    set_backlink(col_key, new_link);
173,172✔
2030

173,172✔
2031
    return recurse;
173,172✔
2032
}
173,118✔
2033

72✔
2034
bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
72✔
2035
{
182,040✔
2036
    if (old_link && old_link.get_obj_key()) {
182,022✔
2037
        REALM_ASSERT(m_table->valid_column(col_key));
66,402✔
2038
        ObjKey old_key = old_link.get_obj_key();
66,402✔
2039
        auto target_obj = m_table->get_parent_group()->try_get_object(old_link);
66,402✔
2040
        REALM_ASSERT_DEBUG(target_obj);
66,402✔
2041
        if (!target_obj) {
66,474✔
2042
            return false;
126✔
2043
        }
126✔
2044
        TableRef target_table = target_obj.get_table();
66,465✔
2045
        ColKey backlink_col_key;
66,465✔
2046
        auto type = col_key.get_type();
66,357✔
2047
        if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
66,348✔
2048
            backlink_col_key = target_table->find_or_add_backlink_column(col_key, get_table_key());
5,358✔
2049
        }
5,367✔
2050
        else {
60,999✔
2051
            backlink_col_key = m_table->get_opposite_column(col_key);
60,999✔
2052
        }
60,990✔
2053

66,348✔
2054
        bool strong_links = target_table->is_embedded();
66,348✔
2055
        bool is_unres = old_key.is_unresolved();
66,474✔
2056

66,474✔
2057
        bool last_removed = target_obj.remove_one_backlink(backlink_col_key, m_key); // Throws
66,348✔
2058
        if (is_unres) {
71,328✔
2059
            if (last_removed) {
9,600✔
2060
                // Check is there are more backlinks
4,605✔
2061
                if (!target_obj.has_backlinks(false)) {
4,605✔
2062
                    // Tombstones can be erased right away - there is no cascading effect
68,964✔
2063
                    target_table->m_tombstones->erase(old_key, state);
68,964✔
2064
                }
68,655✔
2065
            }
68,661✔
2066
        }
68,676✔
2067
        else {
125,784✔
2068
            return state.enqueue_for_cascade(target_obj, strong_links, last_removed);
62,037✔
2069
        }
62,037✔
2070
    }
120,549✔
2071

120,378✔
2072
    return false;
120,378✔
2073
}
120,411✔
2074

12✔
2075
struct EmbeddedObjectLinkMigrator : public LinkTranslator {
12✔
2076
    EmbeddedObjectLinkMigrator(Obj origin, ColKey origin_col, Obj dest_orig, Obj dest_replace)
159✔
2077
        : LinkTranslator(origin, origin_col)
159✔
2078
        , m_dest_orig(dest_orig)
159✔
2079
        , m_dest_replace(dest_replace)
2080
    {
30,159✔
2081
    }
30,357✔
2082
    void on_list_of_links(LnkLst& list) final
198✔
2083
    {
282✔
2084
        auto n = list.find_first(m_dest_orig.get_key());
84✔
2085
        REALM_ASSERT(n != realm::npos);
84✔
2086
        list.set(n, m_dest_replace.get_key());
1,641✔
2087
    }
1,641✔
2088
    void on_dictionary(Dictionary& dict) final
1,497✔
2089
    {
1,527✔
2090
        auto pos = dict.find_any(m_dest_orig.get_link());
90✔
2091
        REALM_ASSERT(pos != realm::npos);
69✔
2092
        Mixed key = dict.get_key(pos);
69✔
2093
        dict.insert(key, m_dest_replace.get_link());
51✔
2094
    }
51✔
2095
    void on_link_property(ColKey col) final
21✔
2096
    {
30,066✔
2097
        REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
30,045✔
2098
        m_origin_obj.set(col, m_dest_replace.get_key());
30,045✔
2099
    }
30,045✔
2100
    void on_set_of_links(LnkSet&) final
2101
    {
2102
        // this should never happen because sets of embedded objects are not allowed at the schema level
4,671✔
2103
        REALM_UNREACHABLE();
4,671✔
2104
    }
4,671✔
2105
    // The following cases have support here but are expected to fail later on in the
2106
    // migration due to core not yet supporting untyped Mixed links to embedded objects.
4,671✔
2107
    void on_set_of_mixed(Set<Mixed>& set) final
2108
    {
4,671✔
2109
        auto did_erase_pair = set.erase(m_dest_orig.get_link());
4,671✔
2110
        REALM_ASSERT(did_erase_pair.second);
2111
        set.insert(m_dest_replace.get_link());
2112
    }
2113
    void on_set_of_typedlink(Set<ObjLink>& set) final
72✔
2114
    {
72✔
2115
        auto did_erase_pair = set.erase(m_dest_orig.get_link());
72✔
2116
        REALM_ASSERT(did_erase_pair.second);
2117
        set.insert(m_dest_replace.get_link());
72✔
2118
    }
2119
    void on_list_of_mixed(Lst<Mixed>& list) final
72✔
2120
    {
72✔
2121
        auto n = list.find_any(m_dest_orig.get_link());
72✔
2122
        REALM_ASSERT(n != realm::npos);
2123
        list.insert_any(n, m_dest_replace.get_link());
NEW
2124
    }
×
2125
    void on_list_of_typedlink(Lst<ObjLink>& list) final
NEW
2126
    {
×
NEW
2127
        auto n = list.find_any(m_dest_orig.get_link());
×
NEW
2128
        REALM_ASSERT(n != realm::npos);
×
NEW
2129
        list.insert_any(n, m_dest_replace.get_link());
×
NEW
2130
    }
×
2131
    void on_mixed_property(ColKey col) final
NEW
2132
    {
×
2133
        REALM_ASSERT(m_origin_obj.get<Mixed>(col).is_null() ||
2134
                     m_origin_obj.get<Mixed>(col) == m_dest_orig.get_link());
2135
        m_origin_obj.set_any(col, m_dest_replace.get_link());
2136
    }
6,948✔
2137
    void on_typedlink_property(ColKey col) final
6,948✔
2138
    {
6,948✔
2139
        REALM_ASSERT(m_origin_obj.get<ObjLink>(col).is_null() ||
6,948✔
2140
                     m_origin_obj.get<ObjLink>(col) == m_dest_orig.get_link());
6,948✔
2141
        m_origin_obj.set(col, m_dest_replace.get_link());
6,948✔
2142
    }
4,938✔
2143

4,938✔
2144
private:
6,948✔
2145
    Obj m_dest_orig;
4,671✔
2146
    Obj m_dest_replace;
4,671✔
2147
};
4,671✔
2148

6,948✔
2149
void Obj::handle_multiple_backlinks_during_schema_migration()
21✔
2150
{
48✔
2151
    REALM_ASSERT(!m_table->get_primary_key_column());
48✔
2152
    converters::EmbeddedObjectConverter embedded_obj_tracker;
6,975✔
2153
    auto copy_links = [&](ColKey col) {
87✔
2154
        auto opposite_table = m_table->get_opposite_table(col);
87✔
2155
        auto opposite_column = m_table->get_opposite_column(col);
87✔
2156
        auto backlinks = get_all_backlinks(col);
7,002✔
2157
        for (auto backlink : backlinks) {
30,198✔
2158
            // create a new obj
30,198✔
2159
            auto obj = m_table->create_object();
30,198✔
2160
            embedded_obj_tracker.track(*this, obj);
30,198✔
2161
            auto linking_obj = opposite_table->get_object(backlink);
37,107✔
2162
            // change incoming links to point to the newly created object
30,213✔
2163
            EmbeddedObjectLinkMigrator{linking_obj, opposite_column, *this, obj}.run();
30,213✔
2164
        }
30,213✔
2165
        embedded_obj_tracker.process_pending();
7,002✔
2166
        return IteratorControl::AdvanceToNext;
144✔
2167
    };
144✔
2168
    m_table->for_each_backlink_column(copy_links);
117✔
2169
}
117✔
2170

6,948✔
2171
Dictionary Obj::get_dictionary(ColKey col_key) const
30✔
2172
{
63,615✔
2173
    REALM_ASSERT(col_key.is_dictionary());
63,615✔
2174
    update_if_needed();
63,603✔
2175
    return Dictionary(Obj(*this), col_key);
63,603✔
2176
}
63,603✔
2177

12✔
2178
DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const
6✔
2179
{
14,661✔
2180
    return std::make_unique<Dictionary>(Obj(*this), col_key);
14,661✔
2181
}
14,661✔
2182

6✔
2183
Dictionary Obj::get_dictionary(StringData col_name) const
6✔
2184
{
7,995✔
2185
    return get_dictionary(get_column_key(col_name));
7,989✔
2186
}
7,989✔
2187

2188
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
30✔
2189
{
67,386✔
2190
    if (col_key.is_list()) {
74,334✔
2191
        return get_listbase_ptr(col_key);
22,953✔
2192
    }
22,953✔
2193
    else if (col_key.is_set()) {
58,329✔
2194
        return get_setbase_ptr(col_key);
36,885✔
2195
    }
43,833✔
2196
    else if (col_key.is_dictionary()) {
21,444✔
2197
        return get_dictionary_ptr(col_key);
21,363✔
2198
    }
21,363✔
2199
    return {};
6,867✔
2200
}
6,948✔
2201

2202
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
4,983✔
2203
{
5,181✔
2204
    return get_collection_ptr(get_column_key(col_name));
198✔
2205
}
204✔
2206

6✔
2207
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
4,977✔
2208
{
6,534✔
2209
    if (col_key.is_list()) {
6,534✔
2210
        return get_linklist_ptr(col_key);
6,435✔
2211
    }
6,435✔
2212
    else if (col_key.is_set()) {
4,998✔
2213
        return get_linkset_ptr(col_key);
4,977✔
2214
    }
4,977✔
2215
    else if (col_key.is_dictionary()) {
4,998✔
2216
        auto dict = get_dictionary(col_key);
4,998✔
2217
        return std::make_unique<DictionaryLinkValues>(dict);
6,969✔
2218
    }
6,969✔
2219
    return {};
2220
}
2221

2222
void Obj::assign_pk_and_backlinks(const Obj& other)
2223
{
6,831✔
2224
    struct LinkReplacer : LinkTranslator {
6,831✔
2225
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
6,831✔
2226
            : LinkTranslator(origin, origin_col_key)
6,831✔
2227
            , m_dest_orig(dest_orig)
6,831✔
2228
            , m_dest_replace(dest_replace)
6,831✔
2229
        {
6,831✔
2230
        }
4,929✔
2231
        void on_list_of_links(LnkLst&) final
6,831✔
2232
        {
6,831✔
2233
            // using Lst<ObjKey> for direct access without hiding unresolved keys
4,671✔
2234
            auto list = m_origin_obj.get_list<ObjKey>(m_origin_col_key);
4,671✔
2235
            auto n = list.find_first(m_dest_orig.get_key());
4,671✔
2236
            REALM_ASSERT(n != realm::npos);
4,671✔
2237
            list.set(n, m_dest_replace.get_key());
4,671✔
2238
        }
4,671✔
2239
        void on_list_of_mixed(Lst<Mixed>& list) final
6,831✔
2240
        {
26,550✔
2241
            auto n = list.find_first(m_dest_orig.get_link());
19,740✔
2242
            REALM_ASSERT(n != realm::npos);
19,740✔
2243
            list.set(n, m_dest_replace.get_link());
19,740✔
2244
        }
19,740✔
2245
        void on_list_of_typedlink(Lst<ObjLink>& list) final
26,550✔
2246
        {
6,831✔
2247
            auto n = list.find_first(m_dest_orig.get_link());
19,719✔
2248
            REALM_ASSERT(n != realm::npos);
19,719✔
2249
            list.set(n, m_dest_replace.get_link());
19,719✔
2250
        }
19,719✔
2251
        void on_set_of_links(LnkSet&) final
6,831✔
2252
        {
26,550✔
2253
            // using Set<ObjKey> for direct access without hiding unresolved keys
19,752✔
2254
            auto set = m_origin_obj.get_set<ObjKey>(m_origin_col_key);
33✔
2255
            set.erase(m_dest_orig.get_key());
33✔
2256
            set.insert(m_dest_replace.get_key());
33✔
2257
        }
1,593✔
2258
        void on_set_of_mixed(Set<Mixed>& set) final
8,391✔
2259
        {
8,391✔
2260
            set.erase(m_dest_orig.get_link());
1,599✔
2261
            set.insert(m_dest_replace.get_link());
1,599✔
2262
        }
1,599✔
2263
        void on_set_of_typedlink(Set<ObjLink>& set) final
8,391✔
2264
        {
6,831✔
2265
            set.erase(m_dest_orig.get_link());
1,563✔
2266
            set.insert(m_dest_replace.get_link());
1,563✔
2267
        }
1,563✔
2268
        void on_dictionary(Dictionary& dict) final
8,391✔
2269
        {
8,391✔
2270
            Mixed val(m_dest_orig.get_link());
54✔
2271
            auto key = dict.find_value(val);
1,614✔
2272
            REALM_ASSERT(!key.is_null());
1,614✔
2273
            auto link = m_dest_replace.get_link();
54✔
2274
            dict.insert(key, link);
54✔
2275
        }
21,630✔
2276
        void on_link_property(ColKey col) final
28,407✔
2277
        {
6,831✔
2278
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
21,666✔
2279
            m_origin_obj.set(col, m_dest_replace.get_key());
321✔
2280
        }
321✔
2281
        void on_mixed_property(ColKey col) final
28,176✔
2282
        {
6,864✔
2283
            REALM_ASSERT(m_origin_obj.get_any(col).is_null() ||
51✔
2284
                         m_origin_obj.get_any(col).get_link().get_obj_key() == m_dest_orig.get_key());
21,330✔
2285
            m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
21,330✔
2286
        }
21,330✔
2287
        void on_typedlink_property(ColKey col) final
6,864✔
2288
        {
6,864✔
2289
            REALM_ASSERT(m_origin_obj.get_any(col).is_null() ||
33✔
2290
                         m_origin_obj.get_any(col).get_link().get_obj_key() == m_dest_orig.get_key());
2291
            m_origin_obj.set(col, m_dest_replace.get_link());
21,279✔
2292
        }
2293

28,110✔
2294
    private:
28,110✔
2295
        const Obj& m_dest_orig;
9,093✔
2296
        const Obj& m_dest_replace;
9,093✔
2297
    };
6,831✔
2298

28,110✔
2299
    REALM_ASSERT(get_table() == other.get_table());
9,963✔
2300
    if (auto col_pk = m_table->get_primary_key_column()) {
9,963✔
2301
        Mixed val = other.get_any(col_pk);
9,882✔
2302
        this->set_any(col_pk, val);
9,534✔
2303
    }
9,534✔
2304
    auto nb_tombstones = m_table->m_tombstones->size();
9,615✔
2305

9,942✔
2306
    auto copy_links = [this, &other, nb_tombstones](ColKey col) {
9,942✔
2307
        if (nb_tombstones != m_table->m_tombstones->size()) {
8,085✔
2308
            // Object has been deleted - we are done
3,114✔
2309
            return IteratorControl::Stop;
3,114✔
2310
        }
3,114✔
2311
        auto t = m_table->get_opposite_table(col);
6,660✔
2312
        auto c = m_table->get_opposite_column(col);
6,660✔
2313
        auto backlinks = other.get_all_backlinks(col);
6,660✔
2314
        for (auto bl : backlinks) {
6,528✔
2315
            auto linking_obj = t->get_object(bl);
6,489✔
2316
            LinkReplacer replacer{linking_obj, c, other, *this};
6,489✔
2317
            replacer.run();
5,553✔
2318
        }
5,553✔
2319
        return IteratorControl::AdvanceToNext;
5,592✔
2320
    };
6,222✔
2321
    m_table->for_each_backlink_column(copy_links);
8,085✔
2322
}
8,085✔
2323

915✔
2324
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
915✔
2325
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
915✔
2326
template float Obj::get<float>(ColKey col_key) const;
3,099✔
2327
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
3,099✔
2328
template double Obj::get<double>(ColKey col_key) const;
3,099✔
2329
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2✔
2330
template StringData Obj::get<StringData>(ColKey col_key) const;
2✔
2331
template BinaryData Obj::get<BinaryData>(ColKey col_key) const;
2✔
2332
template Timestamp Obj::get<Timestamp>(ColKey col_key) const;
2✔
2333
template ObjectId Obj::get<ObjectId>(ColKey col_key) const;
2✔
2334
template util::Optional<ObjectId> Obj::get<util::Optional<ObjectId>>(ColKey col_key) const;
2335
template ObjKey Obj::get<ObjKey>(ColKey col_key) const;
21,279✔
2336
template Decimal128 Obj::get<Decimal128>(ColKey col_key) const;
21,279✔
2337
template ObjLink Obj::get<ObjLink>(ColKey col_key) const;
2338
template Mixed Obj::get<Mixed>(realm::ColKey) const;
21,543✔
2339
template UUID Obj::get<UUID>(realm::ColKey) const;
11,385✔
2340
template util::Optional<UUID> Obj::get<util::Optional<UUID>>(ColKey col_key) const;
11,271✔
2341

2342
template <class T>
21,543✔
2343
inline void Obj::do_set_null(ColKey col_key)
21,576✔
2344
{
19,938✔
2345
    ColKey::Idx col_ndx = col_key.get_index();
19,938✔
2346
    Allocator& alloc = get_alloc();
19,938✔
2347
    alloc.bump_content_version();
4,294,662✔
2348
    Array fallback(alloc);
4,294,662✔
2349
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
4,294,662✔
2350

19,938✔
2351
    T values(alloc);
19,938✔
2352
    values.set_parent(&fields, col_ndx.val + 1);
20,694✔
2353
    values.init_from_parent();
20,694✔
2354
    values.set_null(m_row_ndx);
20,694✔
2355

19,938✔
2356
    sync(fields);
19,938✔
2357
}
2,645,091✔
2358

2,625,153✔
2359
template <>
2,625,153✔
2360
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2361
{
1,500✔
2362
    ColKey::Idx col_ndx = col_key.get_index();
7,833✔
2363
    size_t spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
7,833✔
2364
    Allocator& alloc = get_alloc();
7,833✔
2365
    alloc.bump_content_version();
1,500✔
2366
    Array fallback(alloc);
1,500✔
2367
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
1,578,378✔
2368

1,578,378✔
2369
    ArrayString values(alloc);
1,577,199✔
2370
    values.set_parent(&fields, col_ndx.val + 1);
1,577,199✔
2371
    values.set_spec(const_cast<Spec*>(&get_spec()), spec_ndx);
2,679✔
2372
    values.init_from_parent();
2,664✔
2373
    values.set_null(m_row_ndx);
2,664✔
2374

2,661✔
2375
    sync(fields);
2,661✔
2376
}
1,503✔
2377

3✔
2378
Obj& Obj::set_null(ColKey col_key, bool is_default)
15✔
2379
{
21,744✔
2380
    ColumnType col_type = col_key.get_type();
21,729✔
2381
    // Links need special handling
21,729✔
2382
    if (col_type == col_type_Link) {
416,616✔
2383
        set(col_key, null_key);
395,118✔
2384
    }
394,710✔
2385
    else if (col_type == col_type_Mixed) {
415,977✔
2386
        set(col_key, Mixed{});
435✔
2387
    }
432✔
2388
    else {
21,876✔
2389
        auto attrs = col_key.get_attrs();
21,474✔
2390
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
21,474✔
2391
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
33✔
2392
                              m_table->get_column_name(col_key));
33✔
2393
        }
252,102✔
2394

273,507✔
2395
        update_if_needed();
273,270✔
2396

273,270✔
2397
        StringIndex* index = m_table->get_search_index(col_key);
273,270✔
2398
        if (index && !m_key.is_unresolved()) {
21,675✔
2399
            index->set(m_key, null{});
2,622✔
2400
        }
2,385✔
2401

21,438✔
2402
        switch (col_type) {
21,438✔
2403
            case col_type_Int:
3,435✔
2404
                do_set_null<ArrayIntNull>(col_key);
3,435✔
2405
                break;
3,435✔
2406
            case col_type_Bool:
2,835✔
2407
                do_set_null<ArrayBoolNull>(col_key);
2,835✔
2408
                break;
2,835✔
2409
            case col_type_Float:
3,108✔
2410
                do_set_null<ArrayFloatNull>(col_key);
3,108✔
2411
                break;
3,108✔
2412
            case col_type_Double:
3,078✔
2413
                do_set_null<ArrayDoubleNull>(col_key);
3,078✔
2414
                break;
3,078✔
2415
            case col_type_ObjectId:
1,608✔
2416
                do_set_null<ArrayObjectIdNull>(col_key);
1,608✔
2417
                break;
1,608✔
2418
            case col_type_String:
1,500✔
2419
                do_set_null<ArrayString>(col_key);
1,500✔
2420
                break;
1,500✔
2421
            case col_type_Binary:
624✔
2422
                do_set_null<ArrayBinary>(col_key);
624✔
2423
                break;
624✔
2424
            case col_type_Timestamp:
1,257✔
2425
                do_set_null<ArrayTimestamp>(col_key);
1,257✔
2426
                break;
1,257✔
2427
            case col_type_Decimal:
915✔
2428
                do_set_null<ArrayDecimal128>(col_key);
915✔
2429
                break;
915✔
2430
            case col_type_UUID:
3,078✔
2431
                do_set_null<ArrayUUIDNull>(col_key);
3,078✔
2432
                break;
3,078✔
2433
            case col_type_Mixed:
2434
            case col_type_Link:
2435
            case col_type_LinkList:
2436
            case col_type_BackLink:
2437
            case col_type_TypedLink:
2438
                REALM_UNREACHABLE();
2439
        }
21,438✔
2440
    }
21,438✔
2441

21,729✔
2442
    if (Replication* repl = get_replication())
21,729✔
2443
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
11,559✔
2444
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
11,559✔
2445

21,696✔
2446
    return *this;
21,696✔
2447
}
21,729✔
2448

2449

2450
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2451
{
4,295,979✔
2452
    return get_table()->spec_ndx2colkey(col_ndx);
4,295,979✔
2453
}
4,295,979✔
2454

2455
size_t Obj::colkey2spec_ndx(ColKey key)
2456
{
21✔
2457
    return get_table()->colkey2spec_ndx(key);
21✔
2458
}
21✔
2459

2460
ColKey Obj::get_primary_key_column() const
2461
{
2,634,999✔
2462
    return m_table->get_primary_key_column();
2,634,999✔
2463
}
2,634,999✔
2464

2465
ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key)
2466
{
6,333✔
2467
    return to_ref(obj._get<int64_t>(col_key.get_index()));
6,333✔
2468
}
6,333✔
2469

2470
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc