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

realm / realm-core / thomas.goyne_478

02 Aug 2024 05:19PM UTC coverage: 91.089% (-0.01%) from 91.1%
thomas.goyne_478

Pull #7944

Evergreen

tgoyne
Only track pending client resets done by the same core version

If the previous attempt at performing a client reset was done with a different
core version then we should retry the client reset as the new version may have
fixed a bug that made the previous attempt fail (or may be a downgrade to a
version before when the bug was introduced). This also simplifies the tracking
as it means that we don't need to be able to read trackers created by different
versions.

This also means that we can freely change the schema of the table, which this
takes advantage of to drop the unused primary key and make the error required,
as we never actually stored null and the code reading it would have crashed if
it encountered a null error.
Pull Request #7944: Only track pending client resets done by the same core version

102704 of 181534 branches covered (56.58%)

138 of 153 new or added lines in 10 files covered. (90.2%)

85 existing lines in 16 files now uncovered.

216717 of 237917 relevant lines covered (91.09%)

5947762.1 hits per line

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

84.63
/src/realm/index_string.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <cstdio>
20
#include <iomanip>
21
#include <list>
22

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

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

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

39
namespace {
40

41
void get_child(Array& parent, size_t child_ref_ndx, Array& child) noexcept
42
{
24,958,089✔
43
    ref_type child_ref = parent.get_as_ref(child_ref_ndx);
24,958,089✔
44
    child.init_from_ref(child_ref);
24,958,089✔
45
    child.set_parent(&parent, child_ref_ndx);
24,958,089✔
46
}
24,958,089✔
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,886,627✔
106
    const Obj obj{m_cluster_tree->get(key)};
11,886,627✔
107
    return obj.get_any(m_column_key);
11,886,627✔
108
}
11,886,627✔
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,355✔
135
    SortedListComparator slc(column);
5,355✔
136

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

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

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

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

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

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

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

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

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

172
    return cnt;
8,217✔
173
}
8,241✔
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,862✔
180
    SortedListComparator slc(column);
2,862✔
181
    IntegerColumn::const_iterator it_end = key_values.cend();
2,862✔
182
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, key_values);
2,862✔
183

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

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

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

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

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

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

223

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

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

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

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

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

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

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

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

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

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

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

283
        if (stored_key != key)
3,247,725✔
284
            return local_not_found;
709,068✔
285

286
        // Literal row index (tagged)
287
        if (ref & 1) {
2,538,657✔
288
            int64_t key_value = int64_t(ref >> 1);
1,064,760✔
289

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

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

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

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

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

321
        // Update 4 byte index key
322
        key = StringIndex::create_key(index_data, stringoffset);
1,457,127✔
323
    }
1,457,127✔
324
}
1,995,087✔
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
{
43,044✔
369
    if (column.full_word()) {
43,044✔
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,984✔
379

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

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

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

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

393
    // Copy all matches into result column
394
    size_t sz = result.size() + (upper - lower);
42,984✔
395
    result.reserve(sz);
42,984✔
396
    for (IntegerColumn::const_iterator it = lower; it != upper; ++it) {
830,514✔
397
        result.push_back(ObjKey(*it));
787,530✔
398
    }
787,530✔
399
}
42,984✔
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,288✔
471
        return m_items.empty();
3,066,288✔
472
    }
3,066,288✔
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,128✔
552

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

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

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

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

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

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

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

617
        if (is_inner_node) {
86,085✔
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));
86,079✔
627

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

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

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

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

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

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

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

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

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

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

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

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

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

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

833

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

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

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

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

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

899

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

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

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

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

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

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

933

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

968

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

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

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

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

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

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

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

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

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

1057
        bool noextend = old_offsets_size >= REALM_MAX_BPNODE_SIZE;
11,197,800✔
1058

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

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

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

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

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

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

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

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

1093
        return NodeChange(NodeChange::change_Split, get_ref(), new_list.get_ref());
2,147,485,306✔
1094
    }
2,147,485,318✔
1095
}
11,488,134✔
1096

1097

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

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

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

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

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

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

1127

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

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

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

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

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

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

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

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

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

1172
    if (ins_pos == keys.size()) {
11,181,984✔
1173
        if (noextend)
841,137✔
1174
            return false;
24✔
1175

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

1191
    key_type k = key_type(keys.get(ins_pos));
10,340,847✔
1192

1193
    // If key is not present we add it at the correct location
1194
    if (k != key) {
10,340,847✔
1195
        if (noextend)
1,341,024✔
1196
            return false;
645✔
1197

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

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

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

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

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

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

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

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

1280
            if (lower != it_end) {
2,071,710✔
1281
                Mixed lower_value = get(ObjKey(*lower));
2,068,620✔
1282
                if (lower_value == value) {
2,068,620✔
1283
                    return true;
2,067,771✔
1284
                }
2,067,771✔
1285
            }
2,068,620✔
1286
            return false;
3,939✔
1287
        };
2,071,710✔
1288

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

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

1337
    return true;
6,860,496✔
1338
}
8,932,200✔
1339

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

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

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

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

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

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

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

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

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

1477

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

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

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

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

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

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

1588

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

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

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

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

1604

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

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

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

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

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

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

1664
                if (sub_size == 1) {
348,834✔
1665
                    values.erase(pos);
2,889✔
1666
                    m_array->erase(pos_refs);
2,889✔
1667
                    sub.destroy();
2,889✔
1668
                }
2,889✔
1669
            }
348,834✔
1670
        }
1,058,508✔
1671
    }
1,897,689✔
1672
}
1,962,399✔
1673

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

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

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

1692
namespace {
1693

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

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

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

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

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

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

1761
} // anonymous namespace
1762

1763

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

1769

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

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

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

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

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

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

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

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

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

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

1851
            auto index_data = new_value.get_index_data(buffer);
641,190✔
1852
            insert_with_offset(key, index_data, new_value, 0); // Throws
641,190✔
1853
        }
641,190✔
1854
    }
696,750✔
1855
}
696,918✔
1856

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

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

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

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

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

1892

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

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

1925
    IntegerColumn::const_iterator it = key_values.cbegin();
19,623✔
1926
    IntegerColumn::const_iterator end = key_values.cend();
19,623✔
1927
    IntegerColumn::const_iterator first_greater = end;
19,623✔
1928
    while (it != end) {
200,856✔
1929
        Mixed val_it = m_column.get_value(ObjKey(*it));
196,806✔
1930
        int cmp = val_it.compare(value);
196,806✔
1931
        if (cmp == 0) {
196,806✔
1932
            return it;
15,573✔
1933
        }
15,573✔
1934
        if (cmp > 0 && first_greater == end) {
181,233✔
1935
            first_greater = it;
831✔
1936
        }
831✔
1937
        ++it;
181,233✔
1938
    }
181,233✔
1939
    return first_greater;
4,050✔
1940
}
19,623✔
1941

1942
// TODO: same as above, change to std::upper_bound(lower, it_end, value, slc);
1943
IntegerColumn::const_iterator SortedListComparator::find_end_of_unsorted(const Mixed& value,
1944
                                                                         const IntegerColumn& key_values,
1945
                                                                         IntegerColumn::const_iterator begin) const
1946
{
2,119,737✔
1947
    IntegerColumn::const_iterator end = key_values.cend();
2,119,737✔
1948
    if (begin != end && end - begin > 0) {
2,119,737✔
1949
        // optimization: check the last element first
1950
        Mixed last = m_column.get_value(ObjKey(*(key_values.cend() - 1)));
2,119,626✔
1951
        if (last == value) {
2,119,626✔
1952
            return key_values.cend();
2,118,486✔
1953
        }
2,118,486✔
1954
    }
2,119,626✔
1955
    while (begin != end) {
110,967✔
1956
        Mixed val_it = m_column.get_value(ObjKey(*begin));
110,967✔
1957
        if (value.compare(val_it) != 0) {
110,967✔
1958
            return begin;
1,317✔
1959
        }
1,317✔
1960
        ++begin;
109,650✔
1961
    }
109,650✔
1962
    return end;
4,294,967,294✔
1963
}
1,251✔
1964

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

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

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

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

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

2029
#ifdef REALM_DEBUG
2030

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

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

2043
        find_all(results, value);
2044

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

2053
namespace {
2054

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

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

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

2089
} // namespace
2090

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

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

2100
    out << std::hex;
×
2101

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

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

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

2153

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

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

2168
#endif // LCOV_EXCL_STOP ignore debug functions
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc