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

realm / realm-core / thomas.goyne_526

08 Oct 2024 09:07PM UTC coverage: 91.116% (+0.007%) from 91.109%
thomas.goyne_526

Pull #8038

Evergreen

tgoyne
Skip backup files when scanning for audit Realms to upload

Backup realms from file format upgrades are placed next to the original file,
so the audit realm pool will find the backup and attempt to upload it,
triggering another upgrade and backup until the filename becomes too long and
it crashes instead. It needs to instead skip the backup files and delete any
lingering backups of Realms which have already been uploaded.
Pull Request #8038: Skip backup files when scanning for audit Realms to upload

102804 of 181498 branches covered (56.64%)

38 of 43 new or added lines in 3 files covered. (88.37%)

68 existing lines in 9 files now uncovered.

217220 of 238400 relevant lines covered (91.12%)

5584627.22 hits per line

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

84.85
/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,977,148✔
43
    ref_type child_ref = parent.get_as_ref(child_ref_ndx);
24,977,148✔
44
    child.init_from_ref(child_ref);
24,977,148✔
45
    child.set_parent(&parent, child_ref_ndx);
24,977,148✔
46
}
24,977,148✔
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,909,553✔
106
    const Obj obj{m_cluster_tree->get(key)};
11,909,553✔
107
    return obj.get_any(m_column_key);
11,909,553✔
108
}
11,909,553✔
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,889✔
135
    SortedListComparator slc(column);
5,889✔
136

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

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

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

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

149
    return first_key_value;
5,865✔
150
}
5,865✔
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,391✔
156
    SortedListComparator slc(column);
8,391✔
157

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

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

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

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

172
    return cnt;
8,343✔
173
}
8,367✔
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);
54✔
186

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

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

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

202
    // Check string value at upper, if equal return matches in (lower, upper]
203
    ObjKey last_key = ObjKey(*upper);
2,352✔
204
    actual = column.get_value(ObjKey(last_key));
2,352✔
205
    if (actual == value) {
2,352✔
206
        result_ref.payload = from_ref(key_values.get_ref());
1,731✔
207
        result_ref.start_ndx = lower.get_position();
1,731✔
208
        result_ref.end_ndx = upper.get_position() + 1; // one past last match
1,731✔
209
        return size_t(FindRes_column);
1,731✔
210
    }
1,731✔
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);
621✔
216

217
    result_ref.payload = from_ref(key_values.get_ref());
621✔
218
    result_ref.start_ndx = lower.get_position();
621✔
219
    result_ref.end_ndx = upper.get_position();
621✔
220
    return int64_t(FindRes_column);
621✔
221
}
2,352✔
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,999,659✔
228
    // Return`realm::not_found`, or an index to the (any) match
229
    constexpr bool first(method == index_FindFirst);
1,999,659✔
230
    // Return 0, or the number of items that match the specified `value`
231
    constexpr bool get_count(method == index_Count);
1,999,659✔
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,999,659✔
238

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

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

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

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

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

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

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

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

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

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

283
        if (stored_key != key)
3,254,241✔
284
            return local_not_found;
709,929✔
285

286
        // Literal row index (tagged)
287
        if (ref & 1) {
2,544,312✔
288
            int64_t key_value = int64_t(ref >> 1);
1,068,516✔
289

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

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

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

309
            return from_list<method>(value, result_ref, sub, column);
17,076✔
310
        }
17,364✔
311

312
        // Recurse into sub-index;
313
        header = sub_header;
1,458,432✔
314
        data = get_data_from_header(header);
1,458,432✔
315
        width = get_width_from_header(header);
1,458,432✔
316
        is_inner_node = get_is_inner_bptree_node_from_header(header);
1,458,432✔
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,458,432✔
320

321
        // Update 4 byte index key
322
        key = StringIndex::create_key(index_data, stringoffset);
1,458,432✔
323
    }
1,458,432✔
324
}
1,999,659✔
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
{
42,543✔
369
    if (column.full_word()) {
42,543✔
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);
42,483✔
379

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

385
    ObjKey key = ObjKey(*lower);
42,483✔
386

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

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

393
    // Copy all matches into result column
394
    size_t sz = result.size() + (upper - lower);
42,483✔
395
    result.reserve(sz);
42,483✔
396
    for (IntegerColumn::const_iterator it = lower; it != upper; ++it) {
805,083✔
397
        result.push_back(ObjKey(*it));
762,600✔
398
    }
762,600✔
399
}
42,483✔
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,744✔
410
    REALM_ASSERT_DEBUG(i <= 15);
3,123,744✔
411
    i *= 0x204081;
3,123,744✔
412
    i &= 0x1010101;
3,123,744✔
413
    i *= 0xff;
3,123,744✔
414
    return i;
3,123,744✔
415
}
3,123,744✔
416

417
int32_t select_from_mask(int32_t a, int32_t b, int32_t mask)
418
{
3,123,744✔
419
    return a ^ ((a ^ b) & mask);
3,123,744✔
420
}
3,123,744✔
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,744✔
431
    return select_from_mask(upper, lower, replicate_4_lsb_x8(permutation));
3,123,744✔
432
}
3,123,744✔
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,234✔
454
        m_keys_seen.clear();
195,234✔
455
        const key_type upper_key = StringIndex::create_key(m_upper_value, string_offset);
195,234✔
456
        const key_type lower_key = StringIndex::create_key(m_lower_value, string_offset);
195,234✔
457
        for (int p = 0; p < num_permutations; ++p) {
3,318,978✔
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,744✔
461
            const bool new_key = std::find(m_keys_seen.cbegin(), m_keys_seen.cend(), key) == m_keys_seen.cend();
3,123,744✔
462
            if (new_key) {
3,123,744✔
463
                m_keys_seen.push_back(key);
3,059,706✔
464
                add_next(header, string_offset, key);
3,059,706✔
465
            }
3,059,706✔
466
        }
3,123,744✔
467
    }
195,234✔
468

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

474
    Item get_next()
475
    {
3,059,706✔
476
        Item item = m_items.back();
3,059,706✔
477
        m_items.pop_back();
3,059,706✔
478
        return item;
3,059,706✔
479
    }
3,059,706✔
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,706✔
484
        m_items.push_back({header, string_offset, key});
3,059,706✔
485
    }
3,059,706✔
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,288✔
515
        SearchList::Item item = search_list.get_next();
3,059,706✔
516

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

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

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

533
        // If key is outside range, we know there can be no match
534
        if (pos == offsets_size)
3,059,706✔
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,848✔
539
        const uint64_t ref = get_direct(data, width, pos_refs);
3,058,848✔
540

541
        if (is_inner_node) {
3,058,848✔
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,848✔
549

550
        if (stored_key != key)
3,058,848✔
551
            continue;
2,863,125✔
552

553
        // Literal row index (tagged)
554
        if (ref & 1) {
195,723✔
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,077✔
568
        const bool sub_isindex = get_context_flag_from_header(sub_header);
190,077✔
569

570
        // List of row indices with common prefix up to this point, in sorted order.
571
        if (!sub_isindex) {
190,077✔
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);
188,655✔
579
    }
188,655✔
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
{
51,198✔
588
    const char* data = m_data;
51,198✔
589
    const char* header;
51,198✔
590
    uint_least8_t width = m_width;
51,198✔
591
    bool is_inner_node = m_is_inner_bptree_node;
51,198✔
592
    size_t stringoffset = 0;
51,198✔
593

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

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

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

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

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

617
        if (is_inner_node) {
80,985✔
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,979✔
627

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

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

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

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

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

651
        // Recurse into sub-index;
652
        header = sub_header;
29,793✔
653
        data = get_data_from_header(header);
29,793✔
654
        width = get_width_from_header(header);
29,793✔
655
        is_inner_node = get_is_inner_bptree_node_from_header(header);
29,793✔
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;
29,793✔
659

660
        // Update 4 byte index key
661
        key = StringIndex::create_key(index_data, stringoffset);
29,793✔
662
    }
29,793✔
663
}
51,198✔
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,965,987✔
779
    InternalFindResult unused;
1,965,987✔
780
    return ObjKey(index_string<index_FindFirst>(value, unused, column));
1,965,987✔
781
}
1,965,987✔
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
{
57,780✔
787
    if (case_insensitive && value.is_type(type_String)) {
57,780✔
788
        index_string_all_ins(value.get_string(), result, column);
6,582✔
789
    }
6,582✔
790
    else {
51,198✔
791
        index_string_all(value, result, column);
51,198✔
792
    }
51,198✔
793
}
57,780✔
794

795
FindRes IndexArray::index_string_find_all_no_copy(const Mixed& value, const ClusterColumn& column,
796
                                                  InternalFindResult& result) const
797
{
20,781✔
798
    return static_cast<FindRes>(index_string<index_FindAll_nocopy>(value, result, column));
20,781✔
799
}
20,781✔
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
{
694,065✔
809
    Array::Type type = is_leaf ? Array::type_HasRefs : Array::type_InnerBptreeNode;
694,065✔
810
    std::unique_ptr<IndexArray> top(new IndexArray(alloc)); // Throws
694,065✔
811
    top->create(type);                                      // Throws
694,065✔
812

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

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

823
    return top;
694,065✔
824
}
694,065✔
825

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

833

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

841
void StringIndex::insert_to_existing_list_at_lower(ObjKey key, Mixed value, IntegerColumn& list,
842
                                                   const IntegerColumnIterator& lower)
843
{
2,076,189✔
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,076,195✔
848
        if (m_target_column.full_word()) {
2,076,195✔
849
            return list.cend();
354✔
850
        }
354✔
851
        else {
2,075,841✔
852
            SortedListComparator slc(m_target_column);
2,075,841✔
853
            return slc.find_end_of_unsorted(value, list, lower);
2,075,841✔
854
        }
2,075,841✔
855
    }();
2,076,195✔
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,076,189✔
860
    int64_t last_key_value = *last;
2,076,189✔
861
    if (key.value >= last_key_value) {
2,076,189✔
862
        list.insert(upper.get_position(), key.value);
2,040,456✔
863
    }
2,040,456✔
864
    else {
35,733✔
865
        // insert into the group of duplicates, keeping object keys sorted
866
        IntegerColumn::const_iterator inner_lower = std::lower_bound(lower, upper, key.value);
35,733✔
867
        if (*inner_lower != key.value) {
35,733✔
868
            list.insert(inner_lower.get_position(), key.value);
35,652✔
869
        }
35,652✔
870
    }
35,733✔
871
}
2,076,189✔
872

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

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

887
        if (lower_value != value) {
420✔
888
            list.insert(lower.get_position(), key.value);
420✔
889
        }
420✔
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
    }
420✔
897
}
1,980✔
898

899

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

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

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

913
    size_t ins_pos = values.lower_bound_int(key);
1,812✔
914
    if (ins_pos == values.size()) {
1,812✔
915
        // When key is outside current range, we can just add it
916
        values.add(key);
1,812✔
917
        m_array->add(ref);
1,812✔
918
        return;
1,812✔
919
    }
1,812✔
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
void StringIndex::new_node(const NodeChange& nc)
934
{
153✔
935
    StringIndex new_node(inner_node_tag(), m_array->get_alloc());
153✔
936
    switch (nc.type) {
153✔
937
        case NodeChange::change_None:
✔
938
            break;
×
939
        case NodeChange::change_InsertBefore: {
✔
940
            new_node.node_add_key(nc.ref1);
×
941
            new_node.node_add_key(get_ref());
×
942
            break;
×
943
        }
×
944
        case NodeChange::change_InsertAfter: {
6✔
945
            new_node.node_add_key(get_ref());
6✔
946
            new_node.node_add_key(nc.ref1);
6✔
947
            break;
6✔
948
        }
×
949
        case NodeChange::change_Split: {
147✔
950
            new_node.node_add_key(nc.ref1);
147✔
951
            new_node.node_add_key(nc.ref2);
147✔
952
            break;
147✔
953
        }
×
954
    }
153✔
955
    m_array->init_from_ref(new_node.get_ref());
153✔
956
    m_array->update_parent();
153✔
957
}
153✔
958

959
void StringIndex::TreeInsert(ObjKey obj_key, key_type key, size_t offset, StringData index_data, const Mixed& value)
960
{
11,198,946✔
961
    auto nc = do_insert(obj_key, key, offset, index_data, value);
11,198,946✔
962
    if (nc.type != NodeChange::change_None) {
11,198,946✔
963
        new_node(nc);
153✔
964
    }
153✔
965
}
11,198,946✔
966

967

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

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

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

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

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

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

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

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

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

1056
        bool noextend = old_offsets_size >= REALM_MAX_BPNODE_SIZE;
11,200,680✔
1057

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

1063
        // Create new list for item (a leaf)
1064
        StringIndex new_list(m_target_column, alloc);
2,147,484,262✔
1065

1066
        new_list.leaf_insert(obj_key, key, offset, index_data, value);
2,147,484,262✔
1067

1068
        size_t ndx = old_keys.lower_bound_int(key);
2,147,484,262✔
1069

1070
        // insert before
1071
        if (ndx == 0)
2,147,484,262✔
1072
            return NodeChange(NodeChange::change_InsertBefore, new_list.get_ref());
9✔
1073

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

1078
        // split
1079
        Array new_keys(alloc);
2,147,484,244✔
1080
        get_child(*new_list.m_array, 0, new_keys);
2,147,484,244✔
1081
        // Move items after split to new list
1082
        for (size_t i = ndx; i < old_offsets_size; ++i) {
2,147,623,783✔
1083
            int64_t v2 = old_keys.get(i);
271,818✔
1084
            int64_t v3 = m_array->get(i + 1);
271,818✔
1085

1086
            new_keys.add(v2);
271,818✔
1087
            new_list.m_array->add(v3);
271,818✔
1088
        }
271,818✔
1089
        old_keys.truncate(ndx);
2,147,484,244✔
1090
        m_array->truncate(ndx + 1);
2,147,484,244✔
1091

1092
        return NodeChange(NodeChange::change_Split, get_ref(), new_list.get_ref());
2,147,484,244✔
1093
    }
2,147,484,256✔
1094
}
11,491,140✔
1095

1096

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

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

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

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

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

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

1126

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

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

1137
    REALM_ASSERT(ndx <= offsets.size());
27✔
1138
    REALM_ASSERT(offsets.size() < REALM_MAX_BPNODE_SIZE);
27✔
1139

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

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

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

1160
    {
11,175,621✔
1161
        // Get subnode table
1162
        Array keys(alloc);
11,175,621✔
1163
        get_child(*m_array, 0, keys);
11,175,621✔
1164
        REALM_ASSERT(m_array->size() == keys.size() + 1);
11,175,621✔
1165

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

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

1173
        if (ins_pos == keys.size()) {
11,175,621✔
1174
            if (noextend)
840,594✔
1175
                return false;
24✔
1176

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

1192
        key_type k = key_type(keys.get(ins_pos));
10,335,027✔
1193

1194
        // If key is not present we add it at the correct location
1195
        if (k != key) {
10,335,027✔
1196
            if (noextend)
1,335,210✔
1197
                return false;
636✔
1198

1199
            keys.insert(ins_pos, key);
1,334,574✔
1200
            if (!m_target_column.full_word() || is_at_string_end) {
1,334,574✔
1201
                int64_t shifted = int64_t((uint64_t(obj_key.value) << 1) + 1); // shift to indicate literal
1,153,479✔
1202
                m_array->insert(ins_pos_refs, shifted);
1,153,479✔
1203
            }
1,153,479✔
1204
            else {
181,095✔
1205
                // create subindex for rest of string
1206
                StringIndex subindex(m_target_column, m_array->get_alloc());
181,095✔
1207
                subindex.insert_with_offset(obj_key, index_data, value, offset + 4);
181,095✔
1208
                m_array->insert(ins_pos_refs, subindex.get_ref());
181,095✔
1209
            }
181,095✔
1210
            return true;
1,334,574✔
1211
        }
1,335,210✔
1212
    }
10,335,027✔
1213

1214
    // This leaf already has a slot for for the key
1215

1216
    uint64_t slot_value = uint64_t(m_array->get(ins_pos_refs));
8,999,817✔
1217
    size_t suboffset = offset + s_index_key_length;
8,999,817✔
1218

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

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

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

1271
        IntegerColumn::const_iterator lower = sub.cend();
2,079,489✔
1272

1273
        bool value_exists_in_list = false;
2,079,489✔
1274
        if (m_target_column.full_word()) {
2,079,489✔
1275
            lower = sub.cbegin();
360✔
1276
            value_exists_in_list = reconstruct_string(offset, key, index_data) == value.get_string();
360✔
1277
        }
360✔
1278
        else {
2,079,129✔
1279
            SortedListComparator slc(m_target_column);
2,079,129✔
1280
            IntegerColumn::const_iterator it_end = lower;
2,079,129✔
1281
            lower = slc.find_start_of_unsorted(value, sub);
2,079,129✔
1282

1283
            if (lower != it_end) {
2,079,129✔
1284
                Mixed lower_value = get(ObjKey(*lower));
2,076,645✔
1285
                if (lower_value == value) {
2,076,645✔
1286
                    value_exists_in_list = true;
2,075,901✔
1287
                }
2,075,901✔
1288
            }
2,076,645✔
1289
        }
2,079,129✔
1290

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

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

1339
    return true;
6,853,224✔
1340
}
8,932,713✔
1341

1342
Mixed StringIndex::get(ObjKey key) const
1343
{
4,031,709✔
1344
    return m_target_column.get_value(key);
4,031,709✔
1345
}
4,031,709✔
1346

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

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

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

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

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

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

1442
void StringIndex::insert_bulk(const ArrayUnsigned* keys, uint64_t key_offset, size_t num_values, ArrayPayload& values)
1443
{
96,969✔
1444
    if (keys) {
96,969✔
1445
        for (size_t i = 0; i < num_values; ++i) {
1,401✔
1446
            ObjKey key(keys->get(i) + key_offset);
1,173✔
1447
            insert(key, values.get_any(i));
1,173✔
1448
        }
1,173✔
1449
    }
228✔
1450
    else {
96,741✔
1451
        for (size_t i = 0; i < num_values; ++i) {
1,422,261✔
1452
            ObjKey key(i + key_offset);
1,325,520✔
1453
            insert(key, values.get_any(i));
1,325,520✔
1454
        }
1,325,520✔
1455
    }
96,741✔
1456
}
96,969✔
1457

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

1479

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

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

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

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

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

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

1590

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

1597
    values.clear();
4,677✔
1598
    values.ensure_minimum_width(0x7FFFFFFF); // This ensures 31 bits plus a sign bit
4,677✔
1599

1600
    size_t size = 1;
4,677✔
1601
    m_array->truncate_and_destroy_children(size); // Don't touch `values` array
4,677✔
1602

1603
    m_array->set_type(Array::type_HasRefs);
4,677✔
1604
}
4,677✔
1605

1606

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

1614
    // Create 4 byte index key
1615
    key_type key = create_key(index_data, offset);
1,962,315✔
1616

1617
    const size_t pos = values.lower_bound_int(key);
1,962,315✔
1618
    const size_t pos_refs = pos + 1; // first entry in refs points to offsets
1,962,315✔
1619
    REALM_ASSERT(pos != values.size());
1,962,315✔
1620

1621
    if (m_array->is_inner_bptree_node()) {
1,962,315✔
1622
        ref_type ref = m_array->get_as_ref(pos_refs);
64,764✔
1623
        StringIndex node(ref, m_array.get(), pos_refs, m_target_column, alloc);
64,764✔
1624
        node.do_delete(obj_key, index_data, offset);
64,764✔
1625

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

1652
                if (subindex.is_empty()) {
709,227✔
1653
                    values.erase(pos);
26,424✔
1654
                    m_array->erase(pos_refs);
26,424✔
1655
                    subindex.destroy();
26,424✔
1656
                }
26,424✔
1657
            }
709,227✔
1658
            else {
355,731✔
1659
                IntegerColumn sub(alloc, ref_type(ref)); // Throws
355,731✔
1660
                sub.set_parent(m_array.get(), pos_refs);
355,731✔
1661
                size_t r = sub.find_first(obj_key.value);
355,731✔
1662
                size_t sub_size = sub.size(); // Slow
355,731✔
1663
                REALM_ASSERT_EX(r != sub_size, r, sub_size);
355,731✔
1664
                sub.erase(r);
355,731✔
1665

1666
                if (sub_size == 1) {
355,731✔
1667
                    values.erase(pos);
2,982✔
1668
                    m_array->erase(pos_refs);
2,982✔
1669
                    sub.destroy();
2,982✔
1670
                }
2,982✔
1671
            }
355,731✔
1672
        }
1,064,958✔
1673
    }
1,897,551✔
1674
}
1,962,315✔
1675

1676
void StringIndex::erase_string(ObjKey key, StringData value)
1677
{
1,188,765✔
1678
    do_delete(key, value, 0);
1,188,765✔
1679

1680
    // Collapse top nodes with single item
1681
    while (m_array->is_inner_bptree_node()) {
1,188,771✔
1682
        REALM_ASSERT(m_array->size() > 1); // node cannot be empty
64,770✔
1683
        if (m_array->size() > 2)
64,770✔
1684
            break;
64,764✔
1685

1686
        ref_type ref = m_array->get_as_ref(1);
6✔
1687
        m_array->set(1, 1); // avoid destruction of the extracted ref
6✔
1688
        m_array->destroy_deep();
6✔
1689
        m_array->init_from_ref(ref);
6✔
1690
        m_array->update_parent();
6✔
1691
    }
6✔
1692
}
1,188,765✔
1693

1694
namespace {
1695

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

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

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

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

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

1760
    return false;
4,110✔
1761
}
6,366✔
1762

1763
} // anonymous namespace
1764

1765

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

1771

1772
bool StringIndex::is_empty() const
1773
{
774,246✔
1774
    return m_array->size() == 1; // first entry in refs points to offsets
774,246✔
1775
}
774,246✔
1776

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

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

1786
            for (auto& word : words) {
936✔
1787
                Mixed m(word);
936✔
1788
                insert_with_offset(key, m.get_index_data(buffer), m, 0); // Throws
936✔
1789
            }
936✔
1790
        }
198✔
1791
    }
198✔
1792
    else {
3,018,312✔
1793
        insert_with_offset(key, value.get_index_data(buffer), value, offset); // Throws
3,018,312✔
1794
    }
3,018,312✔
1795
}
3,018,510✔
1796

1797
void StringIndex::set(ObjKey key, const Mixed& new_value)
1798
{
697,137✔
1799
    StringConversionBuffer buffer;
697,137✔
1800
    Mixed old_value = get(key);
697,137✔
1801

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

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

1816
        auto w1 = old_words.begin();
168✔
1817
        auto w2 = new_words.begin();
168✔
1818

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

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

1853
            auto index_data = new_value.get_index_data(buffer);
640,995✔
1854
            insert_with_offset(key, index_data, new_value, 0); // Throws
640,995✔
1855
        }
640,995✔
1856
    }
696,969✔
1857
}
697,137✔
1858

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

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

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

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

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

1894

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

1908
// TODO: the next time the StringIndex is migrated (post version 23) and sorted
1909
// properly in these lists replace this method with
1910
// std::lower_bound(key_values.cbegin(), it_end, value, slc);
1911
IntegerColumn::const_iterator SortedListComparator::find_start_of_unsorted(const Mixed& value,
1912
                                                                           const IntegerColumn& key_values) const
1913
{
2,141,091✔
1914
    if (key_values.size() >= 2) {
2,141,091✔
1915
        Mixed first = m_column.get_value(ObjKey(key_values.get(0)));
2,119,779✔
1916
        Mixed last = m_column.get_value(ObjKey(key_values.get(key_values.size() - 1)));
2,119,779✔
1917
        if (first == last) {
2,119,779✔
1918
            if (value.compare(first) <= 0) {
2,113,215✔
1919
                return key_values.cbegin();
2,112,072✔
1920
            }
2,112,072✔
1921
            else {
1,143✔
1922
                return key_values.cend();
1,143✔
1923
            }
1,143✔
1924
        }
2,113,215✔
1925
    }
2,119,779✔
1926

1927
    IntegerColumn::const_iterator it = key_values.cbegin();
27,876✔
1928
    IntegerColumn::const_iterator end = key_values.cend();
27,876✔
1929
    IntegerColumn::const_iterator first_greater = end;
27,876✔
1930
    while (it != end) {
209,217✔
1931
        Mixed val_it = m_column.get_value(ObjKey(*it));
204,858✔
1932
        int cmp = val_it.compare(value);
204,858✔
1933
        if (cmp == 0) {
204,858✔
1934
            return it;
23,517✔
1935
        }
23,517✔
1936
        if (cmp > 0 && first_greater == end) {
181,341✔
1937
            first_greater = it;
957✔
1938
        }
957✔
1939
        ++it;
181,341✔
1940
    }
181,341✔
1941
    return first_greater;
4,359✔
1942
}
27,876✔
1943

1944
// TODO: same as above, change to std::upper_bound(lower, it_end, value, slc);
1945
IntegerColumn::const_iterator SortedListComparator::find_end_of_unsorted(const Mixed& value,
1946
                                                                         const IntegerColumn& key_values,
1947
                                                                         IntegerColumn::const_iterator begin) const
1948
{
2,127,384✔
1949
    IntegerColumn::const_iterator end = key_values.cend();
2,127,384✔
1950
    if (begin != end && end - begin > 0) {
2,127,384✔
1951
        // optimization: check the last element first
1952
        Mixed last = m_column.get_value(ObjKey(*(key_values.cend() - 1)));
2,127,303✔
1953
        if (last == value) {
2,127,303✔
1954
            return key_values.cend();
2,126,250✔
1955
        }
2,126,250✔
1956
    }
2,127,303✔
1957
    while (begin != end) {
110,847✔
1958
        Mixed val_it = m_column.get_value(ObjKey(*begin));
110,847✔
1959
        if (value.compare(val_it) != 0) {
110,847✔
1960
            return begin;
1,248✔
1961
        }
1,248✔
1962
        ++begin;
109,599✔
1963
    }
109,599✔
1964
    return end;
4,294,967,294✔
1965
}
1,134✔
1966

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

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

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

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

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

2031
#ifdef REALM_DEBUG
2032

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

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

2045
        find_all(results, value);
2046

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

2055
namespace {
2056

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

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

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

2091
} // namespace
2092

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

2099
    size_t node_size = node.size();
×
2100
    REALM_ASSERT(node_size >= 1);
×
2101

2102
    out << std::hex;
×
2103

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

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

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

2155

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

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

2170
#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

© 2026 Coveralls, Inc