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

realm / realm-core / thomas.goyne_484

05 Aug 2024 04:20PM UTC coverage: 91.097% (-0.01%) from 91.108%
thomas.goyne_484

Pull #7912

Evergreen

tgoyne
Extract some duplicated code for sync triggers and timers
Pull Request #7912: Extract some duplicated code for sync triggers and timers

102644 of 181486 branches covered (56.56%)

62 of 71 new or added lines in 6 files covered. (87.32%)

87 existing lines in 14 files now uncovered.

216695 of 237872 relevant lines covered (91.1%)

5798402.26 hits per line

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

84.63
/src/realm/index_string.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 <cstdio>
20
#include <iomanip>
21
#include <list>
22

23
#ifdef REALM_DEBUG
24
#include <iostream>
25
#endif
26

27
#include <realm/exceptions.hpp>
28
#include <realm/index_string.hpp>
29
#include <realm/table.hpp>
30
#include <realm/list.hpp>
31
#include <realm/timestamp.hpp>
32
#include <realm/column_integer.hpp>
33
#include <realm/unicode.hpp>
34
#include <realm/tokenizer.hpp>
35

36
using namespace realm;
37
using namespace realm::util;
38

39
namespace {
40

41
void get_child(Array& parent, size_t child_ref_ndx, Array& child) noexcept
42
{
24,950,556✔
43
    ref_type child_ref = parent.get_as_ref(child_ref_ndx);
24,950,556✔
44
    child.init_from_ref(child_ref);
24,950,556✔
45
    child.set_parent(&parent, child_ref_ndx);
24,950,556✔
46
}
24,950,556✔
47

48
// This method reconstructs the string inserted in the search index based on a string
49
// that matches so far and the last key (only works if complete strings are stored in the index)
50
static StringData reconstruct_string(size_t offset, StringIndex::key_type key, StringData new_string)
51
{
1,146✔
52
    if (key == 0)
1,146✔
53
        return StringData();
×
54

55
    size_t rest_len = 4;
1,146✔
56
    char* k = reinterpret_cast<char*>(&key);
1,146✔
57
    if (k[0] == 'X')
1,146✔
58
        rest_len = 3;
234✔
59
    else if (k[1] == 'X')
912✔
60
        rest_len = 2;
384✔
61
    else if (k[2] == 'X')
528✔
62
        rest_len = 1;
228✔
63
    else if (k[3] == 'X')
300✔
64
        rest_len = 0;
48✔
65

66
    REALM_ASSERT(offset + rest_len <= new_string.size());
1,146✔
67

68
    return StringData(new_string.data(), offset + rest_len);
1,146✔
69
}
1,146✔
70

71
template <IndexMethod method>
72
int64_t from_list_full_word(InternalFindResult&, const IntegerColumn&);
73

74
template <>
75
inline int64_t from_list_full_word<index_FindFirst>(InternalFindResult&, const IntegerColumn& key_values)
76
{
×
77
    return key_values.get(0);
×
78
}
×
79

80
template <>
81
inline int64_t from_list_full_word<index_Count>(InternalFindResult&, const IntegerColumn& key_values)
82
{
×
83
    return int64_t(key_values.size());
×
84
}
×
85

86
template <>
87
inline int64_t from_list_full_word<index_FindAll_nocopy>(InternalFindResult& result_ref,
88
                                                         const IntegerColumn& key_values)
89
{
288✔
90
    result_ref.payload = from_ref(key_values.get_ref());
288✔
91
    result_ref.start_ndx = 0;
288✔
92
    result_ref.end_ndx = key_values.size();
288✔
93
    return size_t(FindRes_column);
288✔
94
}
288✔
95

96
} // anonymous namespace
97

98
DataType ClusterColumn::get_data_type() const
99
{
×
100
    const Table* table = m_cluster_tree->get_owning_table();
×
101
    return table->get_column_type(m_column_key);
×
102
}
×
103

104
Mixed ClusterColumn::get_value(ObjKey key) const
105
{
11,889,129✔
106
    const Obj obj{m_cluster_tree->get(key)};
11,889,129✔
107
    return obj.get_any(m_column_key);
11,889,129✔
108
}
11,889,129✔
109

110
Lst<String> ClusterColumn::get_list(ObjKey key) const
111
{
6✔
112
    const Obj obj{m_cluster_tree->get(key)};
6✔
113
    return obj.get_list<String>(m_column_key);
6✔
114
}
6✔
115

116
std::vector<ObjKey> ClusterColumn::get_all_keys() const
117
{
18✔
118
    std::vector<ObjKey> ret;
18✔
119
    ret.reserve(m_cluster_tree->size());
18✔
120
    m_cluster_tree->traverse([&ret](const Cluster* cluster) {
18✔
121
        auto sz = cluster->node_size();
18✔
122
        for (size_t i = 0; i < sz; i++) {
198✔
123
            ret.push_back(cluster->get_real_key(i));
180✔
124
        }
180✔
125

126
        return IteratorControl::AdvanceToNext;
18✔
127
    });
18✔
128
    return ret;
18✔
129
}
18✔
130

131
template <>
132
int64_t IndexArray::from_list<index_FindFirst>(const Mixed& value, InternalFindResult& /* result_ref */,
133
                                               const IntegerColumn& key_values, const ClusterColumn& column) const
134
{
5,187✔
135
    SortedListComparator slc(column);
5,187✔
136

137
    IntegerColumn::const_iterator it_end = key_values.cend();
5,187✔
138
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, key_values);
5,187✔
139

140
    if (lower == it_end)
5,187✔
141
        return null_key.value;
24✔
142

143
    int64_t first_key_value = *lower;
5,163✔
144

145
    Mixed actual = column.get_value(ObjKey(first_key_value));
5,163✔
146
    if (actual != value)
5,163✔
147
        return null_key.value;
×
148

149
    return first_key_value;
5,163✔
150
}
5,163✔
151

152
template <>
153
int64_t IndexArray::from_list<index_Count>(const Mixed& value, InternalFindResult& /* result_ref */,
154
                                           const IntegerColumn& key_values, const ClusterColumn& column) const
155
{
8,172✔
156
    SortedListComparator slc(column);
8,172✔
157

158
    IntegerColumn::const_iterator it_end = key_values.cend();
8,172✔
159
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, key_values);
8,172✔
160
    if (lower == it_end)
8,172✔
161
        return 0;
24✔
162

163
    int64_t first_key_value = *lower;
8,148✔
164

165
    Mixed actual = column.get_value(ObjKey(first_key_value));
8,148✔
166
    if (actual != value)
8,148✔
167
        return 0;
24✔
168

169
    IntegerColumn::const_iterator upper = slc.find_end_of_unsorted(value, key_values, lower);
8,124✔
170
    int64_t cnt = upper - lower;
8,124✔
171

172
    return cnt;
8,124✔
173
}
8,148✔
174

175
template <>
176
int64_t IndexArray::from_list<index_FindAll_nocopy>(const Mixed& value, InternalFindResult& result_ref,
177
                                                    const IntegerColumn& key_values,
178
                                                    const ClusterColumn& column) const
179
{
2,796✔
180
    SortedListComparator slc(column);
2,796✔
181
    IntegerColumn::const_iterator it_end = key_values.cend();
2,796✔
182
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, key_values);
2,796✔
183

184
    if (lower == it_end)
2,796✔
185
        return size_t(FindRes_not_found);
57✔
186

187
    ObjKey first_key = ObjKey(*lower);
2,739✔
188

189
    Mixed actual = column.get_value(ObjKey(first_key));
2,739✔
190
    if (actual != value)
2,739✔
191
        return size_t(FindRes_not_found);
54✔
192

193
    // Optimization: check the last entry before trying upper bound.
194
    IntegerColumn::const_iterator upper = it_end;
2,685✔
195
    --upper;
2,685✔
196
    // Single result if upper matches lower
197
    if (upper == lower) {
2,685✔
198
        result_ref.payload = *lower;
324✔
199
        return size_t(FindRes_single);
324✔
200
    }
324✔
201

202
    // Check string value at upper, if equal return matches in (lower, upper]
203
    ObjKey last_key = ObjKey(*upper);
2,361✔
204
    actual = column.get_value(ObjKey(last_key));
2,361✔
205
    if (actual == value) {
2,361✔
206
        result_ref.payload = from_ref(key_values.get_ref());
1,695✔
207
        result_ref.start_ndx = lower.get_position();
1,695✔
208
        result_ref.end_ndx = upper.get_position() + 1; // one past last match
1,695✔
209
        return size_t(FindRes_column);
1,695✔
210
    }
1,695✔
211

212
    // Last result is not equal, find the upper bound of the range of results.
213
    // Note that we are passing upper which is cend() - 1 here as we already
214
    // checked the last item manually.
215
    upper = slc.find_end_of_unsorted(value, key_values, lower);
666✔
216

217
    result_ref.payload = from_ref(key_values.get_ref());
666✔
218
    result_ref.start_ndx = lower.get_position();
666✔
219
    result_ref.end_ndx = upper.get_position();
666✔
220
    return int64_t(FindRes_column);
666✔
221
}
2,361✔
222

223

224
template <IndexMethod method>
225
int64_t IndexArray::index_string(const Mixed& value, InternalFindResult& result_ref,
226
                                 const ClusterColumn& column) const
227
{
1,995,729✔
228
    // Return`realm::not_found`, or an index to the (any) match
229
    constexpr bool first(method == index_FindFirst);
1,995,729✔
230
    // Return 0, or the number of items that match the specified `value`
231
    constexpr bool get_count(method == index_Count);
1,995,729✔
232
    // Same as `index_FindAll` but does not copy matching rows into `column`
233
    // returns FindRes_not_found if there are no matches
234
    // returns FindRes_single and the row index (literal) in result_ref.payload
235
    // or returns FindRes_column and the reference to a column of duplicates in
236
    // result_ref.result with the results in the bounds start_ndx, and end_ndx
237
    constexpr bool allnocopy(method == index_FindAll_nocopy);
1,995,729✔
238

239
    constexpr int64_t local_not_found = allnocopy ? int64_t(FindRes_not_found) : first ? null_key.value : 0;
1,995,729✔
240

241
    const char* data = m_data;
1,995,729✔
242
    const char* header;
1,995,729✔
243
    uint_least8_t width = m_width;
1,995,729✔
244
    bool is_inner_node = m_is_inner_bptree_node;
1,995,729✔
245
    typedef StringIndex::key_type key_type;
1,995,729✔
246
    size_t stringoffset = 0;
1,995,729✔
247

248
    StringConversionBuffer buffer;
1,995,729✔
249
    StringData index_data = value.get_index_data(buffer);
1,995,729✔
250

251
    // Create 4 byte index key
252
    key_type key = StringIndex::create_key(index_data, stringoffset);
1,995,729✔
253

254
    for (;;) {
3,570,369✔
255
        // Get subnode table
256
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
3,570,369✔
257

258
        // Find the position matching the key
259
        const char* offsets_header = m_alloc.translate(offsets_ref);
3,570,369✔
260
        const char* offsets_data = get_data_from_header(offsets_header);
3,570,369✔
261
        size_t offsets_size = get_size_from_header(offsets_header);
3,570,369✔
262
        size_t pos = ::lower_bound<32>(offsets_data, offsets_size, key); // keys are always 32 bits wide
3,570,369✔
263

264
        // If key is outside range, we know there can be no match
265
        if (pos == offsets_size)
3,570,369✔
266
            return local_not_found;
203,637✔
267

268
        // Get entry under key
269
        size_t pos_refs = pos + 1; // first entry in refs points to offsets
3,366,732✔
270
        uint64_t ref = get_direct(data, width, pos_refs);
3,366,732✔
271

272
        if (is_inner_node) {
3,366,732✔
273
            // Set vars for next iteration
274
            header = m_alloc.translate(to_ref(ref));
123,969✔
275
            data = get_data_from_header(header);
123,969✔
276
            width = get_width_from_header(header);
123,969✔
277
            is_inner_node = get_is_inner_bptree_node_from_header(header);
123,969✔
278
            continue;
123,969✔
279
        }
123,969✔
280

281
        key_type stored_key = key_type(get_direct<32>(offsets_data, pos));
3,242,763✔
282

283
        if (stored_key != key)
3,242,763✔
284
            return local_not_found;
708,942✔
285

286
        // Literal row index (tagged)
287
        if (ref & 1) {
2,533,821✔
288
            int64_t key_value = int64_t(ref >> 1);
1,066,809✔
289

290
            Mixed a = column.full_word() ? reconstruct_string(stringoffset, key, index_data)
1,066,809✔
291
                                         : column.get_value(ObjKey(key_value));
1,066,809✔
292
            if (a == value) {
1,066,809✔
293
                result_ref.payload = key_value;
1,046,013✔
294
                return first ? key_value : get_count ? 1 : FindRes_single;
1,046,013✔
295
            }
1,046,013✔
296
            return local_not_found;
20,796✔
297
        }
1,066,809✔
298

299
        const char* sub_header = m_alloc.translate(ref_type(ref));
1,467,012✔
300
        const bool sub_isindex = get_context_flag_from_header(sub_header);
1,467,012✔
301

302
        // List of row indices with common prefix up to this point, in sorted order.
303
        if (!sub_isindex) {
1,467,012✔
304
            const IntegerColumn sub(m_alloc, ref_type(ref));
16,443✔
305
            if (column.full_word()) {
16,443✔
306
                return from_list_full_word<method>(result_ref, sub);
288✔
307
            }
288✔
308

309
            return from_list<method>(value, result_ref, sub, column);
16,155✔
310
        }
16,443✔
311

312
        // Recurse into sub-index;
313
        header = sub_header;
1,450,569✔
314
        data = get_data_from_header(header);
1,450,569✔
315
        width = get_width_from_header(header);
1,450,569✔
316
        is_inner_node = get_is_inner_bptree_node_from_header(header);
1,450,569✔
317

318
        // Go to next key part of the string. If the offset exceeds the string length, the key will be 0
319
        stringoffset += 4;
1,450,569✔
320

321
        // Update 4 byte index key
322
        key = StringIndex::create_key(index_data, stringoffset);
1,450,569✔
323
    }
1,450,569✔
324
}
1,995,729✔
325

326

327
void IndexArray::from_list_all_ins(StringData upper_value, std::vector<ObjKey>& result, const IntegerColumn& rows,
328
                                   const ClusterColumn& column) const
329
{
1,422✔
330
    // optimization for the most common case, where all the strings under a given subindex are equal
331
    Mixed first = column.get_value(ObjKey(*rows.cbegin()));
1,422✔
332
    Mixed last = column.get_value(ObjKey(*(rows.cend() - 1)));
1,422✔
333
    if (first == last) {
1,422✔
334
        if (!first.is_type(type_String))
102✔
335
            return;
×
336
        auto first_str_upper = case_map(first.get_string(), true);
102✔
337
        if (first_str_upper != upper_value) {
102✔
338
            return;
24✔
339
        }
24✔
340

341
        size_t sz = result.size() + rows.size();
78✔
342
        result.reserve(sz);
78✔
343
        for (IntegerColumn::const_iterator it = rows.cbegin(); it != rows.cend(); ++it) {
246✔
344
            result.push_back(ObjKey(*it));
168✔
345
        }
168✔
346
        return;
78✔
347
    }
102✔
348

349
    // special case for very long strings, where they might have a common prefix and end up in the
350
    // same subindex column, but still not be identical
351
    for (IntegerColumn::const_iterator it = rows.cbegin(); it != rows.cend(); ++it) {
66,792✔
352
        ObjKey key = ObjKey(*it);
65,472✔
353
        Mixed val = column.get_value(key);
65,472✔
354
        if (val.is_type(type_String)) {
65,472✔
355
            auto upper_str = case_map(val.get_string(), true);
65,472✔
356
            if (upper_str == upper_value) {
65,472✔
357
                result.push_back(key);
1,440✔
358
            }
1,440✔
359
        }
65,472✔
360
    }
65,472✔
361

362
    return;
1,320✔
363
}
1,422✔
364

365

366
void IndexArray::from_list_all(const Mixed& value, std::vector<ObjKey>& result, const IntegerColumn& rows,
367
                               const ClusterColumn& column) const
368
{
40,776✔
369
    if (column.full_word()) {
40,776✔
370
        result.reserve(rows.size());
60✔
371
        for (IntegerColumn::const_iterator it = rows.cbegin(); it != rows.cend(); ++it) {
186✔
372
            result.push_back(ObjKey(*it));
126✔
373
        }
126✔
374

375
        return;
60✔
376
    }
60✔
377

378
    SortedListComparator slc(column);
40,716✔
379

380
    IntegerColumn::const_iterator it_end = rows.cend();
40,716✔
381
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, rows);
40,716✔
382
    if (lower == it_end)
40,716✔
383
        return;
×
384

385
    ObjKey key = ObjKey(*lower);
40,716✔
386

387
    Mixed a = column.get_value(key);
40,716✔
388
    if (a != value)
40,716✔
389
        return;
×
390

391
    IntegerColumn::const_iterator upper = slc.find_end_of_unsorted(value, rows, lower);
40,716✔
392

393
    // Copy all matches into result column
394
    size_t sz = result.size() + (upper - lower);
40,716✔
395
    result.reserve(sz);
40,716✔
396
    for (IntegerColumn::const_iterator it = lower; it != upper; ++it) {
778,047✔
397
        result.push_back(ObjKey(*it));
737,331✔
398
    }
737,331✔
399
}
40,716✔
400

401

402
namespace {
403

404
// Helper functions for SearchList (index_string_all_ins) for generating permutations of index keys
405

406
// replicates the 4 least significant bits each times 8
407
// eg: abcd -> aaaaaaaabbbbbbbbccccccccdddddddd
408
uint32_t replicate_4_lsb_x8(uint32_t i)
409
{
3,123,651✔
410
    REALM_ASSERT_DEBUG(i <= 15);
3,123,651✔
411
    i *= 0x204081;
3,123,651✔
412
    i &= 0x1010101;
3,123,651✔
413
    i *= 0xff;
3,123,651✔
414
    return i;
3,123,651✔
415
}
3,123,651✔
416

417
int32_t select_from_mask(int32_t a, int32_t b, int32_t mask)
418
{
3,123,645✔
419
    return a ^ ((a ^ b) & mask);
3,123,645✔
420
}
3,123,645✔
421

422
// Given upper and lower keys: "ABCD" and "abcd", the 4 LSBs in the permutation argument determine the
423
// final key:
424
// Permutation 0  = "ABCD"
425
// Permutation 1  = "ABCd"
426
// Permutation 8  = "aBCD"
427
// Permutation 15 = "abcd"
428
using key_type = StringIndex::key_type;
429
static key_type generate_key(key_type upper, key_type lower, int permutation)
430
{
3,123,651✔
431
    return select_from_mask(upper, lower, replicate_4_lsb_x8(permutation));
3,123,651✔
432
}
3,123,651✔
433

434

435
// Helper structure for IndexArray::index_string_all_ins to generate and keep track of search key permutations,
436
// when traversing the trees.
437
struct SearchList {
438
    struct Item {
439
        const char* header;
440
        size_t string_offset;
441
        key_type key;
442
    };
443

444
    SearchList(const util::Optional<std::string>& upper_value, const util::Optional<std::string>& lower_value)
445
        : m_upper_value(upper_value)
3,291✔
446
        , m_lower_value(lower_value)
3,291✔
447
    {
6,582✔
448
        m_keys_seen.reserve(num_permutations);
6,582✔
449
    }
6,582✔
450

451
    // Add all unique keys for this level to the internal work stack
452
    void add_all_for_level(const char* header, size_t string_offset)
453
    {
195,219✔
454
        m_keys_seen.clear();
195,219✔
455
        const key_type upper_key = StringIndex::create_key(m_upper_value, string_offset);
195,219✔
456
        const key_type lower_key = StringIndex::create_key(m_lower_value, string_offset);
195,219✔
457
        for (int p = 0; p < num_permutations; ++p) {
3,318,885✔
458
            // FIXME: This might still be incorrect due to multi-byte unicode characters (crossing the 4 byte key
459
            // size) being combined incorrectly.
460
            const key_type key = generate_key(upper_key, lower_key, p);
3,123,666✔
461
            const bool new_key = std::find(m_keys_seen.cbegin(), m_keys_seen.cend(), key) == m_keys_seen.cend();
3,123,666✔
462
            if (new_key) {
3,123,666✔
463
                m_keys_seen.push_back(key);
3,059,610✔
464
                add_next(header, string_offset, key);
3,059,610✔
465
            }
3,059,610✔
466
        }
3,123,666✔
467
    }
195,219✔
468

469
    bool empty() const
470
    {
3,065,619✔
471
        return m_items.empty();
3,065,619✔
472
    }
3,065,619✔
473

474
    Item get_next()
475
    {
3,059,547✔
476
        Item item = m_items.back();
3,059,547✔
477
        m_items.pop_back();
3,059,547✔
478
        return item;
3,059,547✔
479
    }
3,059,547✔
480

481
    // Add a single entry to the internal work stack. Used to traverse the inner trees (same key)
482
    void add_next(const char* header, size_t string_offset, key_type key)
483
    {
3,059,586✔
484
        m_items.push_back({header, string_offset, key});
3,059,586✔
485
    }
3,059,586✔
486

487
private:
488
    static constexpr int num_permutations = 1 << sizeof(key_type); // 4 bytes gives up to 16 search keys
489

490
    std::vector<Item> m_items;
491

492
    const util::Optional<std::string> m_upper_value;
493
    const util::Optional<std::string> m_lower_value;
494

495
    std::vector<key_type> m_keys_seen;
496
};
497

498

499
} // namespace
500

501

502
void IndexArray::index_string_all_ins(StringData value, std::vector<ObjKey>& result,
503
                                      const ClusterColumn& column) const
504
{
6,582✔
505
    REALM_ASSERT(!value.is_null());
6,582✔
506

507
    const util::Optional<std::string> upper_value = case_map(value, true);
6,582✔
508
    const util::Optional<std::string> lower_value = case_map(value, false);
6,582✔
509
    SearchList search_list(upper_value, lower_value);
6,582✔
510

511
    const char* top_header = get_header_from_data(m_data);
6,582✔
512
    search_list.add_all_for_level(top_header, 0);
6,582✔
513

514
    while (!search_list.empty()) {
3,066,132✔
515
        SearchList::Item item = search_list.get_next();
3,059,550✔
516

517
        const char* const header = item.header;
3,059,550✔
518
        const size_t string_offset = item.string_offset;
3,059,550✔
519
        const key_type key = item.key;
3,059,550✔
520
        const char* const data = get_data_from_header(header);
3,059,550✔
521
        const uint_least8_t width = get_width_from_header(header);
3,059,550✔
522
        const bool is_inner_node = get_is_inner_bptree_node_from_header(header);
3,059,550✔
523

524
        // Get subnode table
525
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
3,059,550✔
526

527
        // Find the position matching the key
528
        const char* const offsets_header = m_alloc.translate(offsets_ref);
3,059,550✔
529
        const char* const offsets_data = get_data_from_header(offsets_header);
3,059,550✔
530
        const size_t offsets_size = get_size_from_header(offsets_header);
3,059,550✔
531
        const size_t pos = ::lower_bound<32>(offsets_data, offsets_size, key); // keys are always 32 bits wide
3,059,550✔
532

533
        // If key is outside range, we know there can be no match
534
        if (pos == offsets_size)
3,059,550✔
535
            continue;
858✔
536

537
        // Get entry under key
538
        const size_t pos_refs = pos + 1; // first entry in refs points to offsets
3,058,692✔
539
        const uint64_t ref = get_direct(data, width, pos_refs);
3,058,692✔
540

541
        if (is_inner_node) {
3,058,692✔
542
            // Set vars for next iteration
543
            const char* const inner_header = m_alloc.translate(to_ref(ref));
×
544
            search_list.add_next(inner_header, string_offset, key);
×
545
            continue;
×
546
        }
×
547

548
        const key_type stored_key = key_type(get_direct<32>(offsets_data, pos));
3,058,692✔
549

550
        if (stored_key != key)
3,058,692✔
551
            continue;
2,862,447✔
552

553
        // Literal row index (tagged)
554
        if (ref & 1) {
196,245✔
555
            ObjKey k(int64_t(ref >> 1));
5,646✔
556

557
            // The buffer is needed when for when this is an integer index.
558
            StringConversionBuffer buffer;
5,646✔
559
            const StringData str = column.get_value(k).get_index_data(buffer);
5,646✔
560
            const util::Optional<std::string> upper_str = case_map(str, true);
5,646✔
561
            if (upper_str == upper_value) {
5,646✔
562
                result.push_back(k);
5,598✔
563
            }
5,598✔
564
            continue;
5,646✔
565
        }
5,646✔
566

567
        const char* const sub_header = m_alloc.translate(ref_type(ref));
190,599✔
568
        const bool sub_isindex = get_context_flag_from_header(sub_header);
190,599✔
569

570
        // List of row indices with common prefix up to this point, in sorted order.
571
        if (!sub_isindex) {
190,599✔
572
            const IntegerColumn sub(m_alloc, ref_type(ref));
1,422✔
573
            from_list_all_ins(upper_value, result, sub, column);
1,422✔
574
            continue;
1,422✔
575
        }
1,422✔
576

577
        // Recurse into sub-index;
578
        search_list.add_all_for_level(sub_header, string_offset + 4);
189,177✔
579
    }
189,177✔
580

581
    // sort the result and return a std::vector
582
    std::sort(result.begin(), result.end());
6,582✔
583
}
6,582✔
584

585

586
void IndexArray::index_string_all(const Mixed& value, std::vector<ObjKey>& result, const ClusterColumn& column) const
587
{
49,788✔
588
    const char* data = m_data;
49,788✔
589
    const char* header;
49,788✔
590
    uint_least8_t width = m_width;
49,788✔
591
    bool is_inner_node = m_is_inner_bptree_node;
49,788✔
592
    size_t stringoffset = 0;
49,788✔
593

594
    StringConversionBuffer buffer;
49,788✔
595
    StringData index_data = value.get_index_data(buffer);
49,788✔
596
    // Create 4 byte index key
597
    key_type key = StringIndex::create_key(index_data, stringoffset);
49,788✔
598

599
    for (;;) {
80,721✔
600
        // Get subnode table
601
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
80,721✔
602

603
        // Find the position matching the key
604
        const char* offsets_header = m_alloc.translate(offsets_ref);
80,721✔
605
        const char* offsets_data = get_data_from_header(offsets_header);
80,721✔
606
        size_t offsets_size = get_size_from_header(offsets_header);
80,721✔
607
        size_t pos = ::lower_bound<32>(offsets_data, offsets_size, key); // keys are always 32 bits wide
80,721✔
608

609
        // If key is outside range, we know there can be no match
610
        if (pos == offsets_size)
80,721✔
611
            return;
12✔
612

613
        // Get entry under key
614
        size_t pos_refs = pos + 1; // first entry in refs points to offsets
80,709✔
615
        uint64_t ref = get_direct(data, width, pos_refs);
80,709✔
616

617
        if (is_inner_node) {
80,709✔
618
            // Set vars for next iteration
619
            header = m_alloc.translate(ref_type(ref));
6✔
620
            data = get_data_from_header(header);
6✔
621
            width = get_width_from_header(header);
6✔
622
            is_inner_node = get_is_inner_bptree_node_from_header(header);
6✔
623
            continue;
6✔
624
        }
6✔
625

626
        key_type stored_key = key_type(get_direct<32>(offsets_data, pos));
80,703✔
627

628
        if (stored_key != key)
80,703✔
629
            return;
6,126✔
630

631
        // Literal row index (tagged)
632
        if (ref & 1) {
74,577✔
633
            ObjKey k(int64_t(ref >> 1));
2,874✔
634

635
            if (column.full_word() || column.get_value(k) == value) {
2,874✔
636
                result.push_back(k);
2,874✔
637
                return;
2,874✔
638
            }
2,874✔
639
            return;
×
640
        }
2,874✔
641

642
        const char* sub_header = m_alloc.translate(ref_type(ref));
71,703✔
643
        const bool sub_isindex = get_context_flag_from_header(sub_header);
71,703✔
644

645
        // List of row indices with common prefix up to this point, in sorted order.
646
        if (!sub_isindex) {
71,703✔
647
            const IntegerColumn sub(m_alloc, ref_type(ref));
40,776✔
648
            return from_list_all(value, result, sub, column);
40,776✔
649
        }
40,776✔
650

651
        // Recurse into sub-index;
652
        header = sub_header;
30,927✔
653
        data = get_data_from_header(header);
30,927✔
654
        width = get_width_from_header(header);
30,927✔
655
        is_inner_node = get_is_inner_bptree_node_from_header(header);
30,927✔
656

657
        // Go to next key part of the string. If the offset exceeds the string length, the key will be 0
658
        stringoffset += 4;
30,927✔
659

660
        // Update 4 byte index key
661
        key = StringIndex::create_key(index_data, stringoffset);
30,927✔
662
    }
30,927✔
663
}
49,788✔
664

665
static void get_all_keys_below(std::set<int64_t>& result, ref_type ref, Allocator& alloc)
666
{
72✔
667
    const char* sub_header = alloc.translate(ref_type(ref));
72✔
668
    const bool sub_isindex = NodeHeader::get_context_flag_from_header(sub_header);
72✔
669

670
    if (sub_isindex) {
72✔
671
        Array tree(alloc);
36✔
672
        tree.init_from_ref(ref);
36✔
673
        auto sz = tree.size();
36✔
674
        for (size_t n = 1; n < sz; n++) {
90✔
675
            auto rot = tree.get_as_ref_or_tagged(n);
54✔
676
            // Literal row index (tagged)
677
            if (rot.is_tagged()) {
54✔
678
                result.insert(rot.get_as_int());
30✔
679
            }
30✔
680
            else {
24✔
681
                get_all_keys_below(result, rot.get_as_ref(), alloc);
24✔
682
            }
24✔
683
        }
54✔
684
    }
36✔
685
    else {
36✔
686
        IntegerColumn tree(alloc, ref);
36✔
687
        tree.for_all([&result](int64_t i) {
84✔
688
            result.insert(i);
84✔
689
        });
84✔
690
    }
36✔
691
}
72✔
692

693
void IndexArray::_index_string_find_all_prefix(std::set<int64_t>& result, StringData str, const char* header) const
694
{
66✔
695
    size_t stringoffset = 0;
66✔
696

697
    for (;;) {
90✔
698
        const char* data = NodeHeader::get_data_from_header(header);
90✔
699
        uint_least8_t width = get_width_from_header(header);
90✔
700

701
        // Create 4 byte lower and upper key
702
        key_type lower = 0;
90✔
703
        size_t i = 0;
90✔
704
        size_t n = str.size() - stringoffset;
90✔
705
        bool is_at_string_end = (n <= 4);
90✔
706
        if (!is_at_string_end) {
90✔
707
            n = 4;
30✔
708
        }
30✔
709
        while (i < n) {
360✔
710
            lower <<= 8;
270✔
711
            lower += str[stringoffset + i++];
270✔
712
        }
270✔
713
        size_t shift = (4 - i) * 8;
90✔
714
        key_type upper = lower + 1;
90✔
715
        lower <<= shift;
90✔
716
        upper <<= shift;
90✔
717
        upper--;
90✔
718

719
        // Get index array
720
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
90✔
721

722
        // Find the position matching the key
723
        const char* offsets_header = m_alloc.translate(offsets_ref);
90✔
724
        const char* offsets_data = get_data_from_header(offsets_header);
90✔
725
        size_t offsets_size = get_size_from_header(offsets_header);
90✔
726
        size_t pos = ::lower_bound<32>(offsets_data, offsets_size, lower); // keys are always 32 bits wide
90✔
727
        // If key is outside range, we know there can be no match
728
        if (pos == offsets_size)
90✔
729
            return;
×
730

731
        size_t pos_refs = pos + 1; // first entry in refs points to offsets
90✔
732

733
        if (NodeHeader::get_is_inner_bptree_node_from_header(header)) {
90✔
734
            bool done = false;
×
735
            while (!done) {
×
736
                // Recursively call with child node
737
                const char* header = m_alloc.translate(to_ref(get_direct(data, width, pos_refs++)));
×
738
                _index_string_find_all_prefix(result, str, header);
×
739

740
                // Check if current node is past end of key range or last node
741
                auto key = get_direct<32>(offsets_data, pos++);
×
742
                done = key > upper || pos == offsets_size;
×
743
            }
×
744
            return;
×
745
        }
×
746

747
        size_t end = ::upper_bound<32>(offsets_data, offsets_size, upper); // keys are always 32 bits wide
90✔
748

749
        if (pos == end) {
90✔
750
            // No match
751
            return;
18✔
752
        }
18✔
753

754
        if (is_at_string_end) {
72✔
755
            // now get all entries from start to end
756
            for (size_t ndx = pos_refs; ndx <= end; ndx++) {
108✔
757
                uint64_t ref = get_direct(data, width, ndx);
60✔
758
                // Literal row index (tagged)
759
                if (ref & 1) {
60✔
760
                    result.emplace(int64_t(ref >> 1));
12✔
761
                }
12✔
762
                else {
48✔
763
                    get_all_keys_below(result, to_ref(ref), m_alloc);
48✔
764
                }
48✔
765
            }
60✔
766
            return;
48✔
767
        }
48✔
768

769
        // When we are not at end of string then we are comparing against the whole key
770
        // and we can have at most one match
771
        REALM_ASSERT(end == pos + 1);
24✔
772
        header = m_alloc.translate(to_ref(get_direct(data, width, pos_refs)));
24✔
773
        stringoffset += 4;
24✔
774
    }
24✔
775
}
66✔
776

777
ObjKey IndexArray::index_string_find_first(const Mixed& value, const ClusterColumn& column) const
778
{
1,962,045✔
779
    InternalFindResult unused;
1,962,045✔
780
    return ObjKey(index_string<index_FindFirst>(value, unused, column));
1,962,045✔
781
}
1,962,045✔
782

783

784
void IndexArray::index_string_find_all(std::vector<ObjKey>& result, const Mixed& value, const ClusterColumn& column,
785
                                       bool case_insensitive) const
786
{
56,370✔
787
    if (case_insensitive && value.is_type(type_String)) {
56,370✔
788
        index_string_all_ins(value.get_string(), result, column);
6,582✔
789
    }
6,582✔
790
    else {
49,788✔
791
        index_string_all(value, result, column);
49,788✔
792
    }
49,788✔
793
}
56,370✔
794

795
FindRes IndexArray::index_string_find_all_no_copy(const Mixed& value, const ClusterColumn& column,
796
                                                  InternalFindResult& result) const
797
{
20,727✔
798
    return static_cast<FindRes>(index_string<index_FindAll_nocopy>(value, result, column));
20,727✔
799
}
20,727✔
800

801
size_t IndexArray::index_string_count(const Mixed& value, const ClusterColumn& column) const
802
{
12,864✔
803
    InternalFindResult unused;
12,864✔
804
    return to_size_t(index_string<index_Count>(value, unused, column));
12,864✔
805
}
12,864✔
806

807
std::unique_ptr<IndexArray> StringIndex::create_node(Allocator& alloc, bool is_leaf)
808
{
695,547✔
809
    Array::Type type = is_leaf ? Array::type_HasRefs : Array::type_InnerBptreeNode;
695,547✔
810
    std::unique_ptr<IndexArray> top(new IndexArray(alloc)); // Throws
695,547✔
811
    top->create(type);                                      // Throws
695,547✔
812

813
    // Mark that this is part of index
814
    // (as opposed to columns under leaves)
815
    top->set_context_flag(true);
695,547✔
816

817
    // Add subcolumns for leaves
818
    Array values(alloc);
695,547✔
819
    values.create(Array::type_Normal);       // Throws
695,547✔
820
    values.ensure_minimum_width(0x7FFFFFFF); // This ensures 31 bits plus a sign bit
695,547✔
821
    top->add(values.get_ref());              // first entry in refs points to offsets
695,547✔
822

823
    return top;
695,547✔
824
}
695,547✔
825

826
StringIndex::key_type StringIndex::get_last_key() const
827
{
355,887✔
828
    Array offsets(m_array->get_alloc());
355,887✔
829
    get_child(*m_array, 0, offsets);
355,887✔
830
    return key_type(offsets.back());
355,887✔
831
}
355,887✔
832

833

834
void StringIndex::insert_with_offset(ObjKey obj_key, StringData index_data, const Mixed& value, size_t offset)
835
{
11,187,162✔
836
    // Create 4 byte index key
837
    key_type key = create_key(index_data, offset);
11,187,162✔
838
    TreeInsert(obj_key, key, offset, index_data, value); // Throws
11,187,162✔
839
}
11,187,162✔
840

841
void StringIndex::insert_to_existing_list_at_lower(ObjKey key, Mixed value, IntegerColumn& list,
842
                                                   const IntegerColumnIterator& lower)
843
{
2,073,165✔
844
    // At this point there exists duplicates of this value, we need to
845
    // insert value beside it's duplicates so that rows are also sorted
846
    // in ascending order.
847
    IntegerColumn::const_iterator upper = [&]() {
2,073,168✔
848
        if (m_target_column.full_word()) {
2,073,168✔
849
            return list.cend();
354✔
850
        }
354✔
851
        else {
2,072,814✔
852
            SortedListComparator slc(m_target_column);
2,072,814✔
853
            return slc.find_end_of_unsorted(value, list, lower);
2,072,814✔
854
        }
2,072,814✔
855
    }();
2,073,168✔
856
    // find insert position (the list has to be kept in sorted order)
857
    // In most cases the refs will be added to the end. So we test for that
858
    // first to see if we can avoid the binary search for insert position
859
    IntegerColumn::const_iterator last = upper - ptrdiff_t(1);
2,073,165✔
860
    int64_t last_key_value = *last;
2,073,165✔
861
    if (key.value >= last_key_value) {
2,073,165✔
862
        list.insert(upper.get_position(), key.value);
2,038,977✔
863
    }
2,038,977✔
864
    else {
34,188✔
865
        // insert into the group of duplicates, keeping object keys sorted
866
        IntegerColumn::const_iterator inner_lower = std::lower_bound(lower, upper, key.value);
34,188✔
867
        if (*inner_lower != key.value) {
34,200✔
868
            list.insert(inner_lower.get_position(), key.value);
34,158✔
869
        }
34,158✔
870
    }
34,188✔
871
}
2,073,165✔
872

873
void StringIndex::insert_to_existing_list(ObjKey key, Mixed value, IntegerColumn& list)
874
{
1,983✔
875
    SortedListComparator slc(m_target_column);
1,983✔
876
    IntegerColumn::const_iterator it_end = list.cend();
1,983✔
877
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, list);
1,983✔
878

879
    if (lower == it_end) {
1,983✔
880
        // Not found and everything is less, just append it to the end.
881
        list.add(key.value);
1,554✔
882
    }
1,554✔
883
    else {
429✔
884
        ObjKey lower_key = ObjKey(*lower);
429✔
885
        Mixed lower_value = get(lower_key);
429✔
886

887
        if (lower_value != value) {
429✔
888
            list.insert(lower.get_position(), key.value);
429✔
889
        }
429✔
890
        else {
×
891
            // At this point there exists duplicates of this value, we need to
892
            // insert value beside it's duplicates so that rows are also sorted
893
            // in ascending order.
894
            insert_to_existing_list_at_lower(key, value, list, lower);
×
895
        }
×
896
    }
429✔
897
}
1,983✔
898

899

900
void StringIndex::insert_row_list(size_t ref, size_t offset, StringData index_data)
901
{
2,412✔
902
    REALM_ASSERT(!m_array->is_inner_bptree_node()); // only works in leaves
2,412✔
903

904
    // Create 4 byte index key
905
    key_type key = create_key(index_data, offset);
2,412✔
906

907
    // Get subnode table
908
    Allocator& alloc = m_array->get_alloc();
2,412✔
909
    Array values(alloc);
2,412✔
910
    get_child(*m_array, 0, values);
2,412✔
911
    REALM_ASSERT(m_array->size() == values.size() + 1);
2,412✔
912

913
    size_t ins_pos = values.lower_bound_int(key);
2,412✔
914
    if (ins_pos == values.size()) {
2,412✔
915
        // When key is outside current range, we can just add it
916
        values.add(key);
2,412✔
917
        m_array->add(ref);
2,412✔
918
        return;
2,412✔
919
    }
2,412✔
920

921
#ifdef REALM_DEBUG // LCOV_EXCL_START ignore debug code
×
922
    // Since we only use this for moving existing values to new
923
    // subindexes, there should never be an existing match.
924
    key_type k = key_type(values.get(ins_pos));
×
925
    REALM_ASSERT(k != key);
×
926
#endif // LCOV_EXCL_STOP ignore debug code
×
927

928
    // If key is not present we add it at the correct location
929
    values.insert(ins_pos, key);
×
930
    m_array->insert(ins_pos + 1, ref);
×
931
}
×
932

933

934
void StringIndex::TreeInsert(ObjKey obj_key, key_type key, size_t offset, StringData index_data, const Mixed& value)
935
{
11,192,484✔
936
    NodeChange nc = do_insert(obj_key, key, offset, index_data, value);
11,192,484✔
937
    switch (nc.type) {
11,192,484✔
938
        case NodeChange::change_None:
11,185,803✔
939
            return;
11,185,803✔
940
        case NodeChange::change_InsertBefore: {
✔
941
            StringIndex new_node(inner_node_tag(), m_array->get_alloc());
×
942
            new_node.node_add_key(nc.ref1);
×
943
            new_node.node_add_key(get_ref());
×
944
            m_array->init_from_ref(new_node.get_ref());
×
945
            m_array->update_parent();
×
946
            return;
×
947
        }
×
948
        case NodeChange::change_InsertAfter: {
6✔
949
            StringIndex new_node(inner_node_tag(), m_array->get_alloc());
6✔
950
            new_node.node_add_key(get_ref());
6✔
951
            new_node.node_add_key(nc.ref1);
6✔
952
            m_array->init_from_ref(new_node.get_ref());
6✔
953
            m_array->update_parent();
6✔
954
            return;
6✔
955
        }
×
956
        case NodeChange::change_Split: {
147✔
957
            StringIndex new_node(inner_node_tag(), m_array->get_alloc());
147✔
958
            new_node.node_add_key(nc.ref1);
147✔
959
            new_node.node_add_key(nc.ref2);
147✔
960
            m_array->init_from_ref(new_node.get_ref());
147✔
961
            m_array->update_parent();
147✔
962
            return;
147✔
963
        }
×
964
    }
11,192,484✔
965
    REALM_ASSERT(false); // LCOV_EXCL_LINE; internal Realm error
966
}
×
967

968

969
StringIndex::NodeChange StringIndex::do_insert(ObjKey obj_key, key_type key, size_t offset, StringData index_data,
970
                                               const Mixed& value)
971
{
11,484,333✔
972
    Allocator& alloc = m_array->get_alloc();
11,484,333✔
973
    if (m_array->is_inner_bptree_node()) {
11,484,333✔
974
        // Get subnode table
975
        Array keys(alloc);
290,667✔
976
        get_child(*m_array, 0, keys);
290,667✔
977
        REALM_ASSERT(m_array->size() == keys.size() + 1);
290,667✔
978

979
        // Find the subnode containing the item
980
        size_t node_ndx = keys.lower_bound_int(key);
290,667✔
981
        if (node_ndx == keys.size()) {
290,667✔
982
            // node can never be empty, so try to fit in last item
983
            node_ndx = keys.size() - 1;
18,879✔
984
        }
18,879✔
985

986
        // Get sublist
987
        size_t refs_ndx = node_ndx + 1; // first entry in refs points to offsets
290,667✔
988
        ref_type ref = m_array->get_as_ref(refs_ndx);
290,667✔
989
        StringIndex target(ref, m_array.get(), refs_ndx, m_target_column, alloc);
290,667✔
990

991
        // Insert item
992
        NodeChange nc = target.do_insert(obj_key, key, offset, index_data, value);
290,667✔
993
        if (nc.type == NodeChange::change_None) {
290,667✔
994
            // update keys
995
            key_type last_key = target.get_last_key();
290,193✔
996
            keys.set(node_ndx, last_key);
290,193✔
997
            return NodeChange::change_None; // no new nodes
290,193✔
998
        }
290,193✔
999

1000
        if (nc.type == NodeChange::change_InsertAfter) {
474✔
1001
            ++node_ndx;
18✔
1002
            ++refs_ndx;
18✔
1003
        }
18✔
1004

1005
        // If there is room, just update node directly
1006
        if (keys.size() < REALM_MAX_BPNODE_SIZE) {
474✔
1007
            if (nc.type == NodeChange::change_Split) {
474✔
1008
                node_insert_split(node_ndx, nc.ref2);
456✔
1009
            }
456✔
1010
            else {
18✔
1011
                node_insert(node_ndx, nc.ref1); // ::INSERT_BEFORE/AFTER
18✔
1012
            }
18✔
1013
            return NodeChange::change_None;
474✔
1014
        }
474✔
1015

1016
        // Else create new node
UNCOV
1017
        StringIndex new_node(inner_node_tag(), alloc);
×
UNCOV
1018
        if (nc.type == NodeChange::change_Split) {
×
1019
            // update offset for left node
1020
            key_type last_key = target.get_last_key();
×
1021
            keys.set(node_ndx, last_key);
×
1022

1023
            new_node.node_add_key(nc.ref2);
×
1024
            ++node_ndx;
×
1025
            ++refs_ndx;
×
1026
        }
×
UNCOV
1027
        else {
×
UNCOV
1028
            new_node.node_add_key(nc.ref1);
×
UNCOV
1029
        }
×
1030

UNCOV
1031
        switch (node_ndx) {
×
1032
            case 0: // insert before
×
1033
                return NodeChange(NodeChange::change_InsertBefore, new_node.get_ref());
×
1034
            case REALM_MAX_BPNODE_SIZE: // insert after
×
1035
                if (nc.type == NodeChange::change_Split)
×
1036
                    return NodeChange(NodeChange::change_Split, get_ref(), new_node.get_ref());
×
1037
                return NodeChange(NodeChange::change_InsertAfter, new_node.get_ref());
×
1038
            default: // split
×
1039
                // Move items after split to new node
1040
                size_t len = m_array->size();
×
1041
                for (size_t i = refs_ndx; i < len; ++i) {
×
1042
                    ref_type ref_i = m_array->get_as_ref(i);
×
1043
                    new_node.node_add_key(ref_i);
×
1044
                }
×
1045
                keys.truncate(node_ndx);
×
1046
                m_array->truncate(refs_ndx);
×
1047
                return NodeChange(NodeChange::change_Split, get_ref(), new_node.get_ref());
×
UNCOV
1048
        }
×
UNCOV
1049
    }
×
1050
    else {
11,193,666✔
1051
        // Is there room in the list?
1052
        Array old_keys(alloc);
11,193,666✔
1053
        get_child(*m_array, 0, old_keys);
11,193,666✔
1054
        const size_t old_offsets_size = old_keys.size();
11,193,666✔
1055
        REALM_ASSERT_EX(m_array->size() == old_offsets_size + 1, m_array->size(), old_offsets_size + 1);
11,193,666✔
1056

1057
        bool noextend = old_offsets_size >= REALM_MAX_BPNODE_SIZE;
11,193,666✔
1058

1059
        // See if we can fit entry into current leaf
1060
        // Works if there is room or it can join existing entries
1061
        if (leaf_insert(obj_key, key, offset, index_data, value, noextend))
11,193,666✔
1062
            return NodeChange::change_None;
11,191,191✔
1063

1064
        // Create new list for item (a leaf)
1065
        StringIndex new_list(m_target_column, alloc);
2,147,487,070✔
1066

1067
        new_list.leaf_insert(obj_key, key, offset, index_data, value);
2,147,487,070✔
1068

1069
        size_t ndx = old_keys.lower_bound_int(key);
2,147,487,070✔
1070

1071
        // insert before
1072
        if (ndx == 0)
2,147,487,070✔
1073
            return NodeChange(NodeChange::change_InsertBefore, new_list.get_ref());
×
1074

1075
        // insert after
1076
        if (ndx == old_offsets_size)
2,147,487,070✔
1077
            return NodeChange(NodeChange::change_InsertAfter, new_list.get_ref());
24✔
1078

1079
        // split
1080
        Array new_keys(alloc);
2,147,487,058✔
1081
        get_child(*new_list.m_array, 0, new_keys);
2,147,487,058✔
1082
        // Move items after split to new list
1083
        for (size_t i = ndx; i < old_offsets_size; ++i) {
2,147,627,023✔
1084
            int64_t v2 = old_keys.get(i);
270,522✔
1085
            int64_t v3 = m_array->get(i + 1);
270,522✔
1086

1087
            new_keys.add(v2);
270,522✔
1088
            new_list.m_array->add(v3);
270,522✔
1089
        }
270,522✔
1090
        old_keys.truncate(ndx);
2,147,487,058✔
1091
        m_array->truncate(ndx + 1);
2,147,487,058✔
1092

1093
        return NodeChange(NodeChange::change_Split, get_ref(), new_list.get_ref());
2,147,487,058✔
1094
    }
2,147,487,070✔
1095
}
11,484,333✔
1096

1097

1098
void StringIndex::node_insert_split(size_t ndx, size_t new_ref)
1099
{
456✔
1100
    REALM_ASSERT(m_array->is_inner_bptree_node());
456✔
1101
    REALM_ASSERT(new_ref);
456✔
1102

1103
    Allocator& alloc = m_array->get_alloc();
456✔
1104
    Array offsets(alloc);
456✔
1105
    get_child(*m_array, 0, offsets);
456✔
1106

1107
    REALM_ASSERT(m_array->size() == offsets.size() + 1);
456✔
1108
    REALM_ASSERT(ndx < offsets.size());
456✔
1109
    REALM_ASSERT(offsets.size() < REALM_MAX_BPNODE_SIZE);
456✔
1110

1111
    // Get sublists
1112
    size_t refs_ndx = ndx + 1; // first entry in refs points to offsets
456✔
1113
    ref_type orig_ref = m_array->get_as_ref(refs_ndx);
456✔
1114
    StringIndex orig_col(orig_ref, m_array.get(), refs_ndx, m_target_column, alloc);
456✔
1115
    StringIndex new_col(new_ref, nullptr, 0, m_target_column, alloc);
456✔
1116

1117
    // Update original key
1118
    key_type last_key = orig_col.get_last_key();
456✔
1119
    offsets.set(ndx, last_key);
456✔
1120

1121
    // Insert new ref
1122
    key_type new_key = new_col.get_last_key();
456✔
1123
    offsets.insert(ndx + 1, new_key);
456✔
1124
    m_array->insert(ndx + 2, new_ref);
456✔
1125
}
456✔
1126

1127

1128
void StringIndex::node_insert(size_t ndx, size_t ref)
1129
{
18✔
1130
    REALM_ASSERT(ref);
18✔
1131
    REALM_ASSERT(m_array->is_inner_bptree_node());
18✔
1132

1133
    Allocator& alloc = m_array->get_alloc();
18✔
1134
    Array offsets(alloc);
18✔
1135
    get_child(*m_array, 0, offsets);
18✔
1136
    REALM_ASSERT(m_array->size() == offsets.size() + 1);
18✔
1137

1138
    REALM_ASSERT(ndx <= offsets.size());
18✔
1139
    REALM_ASSERT(offsets.size() < REALM_MAX_BPNODE_SIZE);
18✔
1140

1141
    StringIndex col(ref, nullptr, 0, m_target_column, alloc);
18✔
1142
    key_type last_key = col.get_last_key();
18✔
1143

1144
    offsets.insert(ndx, last_key);
18✔
1145
    m_array->insert(ndx + 1, ref);
18✔
1146
}
18✔
1147

1148
bool StringIndex::leaf_insert(ObjKey obj_key, key_type key, size_t offset, StringData index_data, const Mixed& value,
1149
                              bool noextend)
1150
{
11,175,798✔
1151
    REALM_ASSERT(!m_array->is_inner_bptree_node());
11,175,798✔
1152
    if (offset >= s_max_offset && m_target_column.full_word()) {
11,175,798✔
1153
        size_t len = value.get_string().size();
6✔
1154
        size_t max = s_max_offset;
6✔
1155
        throw LogicError(ErrorCodes::LimitExceeded,
6✔
1156
                         util::format("String of length %1 exceeds maximum string length of %2.", len, max));
6✔
1157
    }
6✔
1158

1159
    // Get subnode table
1160
    Allocator& alloc = m_array->get_alloc();
11,175,792✔
1161
    Array keys(alloc);
11,175,792✔
1162
    get_child(*m_array, 0, keys);
11,175,792✔
1163
    REALM_ASSERT(m_array->size() == keys.size() + 1);
11,175,792✔
1164

1165
    // If we are keeping the complete string in the index
1166
    // we want to know if this is the last part
1167
    bool is_at_string_end = offset + 4 >= index_data.size();
11,175,792✔
1168

1169
    size_t ins_pos = keys.lower_bound_int(key);
11,175,792✔
1170
    size_t ins_pos_refs = ins_pos + 1; // first entry in refs points to offsets
11,175,792✔
1171

1172
    if (ins_pos == keys.size()) {
11,175,792✔
1173
        if (noextend)
842,526✔
1174
            return false;
24✔
1175

1176
        // When key is outside current range, we can just add it
1177
        keys.add(key);
842,502✔
1178
        if (!m_target_column.full_word() || is_at_string_end) {
842,502✔
1179
            int64_t shifted = int64_t((uint64_t(obj_key.value) << 1) + 1); // shift to indicate literal
481,083✔
1180
            m_array->add(shifted);
481,083✔
1181
        }
481,083✔
1182
        else {
361,419✔
1183
            // create subindex for rest of string
1184
            StringIndex subindex(m_target_column, m_array->get_alloc());
361,419✔
1185
            subindex.insert_with_offset(obj_key, index_data, value, offset + 4);
361,419✔
1186
            m_array->add(subindex.get_ref());
361,419✔
1187
        }
361,419✔
1188
        return true;
842,502✔
1189
    }
842,526✔
1190

1191
    key_type k = key_type(keys.get(ins_pos));
10,333,266✔
1192

1193
    // If key is not present we add it at the correct location
1194
    if (k != key) {
10,333,266✔
1195
        if (noextend)
1,333,608✔
1196
            return false;
603✔
1197

1198
        keys.insert(ins_pos, key);
1,333,005✔
1199
        if (!m_target_column.full_word() || is_at_string_end) {
1,333,005✔
1200
            int64_t shifted = int64_t((uint64_t(obj_key.value) << 1) + 1); // shift to indicate literal
1,151,925✔
1201
            m_array->insert(ins_pos_refs, shifted);
1,151,925✔
1202
        }
1,151,925✔
1203
        else {
181,080✔
1204
            // create subindex for rest of string
1205
            StringIndex subindex(m_target_column, m_array->get_alloc());
181,080✔
1206
            subindex.insert_with_offset(obj_key, index_data, value, offset + 4);
181,080✔
1207
            m_array->insert(ins_pos_refs, subindex.get_ref());
181,080✔
1208
        }
181,080✔
1209
        return true;
1,333,005✔
1210
    }
1,333,608✔
1211

1212
    // This leaf already has a slot for for the key
1213

1214
    uint64_t slot_value = uint64_t(m_array->get(ins_pos_refs));
8,999,658✔
1215
    size_t suboffset = offset + s_index_key_length;
8,999,658✔
1216

1217
    // Single match (lowest bit set indicates literal row_ndx)
1218
    if ((slot_value & 1) != 0) {
8,999,658✔
1219
        ObjKey obj_key2 = ObjKey(int64_t(slot_value >> 1));
68,361✔
1220
        Mixed v2 = m_target_column.full_word() ? reconstruct_string(offset, key, index_data) : get(obj_key2);
68,361✔
1221
        if (v2 == value) {
68,361✔
1222
            if (obj_key.value != obj_key2.value) {
9,738✔
1223
                // Strings are equal but this is not a list.
1224
                // Create a list and add both rows.
1225

1226
                // convert to list (in sorted order)
1227
                Array row_list(alloc);
9,708✔
1228
                row_list.create(Array::type_Normal); // Throws
9,708✔
1229
                row_list.add(obj_key < obj_key2 ? obj_key.value : obj_key2.value);
9,708✔
1230
                row_list.add(obj_key < obj_key2 ? obj_key2.value : obj_key.value);
9,708✔
1231
                m_array->set(ins_pos_refs, row_list.get_ref());
9,708✔
1232
            }
9,708✔
1233
        }
9,738✔
1234
        else {
58,623✔
1235
            StringConversionBuffer buffer;
58,623✔
1236
            auto index_data_2 = v2.get_index_data(buffer);
58,623✔
1237
            if (index_data == index_data_2 || suboffset > s_max_offset) {
58,623✔
1238
                // These strings have the same prefix up to this point but we
1239
                // don't want to recurse further, create a list in sorted order.
1240
                bool row_ndx_first = value < v2;
546✔
1241
                Array row_list(alloc);
546✔
1242
                row_list.create(Array::type_Normal); // Throws
546✔
1243
                row_list.add(row_ndx_first ? obj_key.value : obj_key2.value);
546✔
1244
                row_list.add(row_ndx_first ? obj_key2.value : obj_key.value);
546✔
1245
                m_array->set(ins_pos_refs, row_list.get_ref());
546✔
1246
            }
546✔
1247
            else {
58,077✔
1248
                // These strings have the same prefix up to this point but they
1249
                // are actually not equal. Extend the tree recursivly until the
1250
                // prefix of these strings is different.
1251
                StringIndex subindex(m_target_column, m_array->get_alloc());
58,077✔
1252
                subindex.insert_with_offset(obj_key2, index_data_2, v2, suboffset);
58,077✔
1253
                subindex.insert_with_offset(obj_key, index_data, value, suboffset);
58,077✔
1254
                // Join the string of SubIndices to the current position of m_array
1255
                m_array->set(ins_pos_refs, subindex.get_ref());
58,077✔
1256
            }
58,077✔
1257
        }
58,623✔
1258
        return true;
68,361✔
1259
    }
68,361✔
1260

1261
    // If there already is a list of matches, we see if we fit there
1262
    // or it has to be split into a subindex
1263
    ref_type ref = ref_type(slot_value);
8,931,297✔
1264
    char* header = alloc.translate(ref);
8,931,297✔
1265
    if (!Array::get_context_flag_from_header(header)) {
8,931,297✔
1266
        IntegerColumn sub(alloc, ref); // Throws
2,077,080✔
1267
        sub.set_parent(m_array.get(), ins_pos_refs);
2,077,080✔
1268

1269
        IntegerColumn::const_iterator it_end = sub.cend();
2,077,080✔
1270
        IntegerColumn::const_iterator lower = it_end;
2,077,080✔
1271

1272
        auto value_exists_in_list = [&]() {
2,077,632✔
1273
            if (m_target_column.full_word()) {
2,077,632✔
1274
                lower = sub.cbegin();
360✔
1275
                return reconstruct_string(offset, key, index_data) == value.get_string();
360✔
1276
            }
360✔
1277
            SortedListComparator slc(m_target_column);
2,077,272✔
1278
            lower = slc.find_start_of_unsorted(value, sub);
2,077,272✔
1279

1280
            if (lower != it_end) {
2,077,272✔
1281
                Mixed lower_value = get(ObjKey(*lower));
2,074,047✔
1282
                if (lower_value == value) {
2,074,047✔
1283
                    return true;
2,072,829✔
1284
                }
2,072,829✔
1285
            }
2,074,047✔
1286
            return false;
4,443✔
1287
        };
2,077,272✔
1288

1289
        // If we found the value in this list, add the duplicate to the list.
1290
        if (value_exists_in_list()) {
2,077,080✔
1291
            insert_to_existing_list_at_lower(obj_key, value, sub, lower);
2,073,171✔
1292
        }
2,073,171✔
1293
        else {
3,909✔
1294
            // If the list only stores duplicates we are free to branch and
1295
            // and create a sub index with this existing list as one of the
1296
            // leafs, but if the list doesn't only contain duplicates we
1297
            // must respect that we store a common key prefix up to this
1298
            // point and insert into the existing list.
1299
            ObjKey key_of_any_dup = ObjKey(sub.get(0));
3,909✔
1300
            StringConversionBuffer buffer;
3,909✔
1301
            StringData index_data_2 = m_target_column.full_word() ? reconstruct_string(offset, key, index_data)
3,909✔
1302
                                                                  : get(key_of_any_dup).get_index_data(buffer);
3,909✔
1303
            if (index_data == index_data_2 || suboffset > s_max_offset) {
4,383✔
1304
                insert_to_existing_list(obj_key, value, sub);
1,983✔
1305
            }
1,983✔
1306
            else {
1,926✔
1307
#ifdef REALM_DEBUG
1,926✔
1308
                bool contains_only_duplicates = true;
1,926✔
1309
                if (!m_target_column.full_word() && sub.size() > 1) {
2,406✔
1310
                    ObjKey first_key = ObjKey(sub.get(0));
1,734✔
1311
                    ObjKey last_key = ObjKey(sub.back());
1,734✔
1312
                    auto first = get(first_key);
1,734✔
1313
                    auto last = get(last_key);
1,734✔
1314
                    // Since the list is kept in sorted order, the first and
1315
                    // last values will be the same only if the whole list is
1316
                    // storing duplicate values.
1317
                    if (first != last) {
1,734✔
1318
                        contains_only_duplicates = false; // LCOV_EXCL_LINE
1319
                    }
×
1320
                }
1,734✔
1321
                REALM_ASSERT_DEBUG(contains_only_duplicates);
1,926✔
1322
#endif
1,926✔
1323
                // The buffer is needed for when this is an integer index.
1324
                StringIndex subindex(m_target_column, m_array->get_alloc());
1,926✔
1325
                subindex.insert_row_list(sub.get_ref(), suboffset, index_data_2);
1,926✔
1326
                subindex.insert_with_offset(obj_key, index_data, value, suboffset);
1,926✔
1327
                m_array->set(ins_pos_refs, subindex.get_ref());
1,926✔
1328
            }
1,926✔
1329
        }
3,909✔
1330
        return true;
2,077,080✔
1331
    }
2,077,080✔
1332

1333
    // The key matches, but there is a subindex here so go down a level in the tree.
1334
    StringIndex subindex(ref, m_array.get(), ins_pos_refs, m_target_column, alloc);
6,854,217✔
1335
    subindex.insert_with_offset(obj_key, index_data, value, suboffset);
6,854,217✔
1336

1337
    return true;
6,854,217✔
1338
}
8,931,297✔
1339

1340
Mixed StringIndex::get(ObjKey key) const
1341
{
4,030,227✔
1342
    return m_target_column.get_value(key);
4,030,227✔
1343
}
4,030,227✔
1344

1345
void StringIndex::erase(ObjKey key)
1346
{
1,185,105✔
1347
    StringConversionBuffer buffer;
1,185,105✔
1348
    if (m_target_column.full_word()) {
1,185,105✔
1349
        if (m_target_column.tokenize()) {
78✔
1350
            // This is a full text index
1351
            auto index_data(get(key).get_index_data(buffer));
72✔
1352
            auto words = Tokenizer::get_instance()->reset(std::string_view(index_data)).get_all_tokens();
72✔
1353
            for (auto& w : words) {
2,460✔
1354
                erase_string(key, w);
2,460✔
1355
            }
2,460✔
1356
        }
72✔
1357
        else {
6✔
1358
            // This is a list (of strings)
1359
            erase_list(key, m_target_column.get_list(key));
6✔
1360
        }
6✔
1361
    }
78✔
1362
    else {
1,185,027✔
1363
        erase_string(key, get(key).get_index_data(buffer));
1,185,027✔
1364
    }
1,185,027✔
1365
}
1,185,105✔
1366

1367
void StringIndex::erase_list(ObjKey key, const Lst<String>& list)
1368
{
12✔
1369
    std::vector<StringData> strings;
12✔
1370
    strings.reserve(list.size());
12✔
1371
    for (auto& val : list) {
48✔
1372
        strings.push_back(val);
48✔
1373
    }
48✔
1374

1375
    std::sort(strings.begin(), strings.end());
12✔
1376
    auto last = std::unique(strings.begin(), strings.end());
12✔
1377
    for (auto it = strings.begin(); it != last; ++it) {
48✔
1378
        erase_string(key, *it);
36✔
1379
    }
36✔
1380
}
12✔
1381

1382
namespace {
1383
template <typename T>
1384
void intersect(std::vector<ObjKey>& result, T& keys)
1385
{
294✔
1386
    if (result.empty()) {
294✔
1387
        result.reserve(keys.size());
198✔
1388
        for (auto k : keys) {
498✔
1389
            result.emplace_back(k);
498✔
1390
        }
498✔
1391
    }
198✔
1392
    else {
96✔
1393
        auto it = result.begin();
96✔
1394
        auto keep = it;
96✔
1395
        auto m = keys.begin();
96✔
1396

1397
        // only keep intersection
1398
        while (it != result.end() && m != keys.end()) {
354✔
1399
            int64_t int_val = *m;
258✔
1400
            if (it->value < int_val) {
258✔
1401
                it++; // don't keep if match is not in new set
18✔
1402
            }
18✔
1403
            else if (it->value > int_val) {
240✔
1404
                ++m; // ignore new matches
102✔
1405
            }
102✔
1406
            else {
138✔
1407
                // Found both places - make sure it is kept
1408
                if (keep < it)
138✔
1409
                    *keep = *it;
6✔
1410
                ++keep;
138✔
1411
                ++it;
138✔
1412
                ++m;
138✔
1413
            }
138✔
1414
        }
258✔
1415
        if (keep != result.end()) {
96✔
1416
            result.erase(keep, result.end());
24✔
1417
        }
24✔
1418
    }
96✔
1419
}
294✔
1420

1421
struct FindResWrapper {
1422
    InternalFindResult& res;
1423
    IntegerColumn& indexes;
1424
    size_t n = 0;
1425
    size_t size()
1426
    {
144✔
1427
        return res.end_ndx - res.start_ndx;
144✔
1428
    }
144✔
1429
    auto begin()
1430
    {
228✔
1431
        return indexes.cbegin();
228✔
1432
    }
228✔
1433
    auto end()
1434
    {
384✔
1435
        return indexes.cend();
384✔
1436
    }
384✔
1437
};
1438
} // namespace
1439

1440
void StringIndex::insert_bulk(const ArrayUnsigned* keys, uint64_t key_offset, size_t num_values, ArrayPayload& values)
1441
{
96,687✔
1442
    if (keys) {
96,687✔
1443
        for (size_t i = 0; i < num_values; ++i) {
939✔
1444
            ObjKey key(keys->get(i) + key_offset);
780✔
1445
            insert(key, values.get_any(i));
780✔
1446
        }
780✔
1447
    }
159✔
1448
    else {
96,528✔
1449
        for (size_t i = 0; i < num_values; ++i) {
1,420,446✔
1450
            ObjKey key(i + key_offset);
1,323,918✔
1451
            insert(key, values.get_any(i));
1,323,918✔
1452
        }
1,323,918✔
1453
    }
96,528✔
1454
}
96,687✔
1455

1456
void StringIndex::insert_bulk_list(const ArrayUnsigned* keys, uint64_t key_offset, size_t num_values,
1457
                                   ArrayInteger& ref_array)
1458
{
258✔
1459
    auto get_obj_key = [&](size_t n) {
60,000✔
1460
        if (keys) {
60,000✔
1461
            return ObjKey(keys->get(n) + key_offset);
×
1462
        }
×
1463
        return ObjKey(n + key_offset);
60,000✔
1464
    };
60,000✔
1465
    for (size_t i = 0; i < num_values; ++i) {
60,258✔
1466
        ObjKey key = get_obj_key(i);
60,000✔
1467
        if (auto ref = to_ref(ref_array.get(i))) {
60,000✔
1468
            BPlusTree<String> values(ref_array.get_alloc());
60,000✔
1469
            values.init_from_ref(ref);
60,000✔
1470
            values.for_all([&](const StringData& str) {
180,000✔
1471
                insert(key, str);
180,000✔
1472
            });
180,000✔
1473
        }
60,000✔
1474
    }
60,000✔
1475
}
258✔
1476

1477

1478
void StringIndex::find_all_fulltext(std::vector<ObjKey>& result, StringData value) const
1479
{
378✔
1480
    InternalFindResult res;
378✔
1481
    REALM_ASSERT(result.empty());
378✔
1482

1483
    auto tokenizer = Tokenizer::get_instance();
378✔
1484
    tokenizer->reset({value.data(), value.size()});
378✔
1485
    auto [includes, excludes] = tokenizer->get_search_tokens();
378✔
1486
    if (includes.empty()) {
378✔
1487
        if (excludes.empty()) {
24✔
1488
            throw InvalidArgument("Missing search token");
6✔
1489
        }
6✔
1490
        result = m_target_column.get_all_keys();
18✔
1491
    }
18✔
1492
    else {
354✔
1493
        for (auto& token : includes) {
432✔
1494
            if (token.back() == '*') {
432✔
1495
                std::set<int64_t> keys;
66✔
1496
                m_array->index_string_find_all_prefix(keys, StringData(token.data(), token.size() - 1));
66✔
1497
                intersect(result, keys);
66✔
1498
            }
66✔
1499
            else {
366✔
1500
                switch (find_all_no_copy(StringData{token}, res)) {
366✔
1501
                    case FindRes_not_found:
12✔
1502
                        result.clear();
12✔
1503
                        break;
12✔
1504
                    case FindRes_column: {
228✔
1505
                        IntegerColumn indexes(m_array->get_alloc(), ref_type(res.payload));
228✔
1506
                        FindResWrapper wrapper{res, indexes};
228✔
1507
                        intersect(result, wrapper);
228✔
1508
                        break;
228✔
1509
                    }
×
1510
                    case FindRes_single:
126✔
1511
                        // merge in single res
1512
                        if (result.empty()) {
126✔
1513
                            result.emplace_back(res.payload);
96✔
1514
                        }
96✔
1515
                        else {
30✔
1516
                            ObjKey key(res.payload);
30✔
1517
                            auto pos = std::lower_bound(result.begin(), result.end(), key);
30✔
1518
                            if (pos != result.end() && key == *pos) {
30✔
1519
                                result.clear();
30✔
1520
                                result.push_back(key);
30✔
1521
                            }
30✔
1522
                            else {
×
1523
                                result.clear();
×
1524
                            }
×
1525
                        }
30✔
1526
                        break;
126✔
1527
                }
366✔
1528
            }
366✔
1529
            if (result.empty())
432✔
1530
                return;
36✔
1531
        }
432✔
1532
    }
354✔
1533

1534
    for (auto& token : excludes) {
336✔
1535
        if (token.back() == '*') {
96✔
1536
            throw IllegalOperation("Exclude by prefix is not implemented");
×
1537
        }
×
1538
        if (result.empty())
96✔
1539
            return;
×
1540

1541
        switch (find_all_no_copy(StringData{token}, res)) {
96✔
1542
            case FindRes_not_found:
✔
1543
                // Nothing to exclude
1544
                break;
×
1545
            case FindRes_column: {
60✔
1546
                IntegerColumn indexes(m_array->get_alloc(), ref_type(res.payload));
60✔
1547

1548
                auto it = result.begin();
60✔
1549
                auto keep = it;
60✔
1550
                size_t m = res.start_ndx;
60✔
1551
                auto idx_val = indexes.get(m);
60✔
1552

1553
                while (it != result.end()) {
390✔
1554
                    if (it->value < idx_val) {
330✔
1555
                        // Not found in excludes
1556
                        if (keep < it)
156✔
1557
                            *keep = *it;
156✔
1558
                        ++keep;
156✔
1559
                        ++it;
156✔
1560
                    }
156✔
1561
                    else {
174✔
1562
                        if (it->value == idx_val) {
174✔
1563
                            // found in excludes - don't keep
1564
                            ++it;
150✔
1565
                        }
150✔
1566
                        ++m;
174✔
1567
                        idx_val = m < res.end_ndx ? indexes.get(m) : std::numeric_limits<int64_t>::max();
174✔
1568
                    }
174✔
1569
                }
330✔
1570
                if (keep != result.end()) {
60✔
1571
                    result.erase(keep, result.end());
60✔
1572
                }
60✔
1573
                break;
60✔
1574
            }
×
1575
            case FindRes_single: {
36✔
1576
                // exclude single res
1577
                ObjKey key(res.payload);
36✔
1578
                auto pos = std::lower_bound(result.begin(), result.end(), key);
36✔
1579
                if (pos != result.end() && key == *pos) {
36✔
1580
                    result.erase(pos);
36✔
1581
                }
36✔
1582
                break;
36✔
1583
            }
×
1584
        }
96✔
1585
    }
96✔
1586
}
336✔
1587

1588

1589
void StringIndex::clear()
1590
{
4,671✔
1591
    Array values(m_array->get_alloc());
4,671✔
1592
    get_child(*m_array, 0, values);
4,671✔
1593
    REALM_ASSERT(m_array->size() == values.size() + 1);
4,671✔
1594

1595
    values.clear();
4,671✔
1596
    values.ensure_minimum_width(0x7FFFFFFF); // This ensures 31 bits plus a sign bit
4,671✔
1597

1598
    size_t size = 1;
4,671✔
1599
    m_array->truncate_and_destroy_children(size); // Don't touch `values` array
4,671✔
1600

1601
    m_array->set_type(Array::type_HasRefs);
4,671✔
1602
}
4,671✔
1603

1604

1605
void StringIndex::do_delete(ObjKey obj_key, StringData index_data, size_t offset)
1606
{
1,957,704✔
1607
    Allocator& alloc = m_array->get_alloc();
1,957,704✔
1608
    Array values(alloc);
1,957,704✔
1609
    get_child(*m_array, 0, values);
1,957,704✔
1610
    REALM_ASSERT(m_array->size() == values.size() + 1);
1,957,704✔
1611

1612
    // Create 4 byte index key
1613
    key_type key = create_key(index_data, offset);
1,957,704✔
1614

1615
    const size_t pos = values.lower_bound_int(key);
1,957,704✔
1616
    const size_t pos_refs = pos + 1; // first entry in refs points to offsets
1,957,704✔
1617
    REALM_ASSERT(pos != values.size());
1,957,704✔
1618

1619
    if (m_array->is_inner_bptree_node()) {
1,957,704✔
1620
        ref_type ref = m_array->get_as_ref(pos_refs);
64,875✔
1621
        StringIndex node(ref, m_array.get(), pos_refs, m_target_column, alloc);
64,875✔
1622
        node.do_delete(obj_key, index_data, offset);
64,875✔
1623

1624
        // Update the ref
1625
        if (node.is_empty()) {
64,875✔
1626
            values.erase(pos);
108✔
1627
            m_array->erase(pos_refs);
108✔
1628
            node.destroy();
108✔
1629
        }
108✔
1630
        else {
64,767✔
1631
            key_type max_val = node.get_last_key();
64,767✔
1632
            if (max_val != key_type(values.get(pos)))
64,767✔
1633
                values.set(pos, max_val);
108✔
1634
        }
64,767✔
1635
    }
64,875✔
1636
    else {
1,892,829✔
1637
        uint64_t ref = m_array->get(pos_refs);
1,892,829✔
1638
        if (ref & 1) {
1,892,829✔
1639
            REALM_ASSERT(int64_t(ref >> 1) == obj_key.value);
832,173✔
1640
            values.erase(pos);
832,173✔
1641
            m_array->erase(pos_refs);
832,173✔
1642
        }
832,173✔
1643
        else {
1,060,656✔
1644
            // A real ref either points to a list or a subindex
1645
            char* header = alloc.translate(ref_type(ref));
1,060,656✔
1646
            if (Array::get_context_flag_from_header(header)) {
1,060,656✔
1647
                StringIndex subindex(ref_type(ref), m_array.get(), pos_refs, m_target_column, alloc);
705,675✔
1648
                subindex.do_delete(obj_key, index_data, offset + s_index_key_length);
705,675✔
1649

1650
                if (subindex.is_empty()) {
705,675✔
1651
                    values.erase(pos);
28,329✔
1652
                    m_array->erase(pos_refs);
28,329✔
1653
                    subindex.destroy();
28,329✔
1654
                }
28,329✔
1655
            }
705,675✔
1656
            else {
354,981✔
1657
                IntegerColumn sub(alloc, ref_type(ref)); // Throws
354,981✔
1658
                sub.set_parent(m_array.get(), pos_refs);
354,981✔
1659
                size_t r = sub.find_first(obj_key.value);
354,981✔
1660
                size_t sub_size = sub.size(); // Slow
354,981✔
1661
                REALM_ASSERT_EX(r != sub_size, r, sub_size);
354,981✔
1662
                sub.erase(r);
354,981✔
1663

1664
                if (sub_size == 1) {
354,981✔
1665
                    values.erase(pos);
3,300✔
1666
                    m_array->erase(pos_refs);
3,300✔
1667
                    sub.destroy();
3,300✔
1668
                }
3,300✔
1669
            }
354,981✔
1670
        }
1,060,656✔
1671
    }
1,892,829✔
1672
}
1,957,704✔
1673

1674
void StringIndex::erase_string(ObjKey key, StringData value)
1675
{
1,187,652✔
1676
    do_delete(key, value, 0);
1,187,652✔
1677

1678
    // Collapse top nodes with single item
1679
    while (m_array->is_inner_bptree_node()) {
1,187,658✔
1680
        REALM_ASSERT(m_array->size() > 1); // node cannot be empty
64,875✔
1681
        if (m_array->size() > 2)
64,875✔
1682
            break;
64,869✔
1683

1684
        ref_type ref = m_array->get_as_ref(1);
6✔
1685
        m_array->set(1, 1); // avoid destruction of the extracted ref
6✔
1686
        m_array->destroy_deep();
6✔
1687
        m_array->init_from_ref(ref);
6✔
1688
        m_array->update_parent();
6✔
1689
    }
6✔
1690
}
1,187,652✔
1691

1692
namespace {
1693

1694
bool has_duplicate_values(const Array& node, const ClusterColumn& target_col) noexcept
1695
{
6,366✔
1696
    Allocator& alloc = node.get_alloc();
6,366✔
1697
    Array child(alloc);
6,366✔
1698
    size_t n = node.size();
6,366✔
1699
    REALM_ASSERT(n >= 1);
6,366✔
1700
    if (node.is_inner_bptree_node()) {
6,366✔
1701
        // Inner node
1702
        for (size_t i = 1; i < n; ++i) {
×
1703
            ref_type ref = node.get_as_ref(i);
×
1704
            child.init_from_ref(ref);
×
1705
            if (has_duplicate_values(child, target_col))
×
1706
                return true;
×
1707
        }
×
1708
        return false;
×
1709
    }
×
1710

1711
    // Leaf node
1712
    for (size_t i = 1; i < n; ++i) {
32,850✔
1713
        int_fast64_t value = node.get(i);
28,740✔
1714
        bool is_single_row_index = (value & 1) != 0;
28,740✔
1715
        if (is_single_row_index)
28,740✔
1716
            continue;
23,280✔
1717

1718
        ref_type ref = to_ref(value);
5,460✔
1719
        child.init_from_ref(ref);
5,460✔
1720

1721
        bool is_subindex = child.get_context_flag();
5,460✔
1722
        if (is_subindex) {
5,460✔
1723
            if (has_duplicate_values(child, target_col))
4,872✔
1724
                return true;
1,800✔
1725
            continue;
3,072✔
1726
        }
4,872✔
1727

1728
        // Child is root of B+-tree of row indexes
1729
        IntegerColumn sub(alloc, ref);
588✔
1730
        if (sub.size() > 1) {
588✔
1731
            ObjKey first_key = ObjKey(sub.get(0));
480✔
1732
            ObjKey last_key = ObjKey(sub.back());
480✔
1733
            Mixed first = target_col.get_value(first_key);
480✔
1734
            Mixed last = target_col.get_value(last_key);
480✔
1735
            // Since the list is kept in sorted order, the first and
1736
            // last values will be the same only if the whole list is
1737
            // storing duplicate values.
1738
            if (first == last) {
480✔
1739
                return true;
432✔
1740
            }
432✔
1741
            // There may also be several short lists combined, so we need to
1742
            // check each of these individually for duplicates.
1743
            IntegerColumn::const_iterator it = sub.cbegin();
48✔
1744
            IntegerColumn::const_iterator it_end = sub.cend();
48✔
1745
            SortedListComparator slc(target_col);
48✔
1746
            while (it != it_end) {
144✔
1747
                Mixed it_data = target_col.get_value(ObjKey(*it));
120✔
1748
                IntegerColumn::const_iterator next = slc.find_end_of_unsorted(it_data, sub, it);
120✔
1749
                size_t count_of_value = next - it; // row index subtraction in `sub`
120✔
1750
                if (count_of_value > 1) {
120✔
1751
                    return true;
24✔
1752
                }
24✔
1753
                it = next;
96✔
1754
            }
96✔
1755
        }
48✔
1756
    }
588✔
1757

1758
    return false;
4,110✔
1759
}
6,366✔
1760

1761
} // anonymous namespace
1762

1763

1764
bool StringIndex::has_duplicate_values() const noexcept
1765
{
1,494✔
1766
    return ::has_duplicate_values(*m_array, m_target_column);
1,494✔
1767
}
1,494✔
1768

1769

1770
bool StringIndex::is_empty() const
1771
{
770,703✔
1772
    return m_array->size() == 1; // first entry in refs points to offsets
770,703✔
1773
}
770,703✔
1774

1775
void StringIndex::insert(ObjKey key, const Mixed& value)
1776
{
3,015,168✔
1777
    StringConversionBuffer buffer;
3,015,168✔
1778
    constexpr size_t offset = 0; // First key from beginning of string
3,015,168✔
1779

1780
    if (this->m_target_column.tokenize()) {
3,015,168✔
1781
        if (value.is_type(type_String)) {
198✔
1782
            auto words = Tokenizer::get_instance()->reset(std::string_view(value.get<StringData>())).get_all_tokens();
198✔
1783

1784
            for (auto& word : words) {
936✔
1785
                Mixed m(word);
936✔
1786
                insert_with_offset(key, m.get_index_data(buffer), m, 0); // Throws
936✔
1787
            }
936✔
1788
        }
198✔
1789
    }
198✔
1790
    else {
3,014,970✔
1791
        insert_with_offset(key, value.get_index_data(buffer), value, offset); // Throws
3,014,970✔
1792
    }
3,014,970✔
1793
}
3,015,168✔
1794

1795
void StringIndex::set(ObjKey key, const Mixed& new_value)
1796
{
696,315✔
1797
    StringConversionBuffer buffer;
696,315✔
1798
    Mixed old_value = get(key);
696,315✔
1799

1800
    if (this->m_target_column.tokenize()) {
696,315✔
1801
        auto tokenizer = Tokenizer::get_instance();
168✔
1802
        StringData old_string = old_value.get_index_data(buffer);
168✔
1803
        std::set<std::string> old_words;
168✔
1804

1805
        if (old_string.size() > 0) {
168✔
1806
            tokenizer->reset({old_string.data(), old_string.size()});
6✔
1807
            old_words = tokenizer->get_all_tokens();
6✔
1808
        }
6✔
1809
        std::set<std::string> new_words;
168✔
1810
        if (new_value.is_type(type_String)) {
168✔
1811
            new_words = tokenizer->reset(std::string_view(new_value.get<StringData>())).get_all_tokens();
168✔
1812
        }
168✔
1813

1814
        auto w1 = old_words.begin();
168✔
1815
        auto w2 = new_words.begin();
168✔
1816

1817
        // Do a diff, deleting words no longer present and
1818
        // inserting new words
1819
        while (w1 != old_words.end() && w2 != new_words.end()) {
540✔
1820
            if (*w1 < *w2) {
372✔
1821
                erase_string(key, *w1);
126✔
1822
                ++w1;
126✔
1823
            }
126✔
1824
            else if (*w2 < *w1) {
246✔
1825
                Mixed m(*w2);
198✔
1826
                insert_with_offset(key, m.get_index_data(buffer), m, 0);
198✔
1827
                ++w2;
198✔
1828
            }
198✔
1829
            else {
48✔
1830
                ++w1;
48✔
1831
                ++w2;
48✔
1832
            }
48✔
1833
        }
372✔
1834
        while (w1 != old_words.end()) {
168✔
1835
            erase_string(key, *w1);
×
1836
            ++w1;
×
1837
        }
×
1838
        while (w2 != new_words.end()) {
3,354✔
1839
            Mixed m(*w2);
3,186✔
1840
            insert_with_offset(key, m.get_index_data(buffer), m, 0);
3,186✔
1841

1842
            ++w2;
3,186✔
1843
        }
3,186✔
1844
    }
168✔
1845
    else {
696,147✔
1846
        if (REALM_LIKELY(new_value != old_value)) {
696,147✔
1847
            // We must erase this row first because erase uses find_first which
1848
            // might find the duplicate if we insert before erasing.
1849
            erase(key); // Throws
640,461✔
1850

1851
            auto index_data = new_value.get_index_data(buffer);
640,461✔
1852
            insert_with_offset(key, index_data, new_value, 0); // Throws
640,461✔
1853
        }
640,461✔
1854
    }
696,147✔
1855
}
696,315✔
1856

1857
void StringIndex::node_add_key(ref_type ref)
1858
{
306✔
1859
    REALM_ASSERT(ref);
306✔
1860
    REALM_ASSERT(m_array->is_inner_bptree_node());
306✔
1861

1862
    Allocator& alloc = m_array->get_alloc();
306✔
1863
    Array offsets(alloc);
306✔
1864
    get_child(*m_array, 0, offsets);
306✔
1865
    REALM_ASSERT(m_array->size() == offsets.size() + 1);
306✔
1866
    REALM_ASSERT(offsets.size() < REALM_MAX_BPNODE_SIZE + 1);
306✔
1867

1868
    Array new_top(alloc);
306✔
1869
    Array new_offsets(alloc);
306✔
1870
    new_top.init_from_ref(ref);
306✔
1871
    new_offsets.init_from_ref(new_top.get_as_ref(0));
306✔
1872
    REALM_ASSERT(!new_offsets.is_empty());
306✔
1873

1874
    int64_t key = new_offsets.back();
306✔
1875
    offsets.add(key);
306✔
1876
    m_array->add(ref);
306✔
1877
}
306✔
1878

1879
// Must return true if value of object(key) is less than 'b'.
1880
bool SortedListComparator::operator()(int64_t key_value, const Mixed& b) // used in lower_bound
1881
{
×
1882
    Mixed a = m_column.get_value(ObjKey(key_value));
×
1883
    if (a.is_null() && !b.is_null())
×
1884
        return true;
×
1885
    else if (b.is_null() && !a.is_null())
×
1886
        return false;
×
1887
    else if (a.is_null() && b.is_null())
×
1888
        return false;
×
1889
    return a.compare(b) < 0;
×
1890
}
×
1891

1892

1893
// Must return true if value of 'a' is less than value of object(key).
1894
bool SortedListComparator::operator()(const Mixed& a, int64_t key_value) // used in upper_bound
1895
{
×
1896
    Mixed b = m_column.get_value(ObjKey(key_value));
×
1897
    if (a.is_null() && !b.is_null())
×
1898
        return true;
×
1899
    else if (b.is_null() && !a.is_null())
×
1900
        return false;
×
1901
    else if (a.is_null() && b.is_null())
×
1902
        return false;
×
1903
    return a.compare(b) < 0;
×
1904
}
×
1905

1906
// TODO: the next time the StringIndex is migrated (post version 23) and sorted
1907
// properly in these lists replace this method with
1908
// std::lower_bound(key_values.cbegin(), it_end, value, slc);
1909
IntegerColumn::const_iterator SortedListComparator::find_start_of_unsorted(const Mixed& value,
1910
                                                                           const IntegerColumn& key_values) const
1911
{
2,136,066✔
1912
    if (key_values.size() >= 2) {
2,136,066✔
1913
        Mixed first = m_column.get_value(ObjKey(key_values.get(0)));
2,115,681✔
1914
        Mixed last = m_column.get_value(ObjKey(key_values.get(key_values.size() - 1)));
2,115,681✔
1915
        if (first == last) {
2,115,681✔
1916
            if (value.compare(first) <= 0) {
2,109,255✔
1917
                return key_values.cbegin();
2,107,947✔
1918
            }
2,107,947✔
1919
            else {
1,308✔
1920
                return key_values.cend();
1,308✔
1921
            }
1,308✔
1922
        }
2,109,255✔
1923
    }
2,115,681✔
1924

1925
    IntegerColumn::const_iterator it = key_values.cbegin();
26,811✔
1926
    IntegerColumn::const_iterator end = key_values.cend();
26,811✔
1927
    IntegerColumn::const_iterator first_greater = end;
26,811✔
1928
    while (it != end) {
207,915✔
1929
        Mixed val_it = m_column.get_value(ObjKey(*it));
203,388✔
1930
        int cmp = val_it.compare(value);
203,388✔
1931
        if (cmp == 0) {
203,388✔
1932
            return it;
22,284✔
1933
        }
22,284✔
1934
        if (cmp > 0 && first_greater == end) {
181,104✔
1935
            first_greater = it;
1,101✔
1936
        }
1,101✔
1937
        ++it;
181,104✔
1938
    }
181,104✔
1939
    return first_greater;
4,527✔
1940
}
26,811✔
1941

1942
// TODO: same as above, change to std::upper_bound(lower, it_end, value, slc);
1943
IntegerColumn::const_iterator SortedListComparator::find_end_of_unsorted(const Mixed& value,
1944
                                                                         const IntegerColumn& key_values,
1945
                                                                         IntegerColumn::const_iterator begin) const
1946
{
2,122,446✔
1947
    IntegerColumn::const_iterator end = key_values.cend();
2,122,446✔
1948
    if (begin != end && end - begin > 0) {
2,122,458✔
1949
        // optimization: check the last element first
1950
        Mixed last = m_column.get_value(ObjKey(*(key_values.cend() - 1)));
2,122,395✔
1951
        if (last == value) {
2,122,395✔
1952
            return key_values.cend();
2,121,135✔
1953
        }
2,121,135✔
1954
    }
2,122,395✔
1955
    while (begin != end) {
110,892✔
1956
        Mixed val_it = m_column.get_value(ObjKey(*begin));
110,847✔
1957
        if (value.compare(val_it) != 0) {
110,847✔
1958
            return begin;
1,266✔
1959
        }
1,266✔
1960
        ++begin;
109,581✔
1961
    }
109,581✔
1962
    return end;
45✔
1963
}
1,311✔
1964

1965
// LCOV_EXCL_START ignore debug functions
1966
void StringIndex::verify() const
1967
{
4,248✔
1968
#ifdef REALM_DEBUG
4,248✔
1969
    m_array->verify();
4,248✔
1970

1971
    Allocator& alloc = m_array->get_alloc();
4,248✔
1972
    const size_t array_size = m_array->size();
4,248✔
1973

1974
    // Get first matching row for every key
1975
    if (m_array->is_inner_bptree_node()) {
4,248✔
1976
        for (size_t i = 1; i < array_size; ++i) {
×
1977
            size_t ref = m_array->get_as_ref(i);
×
1978
            StringIndex ndx(ref, nullptr, 0, m_target_column, alloc);
×
1979
            ndx.verify();
×
1980
        }
×
1981
    }
×
1982
    else {
4,248✔
1983
        size_t column_size = m_target_column.size();
4,248✔
1984
        for (size_t i = 1; i < array_size; ++i) {
8,904✔
1985
            int64_t ref = m_array->get(i);
4,656✔
1986

1987
            // low bit set indicate literal ref (shifted)
1988
            if (ref & 1) {
4,656✔
1989
                size_t r = to_size_t((uint64_t(ref) >> 1));
480✔
1990
                REALM_ASSERT_EX(r < column_size, r, column_size);
480✔
1991
            }
480✔
1992
            else {
4,176✔
1993
                // A real ref either points to a list or a subindex
1994
                char* header = alloc.translate(to_ref(ref));
4,176✔
1995
                if (Array::get_context_flag_from_header(header)) {
4,176✔
1996
                    StringIndex ndx(to_ref(ref), m_array.get(), i, m_target_column, alloc);
4,128✔
1997
                    ndx.verify();
4,128✔
1998
                }
4,128✔
1999
                else {
48✔
2000
                    IntegerColumn sub(alloc, to_ref(ref)); // Throws
48✔
2001
                    IntegerColumn::const_iterator it = sub.cbegin();
48✔
2002
                    IntegerColumn::const_iterator it_end = sub.cend();
48✔
2003
                    SortedListComparator slc(m_target_column);
48✔
2004
                    Mixed previous = get(ObjKey(*it));
48✔
2005
                    size_t last_row = to_size_t(*it);
48✔
2006

2007
                    // Check that strings listed in sub are in sorted order
2008
                    // and if there are duplicates, that the row numbers are
2009
                    // sorted in the group of duplicates.
2010
                    while (it != it_end) {
168✔
2011
                        Mixed it_data = get(ObjKey(*it));
120✔
2012
                        size_t it_row = to_size_t(*it);
120✔
2013
                        REALM_ASSERT(previous <= it_data);
120✔
2014
                        if (it != sub.cbegin() && previous == it_data) {
120✔
2015
                            REALM_ASSERT_EX(it_row > last_row, it_row, last_row);
×
2016
                        }
×
2017
                        last_row = it_row;
120✔
2018
                        previous = get(ObjKey(*it));
120✔
2019
                        ++it;
120✔
2020
                    }
120✔
2021
                }
48✔
2022
            }
4,176✔
2023
        }
4,656✔
2024
    }
4,248✔
2025
// FIXME: Extend verification along the lines of IntegerColumn::verify().
2026
#endif
4,248✔
2027
}
4,248✔
2028

2029
#ifdef REALM_DEBUG
2030

2031
template <class T>
2032
void StringIndex::verify_entries(const ClusterColumn& column) const
2033
{
2034
    std::vector<ObjKey> results;
2035

2036
    auto it = column.begin();
2037
    auto end = column.end();
2038
    auto col = column.get_column_key();
2039
    while (it != end) {
2040
        ObjKey key = it->get_key();
2041
        T value = it->get<T>(col);
2042

2043
        find_all(results, value);
2044

2045
        auto ndx = find(results.begin(), results.end(), key);
2046
        REALM_ASSERT(ndx != results.end());
2047
        size_t found = count(value);
2048
        REALM_ASSERT_EX(found >= 1, found);
2049
        results.clear();
2050
    }
2051
}
2052

2053
namespace {
2054

2055
bool is_chars(uint64_t val)
2056
{
×
2057
    if (val == 0)
×
2058
        return true;
×
2059
    if (is_chars(val >> 8)) {
×
2060
        char c = val & 0xFF;
×
2061
        if (!c || std::isprint(c)) {
×
2062
            return true;
×
2063
        }
×
2064
    }
×
2065
    return false;
×
2066
}
×
2067

2068
void out_char(std::ostream& out, uint64_t val)
2069
{
×
2070
    if (val) {
×
2071
        out_char(out, val >> 8);
×
2072
        char c = val & 0xFF;
×
2073
        if (c && c != 'X') {
×
2074
            out << c;
×
2075
        }
×
2076
    }
×
2077
}
×
2078

2079
void out_hex(std::ostream& out, uint64_t val)
2080
{
×
2081
    if (is_chars(val)) {
×
2082
        out_char(out, val);
×
2083
    }
×
2084
    else {
×
2085
        out << int(val);
×
2086
    }
×
2087
}
×
2088

2089
} // namespace
2090

2091
void StringIndex::dump_node_structure(const Array& node, std::ostream& out, int level)
2092
{
×
2093
    int indent = level * 2;
×
2094
    Allocator& alloc = node.get_alloc();
×
2095
    Array subnode(alloc);
×
2096

2097
    size_t node_size = node.size();
×
2098
    REALM_ASSERT(node_size >= 1);
×
2099

2100
    out << std::hex;
×
2101

2102
    bool node_is_leaf = !node.is_inner_bptree_node();
×
2103
    if (node_is_leaf) {
×
2104
        out << std::setw(indent) << ""
×
2105
            << "Leaf (B+ tree) (ref: " << node.get_ref() << ")\n";
×
2106
    }
×
2107
    else {
×
2108
        out << std::setw(indent) << ""
×
2109
            << "Inner node (B+ tree) (ref: " << node.get_ref() << ")\n";
×
2110
    }
×
2111

2112
    subnode.init_from_ref(to_ref(node.front()));
×
2113
    out << std::setw(indent) << ""
×
2114
        << "  Keys (keys_ref: " << subnode.get_ref() << ", ";
×
2115
    if (subnode.is_empty()) {
×
2116
        out << "no keys";
×
2117
    }
×
2118
    else {
×
2119
        out << "keys: ";
×
2120
        for (size_t i = 0; i != subnode.size(); ++i) {
×
2121
            if (i != 0)
×
2122
                out << ", ";
×
2123
            out_hex(out, uint32_t(subnode.get(i)));
×
2124
        }
×
2125
    }
×
2126
    out << ")\n";
×
2127

2128
    if (node_is_leaf) {
×
2129
        for (size_t i = 1; i != node_size; ++i) {
×
2130
            int_fast64_t value = node.get(i);
×
2131
            bool is_single_row_index = (value & 1) != 0;
×
2132
            if (is_single_row_index) {
×
2133
                out << std::setw(indent) << ""
×
2134
                    << "  Single row index (value: " << (value / 2) << ")\n";
×
2135
                continue;
×
2136
            }
×
2137
            subnode.init_from_ref(to_ref(value));
×
2138
            bool is_subindex = subnode.get_context_flag();
×
2139
            if (is_subindex) {
×
2140
                out << std::setw(indent) << ""
×
2141
                    << "  Subindex\n";
×
2142
                dump_node_structure(subnode, out, level + 2);
×
2143
                continue;
×
2144
            }
×
2145
            IntegerColumn indexes(alloc, to_ref(value));
×
2146
            out << std::setw(indent) << ""
×
2147
                << "  List of row indexes\n";
×
2148
            indexes.dump_values(out, level + 2);
×
2149
        }
×
2150
        return;
×
2151
    }
×
2152

2153

2154
    size_t num_children = node_size - 1;
×
2155
    size_t child_ref_begin = 1;
×
2156
    size_t child_ref_end = 1 + num_children;
×
2157
    for (size_t i = child_ref_begin; i != child_ref_end; ++i) {
×
2158
        subnode.init_from_ref(node.get_as_ref(i));
×
2159
        dump_node_structure(subnode, out, level + 1);
×
2160
    }
×
2161
}
×
2162

2163
void StringIndex::print() const
2164
{
×
2165
    dump_node_structure(*m_array, std::cout, 0);
×
2166
}
×
2167

2168
#endif // LCOV_EXCL_STOP ignore debug functions
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