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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

86.59
/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/timestamp.hpp>
31
#include <realm/column_integer.hpp>
32
#include <realm/unicode.hpp>
33
#include <realm/tokenizer.hpp>
34

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

38
namespace {
39

40
void get_child(Array& parent, size_t child_ref_ndx, Array& child) noexcept
41
{
19,912,689✔
42
    ref_type child_ref = parent.get_as_ref(child_ref_ndx);
19,912,689✔
43
    child.init_from_ref(child_ref);
19,912,689✔
44
    child.set_parent(&parent, child_ref_ndx);
19,912,689✔
45
}
19,912,689✔
46

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

543✔
54
    size_t rest_len = 4;
1,086✔
55
    char* k = reinterpret_cast<char*>(&key);
1,086✔
56
    if (k[0] == 'X')
1,086✔
57
        rest_len = 3;
234✔
58
    else if (k[1] == 'X')
852✔
59
        rest_len = 2;
360✔
60
    else if (k[2] == 'X')
492✔
61
        rest_len = 1;
228✔
62
    else if (k[3] == 'X')
264✔
63
        rest_len = 0;
36✔
64

543✔
65
    REALM_ASSERT(offset + rest_len <= new_string.size());
1,086✔
66

543✔
67
    return StringData(new_string.data(), offset + rest_len);
1,086✔
68
}
1,086✔
69

70

71
} // anonymous namespace
72

73
DataType ClusterColumn::get_data_type() const
74
{
×
75
    const Table* table = m_cluster_tree->get_owning_table();
×
76
    return table->get_column_type(m_column_key);
×
77
}
×
78

79
Mixed ClusterColumn::get_value(ObjKey key) const
80
{
11,592,132✔
81
    const Obj obj{m_cluster_tree->get(key)};
11,592,132✔
82
    return obj.get_any(m_column_key);
11,592,132✔
83
}
11,592,132✔
84

85
std::vector<ObjKey> ClusterColumn::get_all_keys() const
86
{
18✔
87
    std::vector<ObjKey> ret;
18✔
88
    ret.reserve(m_cluster_tree->size());
18✔
89
    m_cluster_tree->traverse([&ret](const Cluster* cluster) {
18✔
90
        auto sz = cluster->node_size();
18✔
91
        for (size_t i = 0; i < sz; i++) {
198✔
92
            ret.push_back(cluster->get_real_key(i));
180✔
93
        }
180✔
94

9✔
95
        return IteratorControl::AdvanceToNext;
18✔
96
    });
18✔
97
    return ret;
18✔
98
}
18✔
99

100
template <>
101
int64_t IndexArray::from_list<index_FindFirst>(const Mixed& value, InternalFindResult& /* result_ref */,
102
                                               const IntegerColumn& key_values, const ClusterColumn& column) const
103
{
5,619✔
104
    SortedListComparator slc(column);
5,619✔
105

2,892✔
106
    IntegerColumn::const_iterator it_end = key_values.cend();
5,619✔
107
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, key_values);
5,619✔
108

2,892✔
109
    if (lower == it_end)
5,619✔
110
        return null_key.value;
24✔
111

2,880✔
112
    int64_t first_key_value = *lower;
5,595✔
113

2,880✔
114
    Mixed actual = column.get_value(ObjKey(first_key_value));
5,595✔
115
    if (actual != value)
5,595✔
116
        return null_key.value;
×
117

2,880✔
118
    return first_key_value;
5,595✔
119
}
5,595✔
120

121
template <>
122
int64_t IndexArray::from_list<index_Count>(const Mixed& value, InternalFindResult& /* result_ref */,
123
                                           const IntegerColumn& key_values, const ClusterColumn& column) const
124
{
8,199✔
125
    SortedListComparator slc(column);
8,199✔
126

4,107✔
127
    IntegerColumn::const_iterator it_end = key_values.cend();
8,199✔
128
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, key_values);
8,199✔
129
    if (lower == it_end)
8,199✔
130
        return 0;
24✔
131

4,095✔
132
    int64_t first_key_value = *lower;
8,175✔
133

4,095✔
134
    Mixed actual = column.get_value(ObjKey(first_key_value));
8,175✔
135
    if (actual != value)
8,175✔
136
        return 0;
24✔
137

4,083✔
138
    IntegerColumn::const_iterator upper = slc.find_end_of_unsorted(value, key_values, lower);
8,151✔
139
    int64_t cnt = upper - lower;
8,151✔
140

4,083✔
141
    return cnt;
8,151✔
142
}
8,151✔
143

144
template <>
145
int64_t IndexArray::from_list<index_FindAll_nocopy>(const Mixed& value, InternalFindResult& result_ref,
146
                                                    const IntegerColumn& key_values,
147
                                                    const ClusterColumn& column) const
148
{
2,571✔
149
    SortedListComparator slc(column);
2,571✔
150
    IntegerColumn::const_iterator it_end = key_values.cend();
2,571✔
151
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, key_values);
2,571✔
152

1,293✔
153
    if (lower == it_end)
2,571✔
154
        return size_t(FindRes_not_found);
51✔
155

1,269✔
156
    ObjKey first_key = ObjKey(*lower);
2,520✔
157

1,269✔
158
    Mixed actual = column.get_value(ObjKey(first_key));
2,520✔
159
    if (actual != value)
2,520✔
160
        return size_t(FindRes_not_found);
66✔
161

1,233✔
162
    // Optimization: check the last entry before trying upper bound.
1,233✔
163
    IntegerColumn::const_iterator upper = it_end;
2,454✔
164
    --upper;
2,454✔
165
    // Single result if upper matches lower
1,233✔
166
    if (upper == lower) {
2,454✔
167
        result_ref.payload = *lower;
342✔
168
        return size_t(FindRes_single);
342✔
169
    }
342✔
170

1,062✔
171
    // Check string value at upper, if equal return matches in (lower, upper]
1,062✔
172
    ObjKey last_key = ObjKey(*upper);
2,112✔
173
    actual = column.get_value(ObjKey(last_key));
2,112✔
174
    if (actual == value) {
2,112✔
175
        result_ref.payload = from_ref(key_values.get_ref());
1,431✔
176
        result_ref.start_ndx = lower.get_position();
1,431✔
177
        result_ref.end_ndx = upper.get_position() + 1; // one past last match
1,431✔
178
        return size_t(FindRes_column);
1,431✔
179
    }
1,431✔
180

339✔
181
    // Last result is not equal, find the upper bound of the range of results.
339✔
182
    // Note that we are passing upper which is cend() - 1 here as we already
339✔
183
    // checked the last item manually.
339✔
184
    upper = slc.find_end_of_unsorted(value, key_values, lower);
681✔
185

339✔
186
    result_ref.payload = from_ref(key_values.get_ref());
681✔
187
    result_ref.start_ndx = lower.get_position();
681✔
188
    result_ref.end_ndx = upper.get_position();
681✔
189
    return size_t(FindRes_column);
681✔
190
}
681✔
191

192

193
template <IndexMethod method>
194
int64_t IndexArray::index_string(const Mixed& value, InternalFindResult& result_ref,
195
                                 const ClusterColumn& column) const
196
{
2,260,236✔
197
    // Return`realm::not_found`, or an index to the (any) match
1,123,815✔
198
    constexpr bool first(method == index_FindFirst);
2,260,236✔
199
    // Return 0, or the number of items that match the specified `value`
1,123,815✔
200
    constexpr bool get_count(method == index_Count);
2,260,236✔
201
    // Same as `index_FindAll` but does not copy matching rows into `column`
1,123,815✔
202
    // returns FindRes_not_found if there are no matches
1,123,815✔
203
    // returns FindRes_single and the row index (literal) in result_ref.payload
1,123,815✔
204
    // or returns FindRes_column and the reference to a column of duplicates in
1,123,815✔
205
    // result_ref.result with the results in the bounds start_ndx, and end_ndx
1,123,815✔
206
    constexpr bool allnocopy(method == index_FindAll_nocopy);
2,260,236✔
207

1,123,815✔
208
    constexpr int64_t local_not_found = allnocopy ? int64_t(FindRes_not_found) : first ? null_key.value : 0;
2,260,236✔
209

1,123,815✔
210
    const char* data = m_data;
2,260,236✔
211
    const char* header;
2,260,236✔
212
    uint_least8_t width = m_width;
2,260,236✔
213
    bool is_inner_node = m_is_inner_bptree_node;
2,260,236✔
214
    typedef StringIndex::key_type key_type;
2,260,236✔
215
    size_t stringoffset = 0;
2,260,236✔
216

1,123,815✔
217
    StringConversionBuffer buffer;
2,260,236✔
218
    StringData index_data = value.get_index_data(buffer);
2,260,236✔
219

1,123,815✔
220
    // Create 4 byte index key
1,123,815✔
221
    key_type key = StringIndex::create_key(index_data, stringoffset);
2,260,236✔
222

1,123,815✔
223
    for (;;) {
2,779,245✔
224
        // Get subnode table
1,381,554✔
225
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
2,779,245✔
226

1,381,554✔
227
        // Find the position matching the key
1,381,554✔
228
        const char* offsets_header = m_alloc.translate(offsets_ref);
2,779,245✔
229
        const char* offsets_data = get_data_from_header(offsets_header);
2,779,245✔
230
        size_t offsets_size = get_size_from_header(offsets_header);
2,779,245✔
231
        size_t pos = ::lower_bound<32>(offsets_data, offsets_size, key); // keys are always 32 bits wide
2,779,245✔
232

1,381,554✔
233
        // If key is outside range, we know there can be no match
1,381,554✔
234
        if (pos == offsets_size)
2,779,245✔
235
            return local_not_found;
904,314✔
236

931,842✔
237
        // Get entry under key
931,842✔
238
        size_t pos_refs = pos + 1; // first entry in refs points to offsets
1,874,931✔
239
        uint64_t ref = get_direct(data, width, pos_refs);
1,874,931✔
240

931,842✔
241
        if (is_inner_node) {
1,874,931✔
242
            // Set vars for next iteration
26,652✔
243
            header = m_alloc.translate(to_ref(ref));
62,259✔
244
            data = get_data_from_header(header);
62,259✔
245
            width = get_width_from_header(header);
62,259✔
246
            is_inner_node = get_is_inner_bptree_node_from_header(header);
62,259✔
247
            continue;
62,259✔
248
        }
62,259✔
249

905,190✔
250
        key_type stored_key = key_type(get_direct<32>(offsets_data, pos));
1,812,672✔
251

905,190✔
252
        if (stored_key != key)
1,812,672✔
253
            return local_not_found;
278,253✔
254

774,783✔
255
        // Literal row index (tagged)
774,783✔
256
        if (ref & 1) {
1,534,419✔
257
            int64_t key_value = int64_t(ref >> 1);
1,061,079✔
258

535,317✔
259
            Mixed a = column.is_fulltext() ? reconstruct_string(stringoffset, key, index_data)
535,401✔
260
                                           : column.get_value(ObjKey(key_value));
1,060,995✔
261
            if (a == value) {
1,061,079✔
262
                result_ref.payload = key_value;
1,058,487✔
263
                return first ? key_value : get_count ? 1 : FindRes_single;
1,051,077✔
264
            }
1,058,487✔
265
            return local_not_found;
2,592✔
266
        }
2,592✔
267

239,466✔
268
        const char* sub_header = m_alloc.translate(ref_type(ref));
473,340✔
269
        const bool sub_isindex = get_context_flag_from_header(sub_header);
473,340✔
270

239,466✔
271
        // List of row indices with common prefix up to this point, in sorted order.
239,466✔
272
        if (!sub_isindex) {
473,340✔
273
            const IntegerColumn sub(m_alloc, ref_type(ref));
16,677✔
274
            if (column.is_fulltext()) {
16,677✔
275
                result_ref.payload = ref;
288✔
276
                result_ref.start_ndx = 0;
288✔
277
                result_ref.end_ndx = sub.size();
288✔
278
                return FindRes_column;
288✔
279
            }
288✔
280

8,292✔
281
            return from_list<method>(value, result_ref, sub, column);
16,389✔
282
        }
16,389✔
283

231,030✔
284
        // Recurse into sub-index;
231,030✔
285
        header = sub_header;
456,663✔
286
        data = get_data_from_header(header);
456,663✔
287
        width = get_width_from_header(header);
456,663✔
288
        is_inner_node = get_is_inner_bptree_node_from_header(header);
456,663✔
289

231,030✔
290
        // Go to next key part of the string. If the offset exceeds the string length, the key will be 0
231,030✔
291
        stringoffset += 4;
456,663✔
292

231,030✔
293
        // Update 4 byte index key
231,030✔
294
        key = StringIndex::create_key(index_data, stringoffset);
456,663✔
295
    }
456,663✔
296
}
2,260,236✔
297

298

299
void IndexArray::from_list_all_ins(StringData upper_value, std::vector<ObjKey>& result, const IntegerColumn& rows,
300
                                   const ClusterColumn& column) const
301
{
1,422✔
302
    // optimization for the most common case, where all the strings under a given subindex are equal
711✔
303
    Mixed first = column.get_value(ObjKey(*rows.cbegin()));
1,422✔
304
    Mixed last = column.get_value(ObjKey(*(rows.cend() - 1)));
1,422✔
305
    if (first == last) {
1,422✔
306
        if (!first.is_type(type_String))
102✔
307
            return;
×
308
        auto first_str_upper = case_map(first.get_string(), true);
102✔
309
        if (first_str_upper != upper_value) {
102✔
310
            return;
24✔
311
        }
24✔
312

39✔
313
        size_t sz = result.size() + rows.size();
78✔
314
        result.reserve(sz);
78✔
315
        for (IntegerColumn::const_iterator it = rows.cbegin(); it != rows.cend(); ++it) {
246✔
316
            result.push_back(ObjKey(*it));
168✔
317
        }
168✔
318
        return;
78✔
319
    }
78✔
320

660✔
321
    // special case for very long strings, where they might have a common prefix and end up in the
660✔
322
    // same subindex column, but still not be identical
660✔
323
    for (IntegerColumn::const_iterator it = rows.cbegin(); it != rows.cend(); ++it) {
66,792✔
324
        ObjKey key = ObjKey(*it);
65,472✔
325
        Mixed val = column.get_value(key);
65,472✔
326
        if (val.is_type(type_String)) {
65,472✔
327
            auto upper_str = case_map(val.get_string(), true);
65,472✔
328
            if (upper_str == upper_value) {
65,472✔
329
                result.push_back(key);
1,440✔
330
            }
1,440✔
331
        }
65,472✔
332
    }
65,472✔
333

660✔
334
    return;
1,320✔
335
}
1,320✔
336

337

338
void IndexArray::from_list_all(const Mixed& value, std::vector<ObjKey>& result, const IntegerColumn& rows,
339
                               const ClusterColumn& column) const
340
{
40,995✔
341
    SortedListComparator slc(column);
40,995✔
342

19,863✔
343
    IntegerColumn::const_iterator it_end = rows.cend();
40,995✔
344
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, rows);
40,995✔
345
    if (lower == it_end)
40,995✔
346
        return;
×
347

19,863✔
348
    ObjKey key = ObjKey(*lower);
40,995✔
349

19,863✔
350
    Mixed a = column.get_value(key);
40,995✔
351
    if (a != value)
40,995✔
352
        return;
×
353

19,863✔
354
    IntegerColumn::const_iterator upper = slc.find_end_of_unsorted(value, rows, lower);
40,995✔
355

19,863✔
356
    // Copy all matches into result column
19,863✔
357
    size_t sz = result.size() + (upper - lower);
40,995✔
358
    result.reserve(sz);
40,995✔
359
    for (IntegerColumn::const_iterator it = lower; it != upper; ++it) {
736,773✔
360
        result.push_back(ObjKey(*it));
695,778✔
361
    }
695,778✔
362

19,863✔
363
    return;
40,995✔
364
}
40,995✔
365

366

367
namespace {
368

369
// Helper functions for SearchList (index_string_all_ins) for generating permutations of index keys
370

371
// replicates the 4 least significant bits each times 8
372
// eg: abcd -> aaaaaaaabbbbbbbbccccccccdddddddd
373
uint32_t replicate_4_lsb_x8(uint32_t i)
374
{
3,123,744✔
375
    REALM_ASSERT_DEBUG(i <= 15);
3,123,744✔
376
    i *= 0x204081;
3,123,744✔
377
    i &= 0x1010101;
3,123,744✔
378
    i *= 0xff;
3,123,744✔
379
    return i;
3,123,744✔
380
}
3,123,744✔
381

382
int32_t select_from_mask(int32_t a, int32_t b, int32_t mask) {
3,123,744✔
383
    return a ^ ((a ^ b) & mask);
3,123,744✔
384
}
3,123,744✔
385

386
// Given upper and lower keys: "ABCD" and "abcd", the 4 LSBs in the permutation argument determine the
387
// final key:
388
// Permutation 0  = "ABCD"
389
// Permutation 1  = "ABCd"
390
// Permutation 8  = "aBCD"
391
// Permutation 15 = "abcd"
392
using key_type = StringIndex::key_type;
393
static key_type generate_key(key_type upper, key_type lower, int permutation)
394
{
3,123,744✔
395
    return select_from_mask(upper, lower, replicate_4_lsb_x8(permutation));
3,123,744✔
396
}
3,123,744✔
397

398

399
// Helper structure for IndexArray::index_string_all_ins to generate and keep track of search key permutations,
400
// when traversing the trees.
401
struct SearchList {
402
    struct Item {
403
        const char* header;
404
        size_t string_offset;
405
        key_type key;
406
    };
407

408
    SearchList(const util::Optional<std::string>& upper_value, const util::Optional<std::string>& lower_value)
409
        : m_upper_value(upper_value)
410
        , m_lower_value(lower_value)
411
    {
6,582✔
412
        m_keys_seen.reserve(num_permutations);
6,582✔
413
    }
6,582✔
414

415
    // Add all unique keys for this level to the internal work stack
416
    void add_all_for_level(const char* header, size_t string_offset)
417
    {
195,234✔
418
        m_keys_seen.clear();
195,234✔
419
        const key_type upper_key = StringIndex::create_key(m_upper_value, string_offset);
195,234✔
420
        const key_type lower_key = StringIndex::create_key(m_lower_value, string_offset);
195,234✔
421
        for (int p = 0; p < num_permutations; ++p) {
3,318,978✔
422
            // FIXME: This might still be incorrect due to multi-byte unicode characters (crossing the 4 byte key
1,561,872✔
423
            // size) being combined incorrectly.
1,561,872✔
424
            const key_type key = generate_key(upper_key, lower_key, p);
3,123,744✔
425
            const bool new_key = std::find(m_keys_seen.cbegin(), m_keys_seen.cend(), key) == m_keys_seen.cend();
3,123,744✔
426
            if (new_key) {
3,123,744✔
427
                m_keys_seen.push_back(key);
3,059,706✔
428
                add_next(header, string_offset, key);
3,059,706✔
429
            }
3,059,706✔
430
        }
3,123,744✔
431
    }
195,234✔
432

433
    bool empty() const
434
    {
3,066,288✔
435
        return m_items.empty();
3,066,288✔
436
    }
3,066,288✔
437

438
    Item get_next()
439
    {
3,059,706✔
440
        Item item = m_items.back();
3,059,706✔
441
        m_items.pop_back();
3,059,706✔
442
        return item;
3,059,706✔
443
    }
3,059,706✔
444

445
    // Add a single entry to the internal work stack. Used to traverse the inner trees (same key)
446
    void add_next(const char* header, size_t string_offset, key_type key)
447
    {
3,059,706✔
448
        m_items.push_back({header, string_offset, key});
3,059,706✔
449
    }
3,059,706✔
450

451
private:
452
    static constexpr int num_permutations = 1 << sizeof(key_type); // 4 bytes gives up to 16 search keys
453

454
    std::vector<Item> m_items;
455

456
    const util::Optional<std::string> m_upper_value;
457
    const util::Optional<std::string> m_lower_value;
458

459
    std::vector<key_type> m_keys_seen;
460
};
461

462

463
} // namespace
464

465

466
void IndexArray::index_string_all_ins(StringData value, std::vector<ObjKey>& result,
467
                                      const ClusterColumn& column) const
468
{
6,582✔
469
    REALM_ASSERT(!value.is_null());
6,582✔
470

3,291✔
471
    const util::Optional<std::string> upper_value = case_map(value, true);
6,582✔
472
    const util::Optional<std::string> lower_value = case_map(value, false);
6,582✔
473
    SearchList search_list(upper_value, lower_value);
6,582✔
474

3,291✔
475
    const char* top_header = get_header_from_data(m_data);
6,582✔
476
    search_list.add_all_for_level(top_header, 0);
6,582✔
477

3,291✔
478
    while (!search_list.empty()) {
3,066,288✔
479
        SearchList::Item item = search_list.get_next();
3,059,706✔
480

1,529,853✔
481
        const char* const header = item.header;
3,059,706✔
482
        const size_t string_offset = item.string_offset;
3,059,706✔
483
        const key_type key = item.key;
3,059,706✔
484
        const char* const data = get_data_from_header(header);
3,059,706✔
485
        const uint_least8_t width = get_width_from_header(header);
3,059,706✔
486
        const bool is_inner_node = get_is_inner_bptree_node_from_header(header);
3,059,706✔
487

1,529,853✔
488
        // Get subnode table
1,529,853✔
489
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
3,059,706✔
490

1,529,853✔
491
        // Find the position matching the key
1,529,853✔
492
        const char* const offsets_header = m_alloc.translate(offsets_ref);
3,059,706✔
493
        const char* const offsets_data = get_data_from_header(offsets_header);
3,059,706✔
494
        const size_t offsets_size = get_size_from_header(offsets_header);
3,059,706✔
495
        const size_t pos = ::lower_bound<32>(offsets_data, offsets_size, key); // keys are always 32 bits wide
3,059,706✔
496

1,529,853✔
497
        // If key is outside range, we know there can be no match
1,529,853✔
498
        if (pos == offsets_size)
3,059,706✔
499
            continue;
858✔
500

1,529,424✔
501
        // Get entry under key
1,529,424✔
502
        const size_t pos_refs = pos + 1; // first entry in refs points to offsets
3,058,848✔
503
        const uint64_t ref = get_direct(data, width, pos_refs);
3,058,848✔
504

1,529,424✔
505
        if (is_inner_node) {
3,058,848✔
506
            // Set vars for next iteration
507
            const char* const inner_header = m_alloc.translate(to_ref(ref));
×
508
            search_list.add_next(inner_header, string_offset, key);
×
509
            continue;
×
510
        }
×
511

1,529,424✔
512
        const key_type stored_key = key_type(get_direct<32>(offsets_data, pos));
3,058,848✔
513

1,529,424✔
514
        if (stored_key != key)
3,058,848✔
515
            continue;
2,863,128✔
516

97,860✔
517
        // Literal row index (tagged)
97,860✔
518
        if (ref & 1) {
195,720✔
519
            ObjKey k(int64_t(ref >> 1));
5,646✔
520

2,823✔
521
            // The buffer is needed when for when this is an integer index.
2,823✔
522
            StringConversionBuffer buffer;
5,646✔
523
            const StringData str = column.get_value(k).get_index_data(buffer);
5,646✔
524
            const util::Optional<std::string> upper_str = case_map(str, true);
5,646✔
525
            if (upper_str == upper_value) {
5,646✔
526
                result.push_back(k);
5,598✔
527
            }
5,598✔
528
            continue;
5,646✔
529
        }
5,646✔
530

95,037✔
531
        const char* const sub_header = m_alloc.translate(ref_type(ref));
190,074✔
532
        const bool sub_isindex = get_context_flag_from_header(sub_header);
190,074✔
533

95,037✔
534
        // List of row indices with common prefix up to this point, in sorted order.
95,037✔
535
        if (!sub_isindex) {
190,074✔
536
            const IntegerColumn sub(m_alloc, ref_type(ref));
1,422✔
537
            from_list_all_ins(upper_value, result, sub, column);
1,422✔
538
            continue;
1,422✔
539
        }
1,422✔
540

94,326✔
541
        // Recurse into sub-index;
94,326✔
542
        search_list.add_all_for_level(sub_header, string_offset + 4);
188,652✔
543
    }
188,652✔
544

3,291✔
545
    // sort the result and return a std::vector
3,291✔
546
    std::sort(result.begin(), result.end());
6,582✔
547
}
6,582✔
548

549

550
void IndexArray::index_string_all(const Mixed& value, std::vector<ObjKey>& result, const ClusterColumn& column) const
551
{
48,936✔
552
    const char* data = m_data;
48,936✔
553
    const char* header;
48,936✔
554
    uint_least8_t width = m_width;
48,936✔
555
    bool is_inner_node = m_is_inner_bptree_node;
48,936✔
556
    size_t stringoffset = 0;
48,936✔
557

23,997✔
558
    StringConversionBuffer buffer;
48,936✔
559
    StringData index_data = value.get_index_data(buffer);
48,936✔
560
    // Create 4 byte index key
23,997✔
561
    key_type key = StringIndex::create_key(index_data, stringoffset);
48,936✔
562

23,997✔
563
    for (;;) {
77,034✔
564
        // Get subnode table
36,603✔
565
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
77,034✔
566

36,603✔
567
        // Find the position matching the key
36,603✔
568
        const char* offsets_header = m_alloc.translate(offsets_ref);
77,034✔
569
        const char* offsets_data = get_data_from_header(offsets_header);
77,034✔
570
        size_t offsets_size = get_size_from_header(offsets_header);
77,034✔
571
        size_t pos = ::lower_bound<32>(offsets_data, offsets_size, key); // keys are always 32 bits wide
77,034✔
572

36,603✔
573
        // If key is outside range, we know there can be no match
36,603✔
574
        if (pos == offsets_size)
77,034✔
575
            return;
×
576

36,603✔
577
        // Get entry under key
36,603✔
578
        size_t pos_refs = pos + 1; // first entry in refs points to offsets
77,034✔
579
        uint64_t ref = get_direct(data, width, pos_refs);
77,034✔
580

36,603✔
581
        if (is_inner_node) {
77,034✔
582
            // Set vars for next iteration
583
            header = m_alloc.translate(ref_type(ref));
×
584
            data = get_data_from_header(header);
×
585
            width = get_width_from_header(header);
×
586
            is_inner_node = get_is_inner_bptree_node_from_header(header);
×
587
            continue;
×
588
        }
×
589

36,603✔
590
        key_type stored_key = key_type(get_direct<32>(offsets_data, pos));
77,034✔
591

36,603✔
592
        if (stored_key != key)
77,034✔
593
            return;
6,120✔
594

33,543✔
595
        // Literal row index (tagged)
33,543✔
596
        if (ref & 1) {
70,914✔
597
            ObjKey k(int64_t(ref >> 1));
1,821✔
598

1,074✔
599
            Mixed a = column.get_value(k);
1,821✔
600
            if (a == value) {
1,821✔
601
                result.push_back(k);
1,821✔
602
                return;
1,821✔
603
            }
1,821✔
604
            return;
×
605
        }
×
606

32,469✔
607
        const char* sub_header = m_alloc.translate(ref_type(ref));
69,093✔
608
        const bool sub_isindex = get_context_flag_from_header(sub_header);
69,093✔
609

32,469✔
610
        // List of row indices with common prefix up to this point, in sorted order.
32,469✔
611
        if (!sub_isindex) {
69,093✔
612
            const IntegerColumn sub(m_alloc, ref_type(ref));
40,995✔
613
            return from_list_all(value, result, sub, column);
40,995✔
614
        }
40,995✔
615

12,606✔
616
        // Recurse into sub-index;
12,606✔
617
        header = sub_header;
28,098✔
618
        data = get_data_from_header(header);
28,098✔
619
        width = get_width_from_header(header);
28,098✔
620
        is_inner_node = get_is_inner_bptree_node_from_header(header);
28,098✔
621

12,606✔
622
        // Go to next key part of the string. If the offset exceeds the string length, the key will be 0
12,606✔
623
        stringoffset += 4;
28,098✔
624

12,606✔
625
        // Update 4 byte index key
12,606✔
626
        key = StringIndex::create_key(index_data, stringoffset);
28,098✔
627
    }
28,098✔
628
}
48,936✔
629

630
static void get_all_keys_below(std::set<int64_t>& result, ref_type ref, Allocator& alloc)
631
{
72✔
632
    const char* sub_header = alloc.translate(ref_type(ref));
72✔
633
    const bool sub_isindex = NodeHeader::get_context_flag_from_header(sub_header);
72✔
634

36✔
635
    if (sub_isindex) {
72✔
636
        Array tree(alloc);
36✔
637
        tree.init_from_ref(ref);
36✔
638
        auto sz = tree.size();
36✔
639
        for (size_t n = 1; n < sz; n++) {
90✔
640
            auto rot = tree.get_as_ref_or_tagged(n);
54✔
641
            // Literal row index (tagged)
27✔
642
            if (rot.is_tagged()) {
54✔
643
                result.insert(rot.get_as_int());
30✔
644
            }
30✔
645
            else {
24✔
646
                get_all_keys_below(result, rot.get_as_ref(), alloc);
24✔
647
            }
24✔
648
        }
54✔
649
    }
36✔
650
    else {
36✔
651
        IntegerColumn tree(alloc, ref);
36✔
652
        tree.for_all([&result](int64_t i) {
84✔
653
            result.insert(i);
84✔
654
        });
84✔
655
    }
36✔
656
}
72✔
657

658
void IndexArray::_index_string_find_all_prefix(std::set<int64_t>& result, StringData str, const char* header) const
659
{
48✔
660
    size_t stringoffset = 0;
48✔
661

24✔
662
    for (;;) {
72✔
663
        const char* data = NodeHeader::get_data_from_header(header);
72✔
664
        uint_least8_t width = get_width_from_header(header);
72✔
665

36✔
666
        // Create 4 byte lower and upper key
36✔
667
        key_type lower = 0;
72✔
668
        size_t i = 0;
72✔
669
        size_t n = str.size() - stringoffset;
72✔
670
        bool is_at_string_end = (n <= 4);
72✔
671
        if (!is_at_string_end) {
72✔
672
            n = 4;
24✔
673
        }
24✔
674
        while (i < n) {
282✔
675
            lower <<= 8;
210✔
676
            lower += str[stringoffset + i++];
210✔
677
        }
210✔
678
        size_t shift = (4 - i) * 8;
72✔
679
        key_type upper = lower + 1;
72✔
680
        lower <<= shift;
72✔
681
        upper <<= shift;
72✔
682
        upper--;
72✔
683

36✔
684
        // Get index array
36✔
685
        ref_type offsets_ref = to_ref(get_direct(data, width, 0));
72✔
686

36✔
687
        // Find the position matching the key
36✔
688
        const char* offsets_header = m_alloc.translate(offsets_ref);
72✔
689
        const char* offsets_data = get_data_from_header(offsets_header);
72✔
690
        size_t offsets_size = get_size_from_header(offsets_header);
72✔
691
        size_t pos = ::lower_bound<32>(offsets_data, offsets_size, lower); // keys are always 32 bits wide
72✔
692
        // If key is outside range, we know there can be no match
36✔
693
        if (pos == offsets_size)
72✔
694
            return;
×
695

36✔
696
        size_t pos_refs = pos + 1; // first entry in refs points to offsets
72✔
697

36✔
698
        if (NodeHeader::get_is_inner_bptree_node_from_header(header)) {
72✔
699
            bool done = false;
×
700
            while (!done) {
×
701
                // Recursively call with child node
702
                const char* header = m_alloc.translate(to_ref(get_direct(data, width, pos_refs++)));
×
703
                _index_string_find_all_prefix(result, str, header);
×
704

705
                // Check if current node is past end of key range or last node
706
                auto key = get_direct<32>(offsets_data, pos++);
×
707
                done = key > upper || pos == offsets_size;
×
708
            }
×
709
            return;
×
710
        }
×
711

36✔
712
        size_t end = ::upper_bound<32>(offsets_data, offsets_size, upper); // keys are always 32 bits wide
72✔
713
        if (is_at_string_end) {
72✔
714
            // now get all entries from start to end
24✔
715
            for (size_t ndx = pos_refs; ndx <= end; ndx++) {
108✔
716
                uint64_t ref = get_direct(data, width, ndx);
60✔
717
                // Literal row index (tagged)
30✔
718
                if (ref & 1) {
60✔
719
                    result.emplace(int64_t(ref >> 1));
12✔
720
                }
12✔
721
                else {
48✔
722
                    get_all_keys_below(result, to_ref(ref), m_alloc);
48✔
723
                }
48✔
724
            }
60✔
725
            return;
48✔
726
        }
48✔
727

12✔
728
        // When we are not at end of string then we are comparing against the whole key
12✔
729
        // and we can have at most one match
12✔
730
        REALM_ASSERT(end == pos + 1);
24✔
731
        header = m_alloc.translate(to_ref(get_direct(data, width, pos_refs)));
24✔
732
        stringoffset += 4;
24✔
733
    }
24✔
734
}
48✔
735

736
ObjKey IndexArray::index_string_find_first(const Mixed& value, const ClusterColumn& column) const
737
{
1,591,893✔
738
    InternalFindResult unused;
1,591,893✔
739
    return ObjKey(index_string<index_FindFirst>(value, unused, column));
1,591,893✔
740
}
1,591,893✔
741

742

743
void IndexArray::index_string_find_all(std::vector<ObjKey>& result, const Mixed& value, const ClusterColumn& column,
744
                                       bool case_insensitive) const
745
{
55,518✔
746
    if (case_insensitive && value.is_type(type_String)) {
55,518✔
747
        index_string_all_ins(value.get_string(), result, column);
6,582✔
748
    }
6,582✔
749
    else {
48,936✔
750
        index_string_all(value, result, column);
48,936✔
751
    }
48,936✔
752
}
55,518✔
753

754
FindRes IndexArray::index_string_find_all_no_copy(const Mixed& value, const ClusterColumn& column,
755
                                                  InternalFindResult& result) const
756
{
655,482✔
757
    return static_cast<FindRes>(index_string<index_FindAll_nocopy>(value, result, column));
655,482✔
758
}
655,482✔
759

760
size_t IndexArray::index_string_count(const Mixed& value, const ClusterColumn& column) const
761
{
12,864✔
762
    InternalFindResult unused;
12,864✔
763
    return to_size_t(index_string<index_Count>(value, unused, column));
12,864✔
764
}
12,864✔
765

766
std::unique_ptr<IndexArray> StringIndex::create_node(Allocator& alloc, bool is_leaf)
767
{
177,798✔
768
    Array::Type type = is_leaf ? Array::type_HasRefs : Array::type_InnerBptreeNode;
177,759✔
769
    std::unique_ptr<IndexArray> top(new IndexArray(alloc)); // Throws
177,798✔
770
    top->create(type);                                      // Throws
177,798✔
771

86,946✔
772
    // Mark that this is part of index
86,946✔
773
    // (as opposed to columns under leaves)
86,946✔
774
    top->set_context_flag(true);
177,798✔
775

86,946✔
776
    // Add subcolumns for leaves
86,946✔
777
    Array values(alloc);
177,798✔
778
    values.create(Array::type_Normal);       // Throws
177,798✔
779
    values.ensure_minimum_width(0x7FFFFFFF); // This ensures 31 bits plus a sign bit
177,798✔
780
    top->add(values.get_ref());              // first entry in refs points to offsets
177,798✔
781

86,946✔
782
    return top;
177,798✔
783
}
177,798✔
784

785
StringIndex::key_type StringIndex::get_last_key() const
786
{
110,751✔
787
    Array offsets(m_array->get_alloc());
110,751✔
788
    get_child(*m_array, 0, offsets);
110,751✔
789
    return key_type(offsets.back());
110,751✔
790
}
110,751✔
791

792

793
void StringIndex::insert_with_offset(ObjKey obj_key, StringData index_data, const Mixed& value, size_t offset)
794
{
9,255,603✔
795
    // Create 4 byte index key
4,608,222✔
796
    key_type key = create_key(index_data, offset);
9,255,603✔
797
    TreeInsert(obj_key, key, offset, index_data, value); // Throws
9,255,603✔
798
}
9,255,603✔
799

800
void StringIndex::insert_to_existing_list_at_lower(ObjKey key, Mixed value, IntegerColumn& list,
801
                                                   const IntegerColumnIterator& lower)
802
{
2,066,310✔
803
    SortedListComparator slc(m_target_column);
2,066,310✔
804
    // At this point there exists duplicates of this value, we need to
1,032,054✔
805
    // insert value beside it's duplicates so that rows are also sorted
1,032,054✔
806
    // in ascending order.
1,032,054✔
807
    IntegerColumn::const_iterator upper = slc.find_end_of_unsorted(value, list, lower);
2,066,310✔
808
    // find insert position (the list has to be kept in sorted order)
1,032,054✔
809
    // In most cases the refs will be added to the end. So we test for that
1,032,054✔
810
    // first to see if we can avoid the binary search for insert position
1,032,054✔
811
    IntegerColumn::const_iterator last = upper - ptrdiff_t(1);
2,066,310✔
812
    int64_t last_key_value = *last;
2,066,310✔
813
    if (key.value >= last_key_value) {
2,066,310✔
814
        list.insert(upper.get_position(), key.value);
2,030,451✔
815
    }
2,030,451✔
816
    else {
35,859✔
817
        // insert into the group of duplicates, keeping object keys sorted
17,568✔
818
        IntegerColumn::const_iterator inner_lower = std::lower_bound(lower, upper, key.value);
35,859✔
819
        list.insert(inner_lower.get_position(), key.value);
35,859✔
820
    }
35,859✔
821
}
2,066,310✔
822

823
void StringIndex::insert_to_existing_list(ObjKey key, Mixed value, IntegerColumn& list)
824
{
2,004✔
825
    SortedListComparator slc(m_target_column);
2,004✔
826
    IntegerColumn::const_iterator it_end = list.cend();
2,004✔
827
    IntegerColumn::const_iterator lower = slc.find_start_of_unsorted(value, list);
2,004✔
828

1,029✔
829
    if (lower == it_end) {
2,004✔
830
        // Not found and everything is less, just append it to the end.
816✔
831
        list.add(key.value);
1,584✔
832
    }
1,584✔
833
    else {
420✔
834
        ObjKey lower_key = ObjKey(*lower);
420✔
835
        Mixed lower_value = get(lower_key);
420✔
836

213✔
837
        if (lower_value != value) {
420✔
838
            list.insert(lower.get_position(), key.value);
420✔
839
        }
420✔
840
        else {
×
841
            // At this point there exists duplicates of this value, we need to
842
            // insert value beside it's duplicates so that rows are also sorted
843
            // in ascending order.
844
            insert_to_existing_list_at_lower(key, value, list, lower);
×
845
        }
×
846
    }
420✔
847
}
2,004✔
848

849

850
void StringIndex::insert_row_list(size_t ref, size_t offset, StringData index_data)
851
{
2,148✔
852
    REALM_ASSERT(!m_array->is_inner_bptree_node()); // only works in leaves
2,148✔
853

972✔
854
    // Create 4 byte index key
972✔
855
    key_type key = create_key(index_data, offset);
2,148✔
856

972✔
857
    // Get subnode table
972✔
858
    Allocator& alloc = m_array->get_alloc();
2,148✔
859
    Array values(alloc);
2,148✔
860
    get_child(*m_array, 0, values);
2,148✔
861
    REALM_ASSERT(m_array->size() == values.size() + 1);
2,148✔
862

972✔
863
    size_t ins_pos = values.lower_bound_int(key);
2,148✔
864
    if (ins_pos == values.size()) {
2,148✔
865
        // When key is outside current range, we can just add it
972✔
866
        values.add(key);
2,148✔
867
        m_array->add(ref);
2,148✔
868
        return;
2,148✔
869
    }
2,148✔
870

871
#ifdef REALM_DEBUG // LCOV_EXCL_START ignore debug code
×
872
    // Since we only use this for moving existing values to new
873
    // subindexes, there should never be an existing match.
874
    key_type k = key_type(values.get(ins_pos));
×
875
    REALM_ASSERT(k != key);
×
876
#endif // LCOV_EXCL_STOP ignore debug code
×
877

878
    // If key is not present we add it at the correct location
879
    values.insert(ins_pos, key);
×
880
    m_array->insert(ins_pos + 1, ref);
×
881
}
×
882

883

884
void StringIndex::TreeInsert(ObjKey obj_key, key_type key, size_t offset, StringData index_data, const Mixed& value)
885
{
9,259,842✔
886
    NodeChange nc = do_insert(obj_key, key, offset, index_data, value);
9,259,842✔
887
    switch (nc.type) {
9,259,842✔
888
        case NodeChange::change_None:
9,260,958✔
889
            return;
9,260,958✔
890
        case NodeChange::change_InsertBefore: {
✔
891
            StringIndex new_node(inner_node_tag(), m_array->get_alloc());
×
892
            new_node.node_add_key(nc.ref1);
×
893
            new_node.node_add_key(get_ref());
×
894
            m_array->init_from_ref(new_node.get_ref());
×
895
            m_array->update_parent();
×
896
            return;
×
897
        }
×
898
        case NodeChange::change_InsertAfter: {
9✔
899
            StringIndex new_node(inner_node_tag(), m_array->get_alloc());
9✔
900
            new_node.node_add_key(get_ref());
9✔
901
            new_node.node_add_key(nc.ref1);
9✔
902
            m_array->init_from_ref(new_node.get_ref());
9✔
903
            m_array->update_parent();
9✔
904
            return;
9✔
905
        }
×
906
        case NodeChange::change_Split: {
63✔
907
            StringIndex new_node(inner_node_tag(), m_array->get_alloc());
63✔
908
            new_node.node_add_key(nc.ref1);
63✔
909
            new_node.node_add_key(nc.ref2);
63✔
910
            m_array->init_from_ref(new_node.get_ref());
63✔
911
            m_array->update_parent();
63✔
912
            return;
63✔
913
        }
×
914
    }
×
915
    REALM_ASSERT(false); // LCOV_EXCL_LINE; internal Realm error
×
916
}
×
917

918

919
StringIndex::NodeChange StringIndex::do_insert(ObjKey obj_key, key_type key, size_t offset, StringData index_data,
920
                                               const Mixed& value)
921
{
9,307,509✔
922
    Allocator& alloc = m_array->get_alloc();
9,307,509✔
923
    if (m_array->is_inner_bptree_node()) {
9,307,509✔
924
        // Get subnode table
14,010✔
925
        Array keys(alloc);
45,891✔
926
        get_child(*m_array, 0, keys);
45,891✔
927
        REALM_ASSERT(m_array->size() == keys.size() + 1);
45,891✔
928

14,010✔
929
        // Find the subnode containing the item
14,010✔
930
        size_t node_ndx = keys.lower_bound_int(key);
45,891✔
931
        if (node_ndx == keys.size()) {
45,891✔
932
            // node can never be empty, so try to fit in last item
303✔
933
            node_ndx = keys.size() - 1;
9,642✔
934
        }
9,642✔
935

14,010✔
936
        // Get sublist
14,010✔
937
        size_t refs_ndx = node_ndx + 1; // first entry in refs points to offsets
45,891✔
938
        ref_type ref = m_array->get_as_ref(refs_ndx);
45,891✔
939
        StringIndex target(ref, m_array.get(), refs_ndx, m_target_column, alloc);
45,891✔
940

14,010✔
941
        // Insert item
14,010✔
942
        NodeChange nc = target.do_insert(obj_key, key, offset, index_data, value);
45,891✔
943
        if (nc.type == NodeChange::change_None) {
45,891✔
944
            // update keys
14,004✔
945
            key_type last_key = target.get_last_key();
45,861✔
946
            keys.set(node_ndx, last_key);
45,861✔
947
            return NodeChange::change_None; // no new nodes
45,861✔
948
        }
45,861✔
949

6✔
950
        if (nc.type == NodeChange::change_InsertAfter) {
30✔
951
            ++node_ndx;
9✔
952
            ++refs_ndx;
9✔
953
        }
9✔
954

6✔
955
        // If there is room, just update node directly
6✔
956
        if (keys.size() < REALM_MAX_BPNODE_SIZE) {
30✔
957
            if (nc.type == NodeChange::change_Split) {
30✔
958
                node_insert_split(node_ndx, nc.ref2);
21✔
959
            }
21✔
960
            else {
9✔
961
                node_insert(node_ndx, nc.ref1); // ::INSERT_BEFORE/AFTER
9✔
962
            }
9✔
963
            return NodeChange::change_None;
30✔
964
        }
30✔
965

966
        // Else create new node
967
        StringIndex new_node(inner_node_tag(), alloc);
×
968
        if (nc.type == NodeChange::change_Split) {
×
969
            // update offset for left node
970
            key_type last_key = target.get_last_key();
×
971
            keys.set(node_ndx, last_key);
×
972

973
            new_node.node_add_key(nc.ref2);
×
974
            ++node_ndx;
×
975
            ++refs_ndx;
×
976
        }
×
977
        else {
×
978
            new_node.node_add_key(nc.ref1);
×
979
        }
×
980

981
        switch (node_ndx) {
×
982
            case 0: // insert before
×
983
                return NodeChange(NodeChange::change_InsertBefore, new_node.get_ref());
×
984
            case REALM_MAX_BPNODE_SIZE: // insert after
×
985
                if (nc.type == NodeChange::change_Split)
×
986
                    return NodeChange(NodeChange::change_Split, get_ref(), new_node.get_ref());
×
987
                return NodeChange(NodeChange::change_InsertAfter, new_node.get_ref());
×
988
            default: // split
×
989
                // Move items after split to new node
990
                size_t len = m_array->size();
×
991
                for (size_t i = refs_ndx; i < len; ++i) {
×
992
                    ref_type ref_i = m_array->get_as_ref(i);
×
993
                    new_node.node_add_key(ref_i);
×
994
                }
×
995
                keys.truncate(node_ndx);
×
996
                m_array->truncate(refs_ndx);
×
997
                return NodeChange(NodeChange::change_Split, get_ref(), new_node.get_ref());
×
998
        }
9,261,618✔
999
    }
9,261,618✔
1000
    else {
9,261,618✔
1001
        // Is there room in the list?
4,613,430✔
1002
        Array old_keys(alloc);
9,261,618✔
1003
        get_child(*m_array, 0, old_keys);
9,261,618✔
1004
        const size_t old_offsets_size = old_keys.size();
9,261,618✔
1005
        REALM_ASSERT_EX(m_array->size() == old_offsets_size + 1, m_array->size(), old_offsets_size + 1);
9,261,618✔
1006

4,613,430✔
1007
        bool noextend = old_offsets_size >= REALM_MAX_BPNODE_SIZE;
9,261,618✔
1008

4,613,430✔
1009
        // See if we can fit entry into current leaf
4,613,430✔
1010
        // Works if there is room or it can join existing entries
4,613,430✔
1011
        if (leaf_insert(obj_key, key, offset, index_data, value, noextend))
9,261,618✔
1012
            return NodeChange::change_None;
9,262,320✔
1013

2,147,483,647✔
1014
        // Create new list for item (a leaf)
2,147,483,647✔
1015
        StringIndex new_list(m_target_column, alloc);
2,147,485,258✔
1016

2,147,483,647✔
1017
        new_list.leaf_insert(obj_key, key, offset, index_data, value);
2,147,485,258✔
1018

2,147,483,647✔
1019
        size_t ndx = old_keys.lower_bound_int(key);
2,147,485,258✔
1020

2,147,483,647✔
1021
        // insert before
2,147,483,647✔
1022
        if (ndx == 0)
2,147,485,258✔
1023
            return NodeChange(NodeChange::change_InsertBefore, new_list.get_ref());
×
1024

2,147,483,647✔
1025
        // insert after
2,147,483,647✔
1026
        if (ndx == old_offsets_size)
2,147,485,258✔
1027
            return NodeChange(NodeChange::change_InsertAfter, new_list.get_ref());
18✔
1028

2,147,483,647✔
1029
        // split
2,147,483,647✔
1030
        Array new_keys(alloc);
2,147,485,246✔
1031
        get_child(*new_list.m_array, 0, new_keys);
2,147,485,246✔
1032
        // Move items after split to new list
2,147,483,647✔
1033
        for (size_t i = ndx; i < old_offsets_size; ++i) {
2,147,508,877✔
1034
            int64_t v2 = old_keys.get(i);
40,206✔
1035
            int64_t v3 = m_array->get(i + 1);
40,206✔
1036

16,575✔
1037
            new_keys.add(v2);
40,206✔
1038
            new_list.m_array->add(v3);
40,206✔
1039
        }
40,206✔
1040
        old_keys.truncate(ndx);
2,147,485,246✔
1041
        m_array->truncate(ndx + 1);
2,147,485,246✔
1042

2,147,483,647✔
1043
        return NodeChange(NodeChange::change_Split, get_ref(), new_list.get_ref());
2,147,485,246✔
1044
    }
2,147,485,246✔
1045
}
9,307,509✔
1046

1047

1048
void StringIndex::node_insert_split(size_t ndx, size_t new_ref)
1049
{
21✔
1050
    REALM_ASSERT(m_array->is_inner_bptree_node());
21✔
1051
    REALM_ASSERT(new_ref);
21✔
1052

6✔
1053
    Allocator& alloc = m_array->get_alloc();
21✔
1054
    Array offsets(alloc);
21✔
1055
    get_child(*m_array, 0, offsets);
21✔
1056

6✔
1057
    REALM_ASSERT(m_array->size() == offsets.size() + 1);
21✔
1058
    REALM_ASSERT(ndx < offsets.size());
21✔
1059
    REALM_ASSERT(offsets.size() < REALM_MAX_BPNODE_SIZE);
21✔
1060

6✔
1061
    // Get sublists
6✔
1062
    size_t refs_ndx = ndx + 1; // first entry in refs points to offsets
21✔
1063
    ref_type orig_ref = m_array->get_as_ref(refs_ndx);
21✔
1064
    StringIndex orig_col(orig_ref, m_array.get(), refs_ndx, m_target_column, alloc);
21✔
1065
    StringIndex new_col(new_ref, nullptr, 0, m_target_column, alloc);
21✔
1066

6✔
1067
    // Update original key
6✔
1068
    key_type last_key = orig_col.get_last_key();
21✔
1069
    offsets.set(ndx, last_key);
21✔
1070

6✔
1071
    // Insert new ref
6✔
1072
    key_type new_key = new_col.get_last_key();
21✔
1073
    offsets.insert(ndx + 1, new_key);
21✔
1074
    m_array->insert(ndx + 2, new_ref);
21✔
1075
}
21✔
1076

1077

1078
void StringIndex::node_insert(size_t ndx, size_t ref)
1079
{
9✔
1080
    REALM_ASSERT(ref);
9✔
1081
    REALM_ASSERT(m_array->is_inner_bptree_node());
9✔
1082

1083
    Allocator& alloc = m_array->get_alloc();
9✔
1084
    Array offsets(alloc);
9✔
1085
    get_child(*m_array, 0, offsets);
9✔
1086
    REALM_ASSERT(m_array->size() == offsets.size() + 1);
9✔
1087

1088
    REALM_ASSERT(ndx <= offsets.size());
9✔
1089
    REALM_ASSERT(offsets.size() < REALM_MAX_BPNODE_SIZE);
9✔
1090

1091
    StringIndex col(ref, nullptr, 0, m_target_column, alloc);
9✔
1092
    key_type last_key = col.get_last_key();
9✔
1093

1094
    offsets.insert(ndx, last_key);
9✔
1095
    m_array->insert(ndx + 1, ref);
9✔
1096
}
9✔
1097

1098
bool StringIndex::leaf_insert(ObjKey obj_key, key_type key, size_t offset, StringData index_data, const Mixed& value,
1099
                              bool noextend)
1100
{
9,245,094✔
1101
    REALM_ASSERT(!m_array->is_inner_bptree_node());
9,245,094✔
1102

4,602,417✔
1103
    // Get subnode table
4,602,417✔
1104
    Allocator& alloc = m_array->get_alloc();
9,245,094✔
1105
    Array keys(alloc);
9,245,094✔
1106
    get_child(*m_array, 0, keys);
9,245,094✔
1107
    REALM_ASSERT(m_array->size() == keys.size() + 1);
9,245,094✔
1108

4,602,417✔
1109
    // If we are keeping the complete string in the index
4,602,417✔
1110
    // we want to know if this is the last part
4,602,417✔
1111
    bool is_at_string_end = offset + 4 >= index_data.size();
9,245,094✔
1112

4,602,417✔
1113
    size_t ins_pos = keys.lower_bound_int(key);
9,245,094✔
1114
    size_t ins_pos_refs = ins_pos + 1; // first entry in refs points to offsets
9,245,094✔
1115

4,602,417✔
1116
    if (ins_pos == keys.size()) {
9,245,094✔
1117
        if (noextend)
332,811✔
1118
            return false;
18✔
1119

162,747✔
1120
        // When key is outside current range, we can just add it
162,747✔
1121
        keys.add(key);
332,793✔
1122
        if (!m_target_column.is_fulltext() || is_at_string_end) {
332,793✔
1123
            int64_t shifted = int64_t((uint64_t(obj_key.value) << 1) + 1); // shift to indicate literal
331,983✔
1124
            m_array->add(shifted);
331,983✔
1125
        }
331,983✔
1126
        else {
810✔
1127
            // create subindex for rest of string
411✔
1128
            StringIndex subindex(m_target_column, m_array->get_alloc());
810✔
1129
            subindex.insert_with_offset(obj_key, index_data, value, offset + 4);
810✔
1130
            m_array->add(subindex.get_ref());
810✔
1131
        }
810✔
1132
        return true;
332,793✔
1133
    }
332,793✔
1134

4,439,664✔
1135
    key_type k = key_type(keys.get(ins_pos));
8,912,283✔
1136

4,439,664✔
1137
    // If key is not present we add it at the correct location
4,439,664✔
1138
    if (k != key) {
8,912,283✔
1139
        if (noextend)
720,573✔
1140
            return false;
84✔
1141

352,560✔
1142
        keys.insert(ins_pos, key);
720,489✔
1143
        if (!m_target_column.is_fulltext() || is_at_string_end) {
720,489✔
1144
            int64_t shifted = int64_t((uint64_t(obj_key.value) << 1) + 1); // shift to indicate literal
718,785✔
1145
            m_array->insert(ins_pos_refs, shifted);
718,785✔
1146
        }
718,785✔
1147
        else {
1,704✔
1148
            // create subindex for rest of string
870✔
1149
            StringIndex subindex(m_target_column, m_array->get_alloc());
1,704✔
1150
            subindex.insert_with_offset(obj_key, index_data, value, offset + 4);
1,704✔
1151
            m_array->insert(ins_pos_refs, subindex.get_ref());
1,704✔
1152
        }
1,704✔
1153
        return true;
720,489✔
1154
    }
720,489✔
1155

4,087,071✔
1156
    // This leaf already has a slot for for the key
4,087,071✔
1157

4,087,071✔
1158
    uint64_t slot_value = uint64_t(m_array->get(ins_pos_refs));
8,191,710✔
1159
    size_t suboffset = offset + s_index_key_length;
8,191,710✔
1160

4,087,071✔
1161
    // Single match (lowest bit set indicates literal row_ndx)
4,087,071✔
1162
    if ((slot_value & 1) != 0) {
8,191,710✔
1163
        ObjKey obj_key2 = ObjKey(int64_t(slot_value >> 1));
50,010✔
1164
        Mixed v2 = m_target_column.is_fulltext() ? reconstruct_string(offset, key, index_data) : get(obj_key2);
49,728✔
1165
        if (v2 == value) {
50,010✔
1166
            // Strings are equal but this is not a list.
4,716✔
1167
            // Create a list and add both rows.
4,716✔
1168

4,716✔
1169
            // convert to list (in sorted order)
4,716✔
1170
            Array row_list(alloc);
9,483✔
1171
            row_list.create(Array::type_Normal); // Throws
9,483✔
1172
            row_list.add(obj_key < obj_key2 ? obj_key.value : obj_key2.value);
8,826✔
1173
            row_list.add(obj_key < obj_key2 ? obj_key2.value : obj_key.value);
8,826✔
1174
            m_array->set(ins_pos_refs, row_list.get_ref());
9,483✔
1175
        }
9,483✔
1176
        else {
40,527✔
1177
            StringConversionBuffer buffer;
40,527✔
1178
            auto index_data_2 = v2.get_index_data(buffer);
40,527✔
1179
            if (index_data == index_data_2 || suboffset > s_max_offset) {
40,527✔
1180
                // These strings have the same prefix up to this point but we
267✔
1181
                // don't want to recurse further, create a list in sorted order.
267✔
1182
                bool row_ndx_first = value < v2;
558✔
1183
                Array row_list(alloc);
558✔
1184
                row_list.create(Array::type_Normal); // Throws
558✔
1185
                row_list.add(row_ndx_first ? obj_key.value : obj_key2.value);
465✔
1186
                row_list.add(row_ndx_first ? obj_key2.value : obj_key.value);
465✔
1187
                m_array->set(ins_pos_refs, row_list.get_ref());
558✔
1188
            }
558✔
1189
            else {
39,969✔
1190
                // These strings have the same prefix up to this point but they
18,849✔
1191
                // are actually not equal. Extend the tree recursivly until the
18,849✔
1192
                // prefix of these strings is different.
18,849✔
1193
                StringIndex subindex(m_target_column, m_array->get_alloc());
39,969✔
1194
                subindex.insert_with_offset(obj_key2, index_data_2, v2, suboffset);
39,969✔
1195
                subindex.insert_with_offset(obj_key, index_data, value, suboffset);
39,969✔
1196
                // Join the string of SubIndices to the current position of m_array
18,849✔
1197
                m_array->set(ins_pos_refs, subindex.get_ref());
39,969✔
1198
            }
39,969✔
1199
        }
40,527✔
1200
        return true;
50,010✔
1201
    }
50,010✔
1202

4,063,239✔
1203
    // If there already is a list of matches, we see if we fit there
4,063,239✔
1204
    // or it has to be split into a subindex
4,063,239✔
1205
    ref_type ref = ref_type(slot_value);
8,141,700✔
1206
    char* header = alloc.translate(ref);
8,141,700✔
1207
    if (!Array::get_context_flag_from_header(header)) {
8,141,700✔
1208
        IntegerColumn sub(alloc, ref); // Throws
2,069,763✔
1209
        sub.set_parent(m_array.get(), ins_pos_refs);
2,069,763✔
1210

1,033,590✔
1211
        IntegerColumn::const_iterator it_end = sub.cend();
2,069,763✔
1212
        IntegerColumn::const_iterator lower = it_end;
2,069,763✔
1213

1,033,590✔
1214
        auto value_exists_in_list = [&]() {
2,070,495✔
1215
            if (m_target_column.is_fulltext()) {
2,070,495✔
1216
                return reconstruct_string(offset, key, index_data) == value.get_string();
348✔
1217
            }
348✔
1218
            SortedListComparator slc(m_target_column);
2,070,147✔
1219
            lower = slc.find_start_of_unsorted(value, sub);
2,070,147✔
1220

1,033,971✔
1221
            if (lower != it_end) {
2,070,147✔
1222
                Mixed lower_value = get(ObjKey(*lower));
2,066,910✔
1223
                if (lower_value == value) {
2,066,910✔
1224
                    return true;
2,066,070✔
1225
                }
2,066,070✔
1226
            }
4,077✔
1227
            return false;
4,077✔
1228
        };
4,077✔
1229

1,033,590✔
1230
        // If we found the value in this list, add the duplicate to the list.
1,033,590✔
1231
        if (value_exists_in_list()) {
2,069,763✔
1232
            insert_to_existing_list_at_lower(obj_key, value, sub, lower);
2,066,397✔
1233
        }
2,066,397✔
1234
        else {
3,366✔
1235
            // If the list only stores duplicates we are free to branch and
1,500✔
1236
            // and create a sub index with this existing list as one of the
1,500✔
1237
            // leafs, but if the list doesn't only contain duplicates we
1,500✔
1238
            // must respect that we store a common key prefix up to this
1,500✔
1239
            // point and insert into the existing list.
1,500✔
1240
            ObjKey key_of_any_dup = ObjKey(sub.get(0));
3,366✔
1241
            StringConversionBuffer buffer;
3,366✔
1242
            StringData index_data_2 = m_target_column.is_fulltext() ? reconstruct_string(offset, key, index_data)
1,503✔
1243
                                                                    : get(key_of_any_dup).get_index_data(buffer);
3,363✔
1244
            if (index_data == index_data_2 || suboffset > s_max_offset) {
4,140✔
1245
                insert_to_existing_list(obj_key, value, sub);
2,004✔
1246
            }
2,004✔
1247
            else {
1,362✔
1248
#ifdef REALM_DEBUG
1,362✔
1249
                bool contains_only_duplicates = true;
1,362✔
1250
                if (!m_target_column.is_fulltext() && sub.size() > 1) {
2,142✔
1251
                    ObjKey first_key = ObjKey(sub.get(0));
1,485✔
1252
                    ObjKey last_key = ObjKey(sub.back());
1,485✔
1253
                    auto first = get(first_key);
1,485✔
1254
                    auto last = get(last_key);
1,485✔
1255
                    // Since the list is kept in sorted order, the first and
660✔
1256
                    // last values will be the same only if the whole list is
660✔
1257
                    // storing duplicate values.
660✔
1258
                    if (first != last) {
1,485✔
1259
                        contains_only_duplicates = false; // LCOV_EXCL_LINE
×
1260
                    }
×
1261
                }
1,485✔
1262
                REALM_ASSERT_DEBUG(contains_only_duplicates);
1,362✔
1263
#endif
1,362✔
1264
                // The buffer is needed for when this is an integer index.
471✔
1265
                StringIndex subindex(m_target_column, m_array->get_alloc());
1,362✔
1266
                subindex.insert_row_list(sub.get_ref(), suboffset, index_data_2);
1,362✔
1267
                subindex.insert_with_offset(obj_key, index_data, value, suboffset);
1,362✔
1268
                m_array->set(ins_pos_refs, subindex.get_ref());
1,362✔
1269
            }
1,362✔
1270
        }
3,366✔
1271
        return true;
2,069,763✔
1272
    }
2,069,763✔
1273

3,029,649✔
1274
    // The key matches, but there is a subindex here so go down a level in the tree.
3,029,649✔
1275
    StringIndex subindex(ref, m_array.get(), ins_pos_refs, m_target_column, alloc);
6,071,937✔
1276
    subindex.insert_with_offset(obj_key, index_data, value, suboffset);
6,071,937✔
1277

3,029,649✔
1278
    return true;
6,071,937✔
1279
}
6,071,937✔
1280

1281
Mixed StringIndex::get(ObjKey key) const
1282
{
3,759,699✔
1283
    return m_target_column.get_value(key);
3,759,699✔
1284
}
3,759,699✔
1285

1286
void StringIndex::erase(ObjKey key)
1287
{
974,421✔
1288
    StringConversionBuffer buffer;
974,421✔
1289
    std::string_view value{(get(key).get_index_data(buffer))};
974,421✔
1290
    if (m_target_column.is_fulltext()) {
974,421✔
1291
        auto words = Tokenizer::get_instance()->reset(value).get_all_tokens();
72✔
1292
        for (auto& w : words) {
2,460✔
1293
            erase_string(key, w);
2,460✔
1294
        }
2,460✔
1295
    }
72✔
1296
    else {
974,349✔
1297
        erase_string(key, value);
974,349✔
1298
    }
974,349✔
1299
}
974,421✔
1300

1301
namespace {
1302
template <typename T>
1303
void intersect(std::vector<ObjKey>& result, T& keys)
1304
{
276✔
1305
    if (result.empty()) {
276✔
1306
        result.reserve(keys.size());
180✔
1307
        for (auto k : keys) {
498✔
1308
            result.emplace_back(k);
498✔
1309
        }
498✔
1310
    }
180✔
1311
    else {
96✔
1312
        auto it = result.begin();
96✔
1313
        auto keep = it;
96✔
1314
        auto m = keys.begin();
96✔
1315

48✔
1316
        // only keep intersection
48✔
1317
        while (it != result.end() && m != keys.end()) {
354✔
1318
            int64_t int_val = *m;
258✔
1319
            if (it->value < int_val) {
258✔
1320
                it++; // don't keep if match is not in new set
18✔
1321
            }
18✔
1322
            else if (it->value > int_val) {
240✔
1323
                ++m; // ignore new matches
102✔
1324
            }
102✔
1325
            else {
138✔
1326
                // Found both places - make sure it is kept
69✔
1327
                if (keep < it)
138✔
1328
                    *keep = *it;
6✔
1329
                ++keep;
138✔
1330
                ++it;
138✔
1331
                ++m;
138✔
1332
            }
138✔
1333
        }
258✔
1334
        if (keep != result.end()) {
96✔
1335
            result.erase(keep, result.end());
24✔
1336
        }
24✔
1337
    }
96✔
1338
}
276✔
1339

1340
struct FindResWrapper {
1341
    InternalFindResult& res;
1342
    IntegerColumn& indexes;
1343
    size_t n = 0;
1344
    size_t size()
1345
    {
144✔
1346
        return res.end_ndx - res.start_ndx;
144✔
1347
    }
144✔
1348
    auto begin()
1349
    {
228✔
1350
        return indexes.cbegin();
228✔
1351
    }
228✔
1352
    auto end()
1353
    {
384✔
1354
        return indexes.cend();
384✔
1355
    }
384✔
1356
};
1357
} // namespace
1358

1359
void StringIndex::find_all_fulltext(std::vector<ObjKey>& result, StringData value) const
1360
{
360✔
1361
    InternalFindResult res;
360✔
1362
    REALM_ASSERT(result.empty());
360✔
1363

180✔
1364
    auto tokenizer = Tokenizer::get_instance();
360✔
1365
    tokenizer->reset({value.data(), value.size()});
360✔
1366
    auto [includes, excludes] = tokenizer->get_search_tokens();
360✔
1367
    if (includes.empty()) {
360✔
1368
        if (excludes.empty()) {
24✔
1369
            throw InvalidArgument("Missing search token");
6✔
1370
        }
6✔
1371
        result = m_target_column.get_all_keys();
18✔
1372
    }
18✔
1373
    else {
336✔
1374
        for (auto& token : includes) {
414✔
1375
            if (token.back() == '*') {
414✔
1376
                std::set<int64_t> keys;
48✔
1377
                m_array->index_string_find_all_prefix(keys, StringData(token.data(), token.size() - 1));
48✔
1378
                intersect(result, keys);
48✔
1379
            }
48✔
1380
            else {
366✔
1381
                switch (find_all_no_copy(StringData{token}, res)) {
366✔
1382
                    case FindRes_not_found:
12✔
1383
                        result.clear();
12✔
1384
                        break;
12✔
1385
                    case FindRes_column: {
228✔
1386
                        IntegerColumn indexes(m_array->get_alloc(), ref_type(res.payload));
228✔
1387
                        FindResWrapper wrapper{res, indexes};
228✔
1388
                        intersect(result, wrapper);
228✔
1389
                        break;
228✔
1390
                    }
×
1391
                    case FindRes_single:
126✔
1392
                        // merge in single res
63✔
1393
                        if (result.empty()) {
126✔
1394
                            result.emplace_back(res.payload);
96✔
1395
                        }
96✔
1396
                        else {
30✔
1397
                            ObjKey key(res.payload);
30✔
1398
                            auto pos = std::lower_bound(result.begin(), result.end(), key);
30✔
1399
                            if (pos != result.end() && key == *pos) {
30✔
1400
                                result.clear();
30✔
1401
                                result.push_back(key);
30✔
1402
                            }
30✔
1403
                            else {
×
1404
                                result.clear();
×
1405
                            }
×
1406
                        }
30✔
1407
                        break;
126✔
1408
                }
414✔
1409
            }
414✔
1410
            if (result.empty())
414✔
1411
                return;
18✔
1412
        }
414✔
1413
    }
336✔
1414

180✔
1415
    for (auto& token : excludes) {
348✔
1416
        if (token.back() == '*') {
96✔
1417
            throw IllegalOperation("Exclude by prefix is not implemented");
×
1418
        }
×
1419
        if (result.empty())
96✔
1420
            return;
×
1421

48✔
1422
        switch (find_all_no_copy(StringData{token}, res)) {
96✔
1423
            case FindRes_not_found:
✔
1424
                // Nothing to exclude
1425
                break;
×
1426
            case FindRes_column: {
60✔
1427
                IntegerColumn indexes(m_array->get_alloc(), ref_type(res.payload));
60✔
1428

30✔
1429
                auto it = result.begin();
60✔
1430
                auto keep = it;
60✔
1431
                size_t m = res.start_ndx;
60✔
1432
                auto idx_val = indexes.get(m);
60✔
1433

30✔
1434
                while (it != result.end()) {
390✔
1435
                    if (it->value < idx_val) {
330✔
1436
                        // Not found in excludes
78✔
1437
                        if (keep < it)
156✔
1438
                            *keep = *it;
156✔
1439
                        ++keep;
156✔
1440
                        ++it;
156✔
1441
                    }
156✔
1442
                    else {
174✔
1443
                        if (it->value == idx_val) {
174✔
1444
                            // found in excludes - don't keep
75✔
1445
                            ++it;
150✔
1446
                        }
150✔
1447
                        ++m;
174✔
1448
                        idx_val = m < res.end_ndx ? indexes.get(m) : std::numeric_limits<int64_t>::max();
153✔
1449
                    }
174✔
1450
                }
330✔
1451
                if (keep != result.end()) {
60✔
1452
                    result.erase(keep, result.end());
60✔
1453
                }
60✔
1454
                break;
60✔
1455
            }
×
1456
            case FindRes_single: {
36✔
1457
                // exclude single res
18✔
1458
                ObjKey key(res.payload);
36✔
1459
                auto pos = std::lower_bound(result.begin(), result.end(), key);
36✔
1460
                if (pos != result.end() && key == *pos) {
36✔
1461
                    result.erase(pos);
36✔
1462
                }
36✔
1463
                break;
36✔
1464
            }
×
1465
        }
96✔
1466
    }
96✔
1467
}
336✔
1468

1469

1470
void StringIndex::clear()
1471
{
2,940✔
1472
    Array values(m_array->get_alloc());
2,940✔
1473
    get_child(*m_array, 0, values);
2,940✔
1474
    REALM_ASSERT(m_array->size() == values.size() + 1);
2,940✔
1475

813✔
1476
    values.clear();
2,940✔
1477
    values.ensure_minimum_width(0x7FFFFFFF); // This ensures 31 bits plus a sign bit
2,940✔
1478

813✔
1479
    size_t size = 1;
2,940✔
1480
    m_array->truncate_and_destroy_children(size); // Don't touch `values` array
2,940✔
1481

813✔
1482
    m_array->set_type(Array::type_HasRefs);
2,940✔
1483
}
2,940✔
1484

1485

1486
void StringIndex::do_delete(ObjKey obj_key, StringData index_data, size_t offset)
1487
{
1,283,496✔
1488
    Allocator& alloc = m_array->get_alloc();
1,283,496✔
1489
    Array values(alloc);
1,283,496✔
1490
    get_child(*m_array, 0, values);
1,283,496✔
1491
    REALM_ASSERT(m_array->size() == values.size() + 1);
1,283,496✔
1492

642,087✔
1493
    // Create 4 byte index key
642,087✔
1494
    key_type key = create_key(index_data, offset);
1,283,496✔
1495

642,087✔
1496
    const size_t pos = values.lower_bound_int(key);
1,283,496✔
1497
    const size_t pos_refs = pos + 1; // first entry in refs points to offsets
1,283,496✔
1498
    REALM_ASSERT(pos != values.size());
1,283,496✔
1499

642,087✔
1500
    if (m_array->is_inner_bptree_node()) {
1,283,496✔
1501
        ref_type ref = m_array->get_as_ref(pos_refs);
64,947✔
1502
        StringIndex node(ref, m_array.get(), pos_refs, m_target_column, alloc);
64,947✔
1503
        node.do_delete(obj_key, index_data, offset);
64,947✔
1504

32,508✔
1505
        // Update the ref
32,508✔
1506
        if (node.is_empty()) {
64,947✔
1507
            values.erase(pos);
108✔
1508
            m_array->erase(pos_refs);
108✔
1509
            node.destroy();
108✔
1510
        }
108✔
1511
        else {
64,839✔
1512
            key_type max_val = node.get_last_key();
64,839✔
1513
            if (max_val != key_type(values.get(pos)))
64,839✔
1514
                values.set(pos, max_val);
108✔
1515
        }
64,839✔
1516
    }
64,947✔
1517
    else {
1,218,549✔
1518
        uint64_t ref = m_array->get(pos_refs);
1,218,549✔
1519
        if (ref & 1) {
1,218,549✔
1520
            REALM_ASSERT(int64_t(ref >> 1) == obj_key.value);
628,428✔
1521
            values.erase(pos);
628,428✔
1522
            m_array->erase(pos_refs);
628,428✔
1523
        }
628,428✔
1524
        else {
590,121✔
1525
            // A real ref either points to a list or a subindex
294,261✔
1526
            char* header = alloc.translate(ref_type(ref));
590,121✔
1527
            if (Array::get_context_flag_from_header(header)) {
590,121✔
1528
                StringIndex subindex(ref_type(ref), m_array.get(), pos_refs, m_target_column, alloc);
241,755✔
1529
                subindex.do_delete(obj_key, index_data, offset + s_index_key_length);
241,755✔
1530

121,083✔
1531
                if (subindex.is_empty()) {
241,755✔
1532
                    values.erase(pos);
11,004✔
1533
                    m_array->erase(pos_refs);
11,004✔
1534
                    subindex.destroy();
11,004✔
1535
                }
11,004✔
1536
            }
241,755✔
1537
            else {
348,366✔
1538
                IntegerColumn sub(alloc, ref_type(ref)); // Throws
348,366✔
1539
                sub.set_parent(m_array.get(), pos_refs);
348,366✔
1540
                size_t r = sub.find_first(obj_key.value);
348,366✔
1541
                size_t sub_size = sub.size(); // Slow
348,366✔
1542
                REALM_ASSERT_EX(r != sub_size, r, sub_size);
348,366✔
1543
                sub.erase(r);
348,366✔
1544

173,178✔
1545
                if (sub_size == 1) {
348,366✔
1546
                    values.erase(pos);
2,994✔
1547
                    m_array->erase(pos_refs);
2,994✔
1548
                    sub.destroy();
2,994✔
1549
                }
2,994✔
1550
            }
348,366✔
1551
        }
590,121✔
1552
    }
1,218,549✔
1553
}
1,283,496✔
1554

1555
void StringIndex::erase_string(ObjKey key, StringData value)
1556
{
976,899✔
1557
    do_delete(key, value, 0);
976,899✔
1558

488,577✔
1559
    // Collapse top nodes with single item
488,577✔
1560
    while (m_array->is_inner_bptree_node()) {
976,908✔
1561
        REALM_ASSERT(m_array->size() > 1); // node cannot be empty
64,947✔
1562
        if (m_array->size() > 2)
64,947✔
1563
            break;
64,938✔
1564

6✔
1565
        ref_type ref = m_array->get_as_ref(1);
9✔
1566
        m_array->set(1, 1); // avoid destruction of the extracted ref
9✔
1567
        m_array->destroy_deep();
9✔
1568
        m_array->init_from_ref(ref);
9✔
1569
        m_array->update_parent();
9✔
1570
    }
9✔
1571
}
976,899✔
1572

1573
namespace {
1574

1575
bool has_duplicate_values(const Array& node, const ClusterColumn& target_col) noexcept
1576
{
6,366✔
1577
    Allocator& alloc = node.get_alloc();
6,366✔
1578
    Array child(alloc);
6,366✔
1579
    size_t n = node.size();
6,366✔
1580
    REALM_ASSERT(n >= 1);
6,366✔
1581
    if (node.is_inner_bptree_node()) {
6,366✔
1582
        // Inner node
1583
        for (size_t i = 1; i < n; ++i) {
×
1584
            ref_type ref = node.get_as_ref(i);
×
1585
            child.init_from_ref(ref);
×
1586
            if (has_duplicate_values(child, target_col))
×
1587
                return true;
×
1588
        }
×
1589
        return false;
×
1590
    }
6,366✔
1591

3,183✔
1592
    // Leaf node
3,183✔
1593
    for (size_t i = 1; i < n; ++i) {
32,850✔
1594
        int_fast64_t value = node.get(i);
28,740✔
1595
        bool is_single_row_index = (value & 1) != 0;
28,740✔
1596
        if (is_single_row_index)
28,740✔
1597
            continue;
23,280✔
1598

2,730✔
1599
        ref_type ref = to_ref(value);
5,460✔
1600
        child.init_from_ref(ref);
5,460✔
1601

2,730✔
1602
        bool is_subindex = child.get_context_flag();
5,460✔
1603
        if (is_subindex) {
5,460✔
1604
            if (has_duplicate_values(child, target_col))
4,872✔
1605
                return true;
1,800✔
1606
            continue;
3,072✔
1607
        }
3,072✔
1608

294✔
1609
        // Child is root of B+-tree of row indexes
294✔
1610
        IntegerColumn sub(alloc, ref);
588✔
1611
        if (sub.size() > 1) {
588✔
1612
            ObjKey first_key = ObjKey(sub.get(0));
480✔
1613
            ObjKey last_key = ObjKey(sub.back());
480✔
1614
            Mixed first = target_col.get_value(first_key);
480✔
1615
            Mixed last = target_col.get_value(last_key);
480✔
1616
            // Since the list is kept in sorted order, the first and
240✔
1617
            // last values will be the same only if the whole list is
240✔
1618
            // storing duplicate values.
240✔
1619
            if (first == last) {
480✔
1620
                return true;
432✔
1621
            }
432✔
1622
            // There may also be several short lists combined, so we need to
24✔
1623
            // check each of these individually for duplicates.
24✔
1624
            IntegerColumn::const_iterator it = sub.cbegin();
48✔
1625
            IntegerColumn::const_iterator it_end = sub.cend();
48✔
1626
            SortedListComparator slc(target_col);
48✔
1627
            while (it != it_end) {
144✔
1628
                Mixed it_data = target_col.get_value(ObjKey(*it));
120✔
1629
                IntegerColumn::const_iterator next = slc.find_end_of_unsorted(it_data, sub, it);
120✔
1630
                size_t count_of_value = next - it; // row index subtraction in `sub`
120✔
1631
                if (count_of_value > 1) {
120✔
1632
                    return true;
24✔
1633
                }
24✔
1634
                it = next;
96✔
1635
            }
96✔
1636
        }
48✔
1637
    }
588✔
1638

3,183✔
1639
    return false;
5,238✔
1640
}
6,366✔
1641

1642
} // anonymous namespace
1643

1644

1645
bool StringIndex::has_duplicate_values() const noexcept
1646
{
1,494✔
1647
    return ::has_duplicate_values(*m_array, m_target_column);
1,494✔
1648
}
1,494✔
1649

1650

1651
bool StringIndex::is_empty() const
1652
{
306,873✔
1653
    return m_array->size() == 1; // first entry in refs points to offsets
306,873✔
1654
}
306,873✔
1655

1656
void StringIndex::insert(ObjKey key, const Mixed& value)
1657
{
2,451,258✔
1658
    StringConversionBuffer buffer;
2,451,258✔
1659
    constexpr size_t offset = 0; // First key from beginning of string
2,451,258✔
1660

1,214,160✔
1661
    if (this->m_target_column.is_fulltext()) {
2,451,258✔
1662
        if (value.is_type(type_String)) {
198✔
1663
            auto words = Tokenizer::get_instance()->reset(std::string_view(value.get<StringData>())).get_all_tokens();
198✔
1664
            for (auto& word : words) {
936✔
1665
                Mixed m(word);
936✔
1666
                insert_with_offset(key, m.get_index_data(buffer), m, offset); // Throws
936✔
1667
            }
936✔
1668
        }
198✔
1669
    }
198✔
1670
    else {
2,451,060✔
1671
        insert_with_offset(key, value.get_index_data(buffer), value, offset); // Throws
2,451,060✔
1672
    }
2,451,060✔
1673
}
2,451,258✔
1674

1675
void StringIndex::set(ObjKey key, const Mixed& new_value)
1676
{
663,444✔
1677
    StringConversionBuffer buffer;
663,444✔
1678
    Mixed old_value = get(key);
663,444✔
1679

331,788✔
1680
    if (this->m_target_column.is_fulltext()) {
663,444✔
1681
        auto tokenizer = Tokenizer::get_instance();
168✔
1682
        StringData old_string = old_value.get_index_data(buffer);
168✔
1683
        std::set<std::string> old_words;
168✔
1684

84✔
1685
        if (old_string.size() > 0) {
168✔
1686
            tokenizer->reset({old_string.data(), old_string.size()});
6✔
1687
            old_words = tokenizer->get_all_tokens();
6✔
1688
        }
6✔
1689
        std::set<std::string> new_words;
168✔
1690
        if (new_value.is_type(type_String)) {
168✔
1691
            new_words = tokenizer->reset(std::string_view(new_value.get<StringData>())).get_all_tokens();
168✔
1692
        }
168✔
1693

84✔
1694
        auto w1 = old_words.begin();
168✔
1695
        auto w2 = new_words.begin();
168✔
1696

84✔
1697
        // Do a diff, deleting words no longer present and
84✔
1698
        // inserting new words
84✔
1699
        while (w1 != old_words.end() && w2 != new_words.end()) {
540✔
1700
            if (*w1 < *w2) {
372✔
1701
                erase_string(key, *w1);
126✔
1702
                ++w1;
126✔
1703
            }
126✔
1704
            else if (*w2 < *w1) {
246✔
1705
                Mixed m(*w2);
198✔
1706
                insert_with_offset(key, m.get_index_data(buffer), m, 0);
198✔
1707
                ++w2;
198✔
1708
            }
198✔
1709
            else {
48✔
1710
                ++w1;
48✔
1711
                ++w2;
48✔
1712
            }
48✔
1713
        }
372✔
1714
        while (w1 != old_words.end()) {
168✔
1715
            erase_string(key, *w1);
×
1716
            ++w1;
×
1717
        }
×
1718
        while (w2 != new_words.end()) {
3,354✔
1719
            Mixed m(*w2);
3,186✔
1720
            insert_with_offset(key, m.get_index_data(buffer), m, 0);
3,186✔
1721

1,593✔
1722
            ++w2;
3,186✔
1723
        }
3,186✔
1724
    }
168✔
1725
    else {
663,276✔
1726
        if (REALM_LIKELY(new_value != old_value)) {
663,276✔
1727
            // We must erase this row first because erase uses find_first which
316,923✔
1728
            // might find the duplicate if we insert before erasing.
316,923✔
1729
            erase(key); // Throws
633,672✔
1730

316,923✔
1731
            auto index_data = new_value.get_index_data(buffer);
633,672✔
1732
            insert_with_offset(key, index_data, new_value, 0); // Throws
633,672✔
1733
        }
633,672✔
1734
    }
663,276✔
1735
}
663,444✔
1736

1737
void StringIndex::node_add_key(ref_type ref)
1738
{
144✔
1739
    REALM_ASSERT(ref);
144✔
1740
    REALM_ASSERT(m_array->is_inner_bptree_node());
144✔
1741

66✔
1742
    Allocator& alloc = m_array->get_alloc();
144✔
1743
    Array offsets(alloc);
144✔
1744
    get_child(*m_array, 0, offsets);
144✔
1745
    REALM_ASSERT(m_array->size() == offsets.size() + 1);
144✔
1746
    REALM_ASSERT(offsets.size() < REALM_MAX_BPNODE_SIZE + 1);
144✔
1747

66✔
1748
    Array new_top(alloc);
144✔
1749
    Array new_offsets(alloc);
144✔
1750
    new_top.init_from_ref(ref);
144✔
1751
    new_offsets.init_from_ref(new_top.get_as_ref(0));
144✔
1752
    REALM_ASSERT(!new_offsets.is_empty());
144✔
1753

66✔
1754
    int64_t key = new_offsets.back();
144✔
1755
    offsets.add(key);
144✔
1756
    m_array->add(ref);
144✔
1757
}
144✔
1758

1759
// Must return true if value of object(key) is less than 'b'.
1760
bool SortedListComparator::operator()(int64_t key_value, const Mixed& b) // used in lower_bound
UNCOV
1761
{
×
UNCOV
1762
    Mixed a = m_column.get_value(ObjKey(key_value));
×
NEW
1763
    if (a.is_null() && !b.is_null())
×
UNCOV
1764
        return true;
×
NEW
1765
    else if (b.is_null() && !a.is_null())
×
UNCOV
1766
        return false;
×
NEW
1767
    else if (a.is_null() && b.is_null())
×
UNCOV
1768
        return false;
×
NEW
1769
    return a.compare(b) < 0;
×
NEW
1770
}
×
1771

1772

1773
// Must return true if value of 'a' is less than value of object(key).
1774
bool SortedListComparator::operator()(const Mixed& a, int64_t key_value) // used in upper_bound
NEW
1775
{
×
NEW
1776
    Mixed b = m_column.get_value(ObjKey(key_value));
×
NEW
1777
    if (a.is_null() && !b.is_null())
×
NEW
1778
        return true;
×
NEW
1779
    else if (b.is_null() && !a.is_null())
×
NEW
1780
        return false;
×
NEW
1781
    else if (a.is_null() && b.is_null())
×
NEW
1782
        return false;
×
NEW
1783
    return a.compare(b) < 0;
×
UNCOV
1784
}
×
1785

1786
// TODO: the next time the StringIndex is migrated (post version 23) and sorted
1787
// properly in these lists replace this method with
1788
// std::lower_bound(key_values.cbegin(), it_end, value, slc);
1789
IntegerColumn::const_iterator SortedListComparator::find_start_of_unsorted(const Mixed& value,
1790
                                                                           const IntegerColumn& key_values) const
1791
{
2,129,391✔
1792
    if (key_values.size() >= 2) {
2,129,391✔
1793
        Mixed first = m_column.get_value(ObjKey(key_values.get(0)));
2,115,525✔
1794
        Mixed last = m_column.get_value(ObjKey(key_values.get(key_values.size() - 1)));
2,115,525✔
1795
        if (first == last) {
2,115,525✔
1796
            if (value.compare(first) <= 0) {
2,108,967✔
1797
                return key_values.cbegin();
2,107,764✔
1798
            }
2,107,764✔
1799
            else {
1,203✔
1800
                return key_values.cend();
1,203✔
1801
            }
1,203✔
1802
        }
20,424✔
1803
    }
2,115,525✔
1804

9,996✔
1805
    IntegerColumn::const_iterator it = key_values.cbegin();
20,424✔
1806
    IntegerColumn::const_iterator end = key_values.cend();
20,424✔
1807
    IntegerColumn::const_iterator first_greater = end;
20,424✔
1808
    while (it != end) {
201,918✔
1809
        Mixed val_it = m_column.get_value(ObjKey(*it));
197,610✔
1810
        int cmp = val_it.compare(value);
197,610✔
1811
        if (cmp == 0) {
197,610✔
1812
            return it;
16,116✔
1813
        }
16,116✔
1814
        if (cmp > 0 && first_greater == end) {
181,494✔
1815
            first_greater = it;
915✔
1816
        }
915✔
1817
        ++it;
181,494✔
1818
    }
181,494✔
1819
    return first_greater;
12,153✔
1820
}
20,424✔
1821

1822
// TODO: same as above, change to std::upper_bound(lower, it_end, value, slc);
1823
IntegerColumn::const_iterator SortedListComparator::find_end_of_unsorted(const Mixed& value,
1824
                                                                         const IntegerColumn& key_values,
1825
                                                                         IntegerColumn::const_iterator begin) const
1826
{
2,116,227✔
1827
    IntegerColumn::const_iterator end = key_values.cend();
2,116,227✔
1828
    if (begin != end && end - begin > 0) {
2,116,227✔
1829
        // optimization: check the last element first
1,056,108✔
1830
        Mixed last = m_column.get_value(ObjKey(*(key_values.cend() - 1)));
2,115,714✔
1831
        if (last == value) {
2,115,714✔
1832
            return key_values.cend();
2,114,700✔
1833
        }
2,114,700✔
1834
    }
1,527✔
1835
    while (begin != end) {
111,195✔
1836
        Mixed val_it = m_column.get_value(ObjKey(*begin));
110,985✔
1837
        if (value.compare(val_it) != 0) {
110,985✔
1838
            return begin;
1,317✔
1839
        }
1,317✔
1840
        ++begin;
109,668✔
1841
    }
109,668✔
1842
    return end;
891✔
1843
}
1,527✔
1844

1845
// LCOV_EXCL_START ignore debug functions
1846

1847
#ifdef REALM_DEBUG
1848
void StringIndex::print() const
NEW
1849
{
×
NEW
1850
    dump_node_structure(*m_array, std::cout, 0);
×
NEW
1851
}
×
1852
#endif // REALM_DEBUG
1853

1854
void StringIndex::verify() const
1855
{
4,248✔
1856
#ifdef REALM_DEBUG
4,248✔
1857
    m_array->verify();
4,248✔
1858

2,124✔
1859
    Allocator& alloc = m_array->get_alloc();
4,248✔
1860
    const size_t array_size = m_array->size();
4,248✔
1861

2,124✔
1862
    // Get first matching row for every key
2,124✔
1863
    if (m_array->is_inner_bptree_node()) {
4,248✔
1864
        for (size_t i = 1; i < array_size; ++i) {
×
1865
            size_t ref = m_array->get_as_ref(i);
×
1866
            StringIndex ndx(ref, nullptr, 0, m_target_column, alloc);
×
1867
            ndx.verify();
×
1868
        }
×
1869
    }
×
1870
    else {
4,248✔
1871
        size_t column_size = m_target_column.size();
4,248✔
1872
        for (size_t i = 1; i < array_size; ++i) {
8,904✔
1873
            int64_t ref = m_array->get(i);
4,656✔
1874

2,328✔
1875
            // low bit set indicate literal ref (shifted)
2,328✔
1876
            if (ref & 1) {
4,656✔
1877
                size_t r = to_size_t((uint64_t(ref) >> 1));
480✔
1878
                REALM_ASSERT_EX(r < column_size, r, column_size);
480✔
1879
            }
480✔
1880
            else {
4,176✔
1881
                // A real ref either points to a list or a subindex
2,088✔
1882
                char* header = alloc.translate(to_ref(ref));
4,176✔
1883
                if (Array::get_context_flag_from_header(header)) {
4,176✔
1884
                    StringIndex ndx(to_ref(ref), m_array.get(), i, m_target_column, alloc);
4,128✔
1885
                    ndx.verify();
4,128✔
1886
                }
4,128✔
1887
                else {
48✔
1888
                    IntegerColumn sub(alloc, to_ref(ref)); // Throws
48✔
1889
                    IntegerColumn::const_iterator it = sub.cbegin();
48✔
1890
                    IntegerColumn::const_iterator it_end = sub.cend();
48✔
1891
                    SortedListComparator slc(m_target_column);
48✔
1892
                    Mixed previous = get(ObjKey(*it));
48✔
1893
                    size_t last_row = to_size_t(*it);
48✔
1894

24✔
1895
                    // Check that strings listed in sub are in sorted order
24✔
1896
                    // and if there are duplicates, that the row numbers are
24✔
1897
                    // sorted in the group of duplicates.
24✔
1898
                    while (it != it_end) {
168✔
1899
                        Mixed it_data = get(ObjKey(*it));
120✔
1900
                        size_t it_row = to_size_t(*it);
120✔
1901
                        REALM_ASSERT(previous <= it_data);
120✔
1902
                        if (it != sub.cbegin() && previous == it_data) {
120✔
1903
                            REALM_ASSERT_EX(it_row > last_row, it_row, last_row);
×
1904
                        }
×
1905
                        last_row = it_row;
120✔
1906
                        previous = get(ObjKey(*it));
120✔
1907
                        ++it;
120✔
1908
                    }
120✔
1909
                }
48✔
1910
            }
4,176✔
1911
        }
4,656✔
1912
    }
4,248✔
1913
// FIXME: Extend verification along the lines of IntegerColumn::verify().
2,124✔
1914
#endif
4,248✔
1915
}
4,248✔
1916

1917
#ifdef REALM_DEBUG
1918

1919
template <class T>
1920
void StringIndex::verify_entries(const ClusterColumn& column) const
1921
{
1922
    std::vector<ObjKey> results;
1923

1924
    auto it = column.begin();
1925
    auto end = column.end();
1926
    auto col = column.get_column_key();
1927
    while (it != end) {
1928
        ObjKey key = it->get_key();
1929
        T value = it->get<T>(col);
1930

1931
        find_all(results, value);
1932

1933
        auto ndx = find(results.begin(), results.end(), key);
1934
        REALM_ASSERT(ndx != results.end());
1935
        size_t found = count(value);
1936
        REALM_ASSERT_EX(found >= 1, found);
1937
        results.clear();
1938
    }
1939
}
1940

1941
namespace {
1942

1943
namespace {
1944

1945
bool is_chars(uint64_t val)
NEW
1946
{
×
NEW
1947
    if (val == 0)
×
NEW
1948
        return true;
×
NEW
1949
    if (is_chars(val >> 8)) {
×
NEW
1950
        char c = val & 0xFF;
×
NEW
1951
        if (!c || std::isalpha(c)) {
×
NEW
1952
            return true;
×
NEW
1953
        }
×
NEW
1954
    }
×
NEW
1955
    return false;
×
NEW
1956
}
×
1957

1958
void out_char(std::ostream& out, uint64_t val)
1959
{
×
1960
    if (val) {
×
NEW
1961
        out_char(out, val >> 8);
×
NEW
1962
        char c = val & 0xFF;
×
NEW
1963
        if (c && c != 'X') {
×
1964
            out << c;
×
1965
        }
×
1966
    }
×
1967
}
×
1968

1969
void out_hex(std::ostream& out, uint64_t val)
NEW
1970
{
×
NEW
1971
    if (is_chars(val)) {
×
NEW
1972
        out_char(out, val);
×
NEW
1973
    }
×
NEW
1974
    else {
×
NEW
1975
        out << int(val);
×
NEW
1976
    }
×
NEW
1977
}
×
1978

1979
} // namespace
1980

1981
} // namespace
1982

1983
void StringIndex::dump_node_structure(const Array& node, std::ostream& out, int level)
1984
{
×
1985
    int indent = level * 2;
×
1986
    Allocator& alloc = node.get_alloc();
×
1987
    Array subnode(alloc);
×
1988

1989
    size_t node_size = node.size();
×
1990
    REALM_ASSERT(node_size >= 1);
×
1991

1992
    out << std::hex;
×
1993

1994
    bool node_is_leaf = !node.is_inner_bptree_node();
×
1995
    if (node_is_leaf) {
×
1996
        out << std::setw(indent) << ""
×
1997
            << "Leaf (B+ tree) (ref: " << node.get_ref() << ")\n";
×
1998
    }
×
1999
    else {
×
2000
        out << std::setw(indent) << ""
×
2001
            << "Inner node (B+ tree) (ref: " << node.get_ref() << ")\n";
×
2002
    }
×
2003

2004
    subnode.init_from_ref(to_ref(node.front()));
×
2005
    out << std::setw(indent) << ""
×
2006
        << "  Keys (keys_ref: " << subnode.get_ref() << ", ";
×
2007
    if (subnode.is_empty()) {
×
2008
        out << "no keys";
×
2009
    }
×
2010
    else {
×
2011
        out << "keys: ";
×
2012
        for (size_t i = 0; i != subnode.size(); ++i) {
×
2013
            if (i != 0)
×
2014
                out << ", ";
×
NEW
2015
            out_hex(out, uint32_t(subnode.get(i)));
×
2016
        }
×
2017
    }
×
2018
    out << ")\n";
×
2019

2020
    if (node_is_leaf) {
×
2021
        for (size_t i = 1; i != node_size; ++i) {
×
2022
            int_fast64_t value = node.get(i);
×
2023
            bool is_single_row_index = (value & 1) != 0;
×
2024
            if (is_single_row_index) {
×
2025
                out << std::setw(indent) << ""
×
2026
                    << "  Single row index (value: " << (value / 2) << ")\n";
×
2027
                continue;
×
2028
            }
×
2029
            subnode.init_from_ref(to_ref(value));
×
2030
            bool is_subindex = subnode.get_context_flag();
×
2031
            if (is_subindex) {
×
2032
                out << std::setw(indent) << ""
×
2033
                    << "  Subindex\n";
×
2034
                dump_node_structure(subnode, out, level + 2);
×
2035
                continue;
×
2036
            }
×
2037
            IntegerColumn indexes(alloc, to_ref(value));
×
2038
            out << std::setw(indent) << ""
×
2039
                << "  List of row indexes\n";
×
2040
            indexes.dump_values(out, level + 2);
×
2041
        }
×
2042
        return;
×
2043
    }
×
2044

2045

2046
    size_t num_children = node_size - 1;
×
2047
    size_t child_ref_begin = 1;
×
2048
    size_t child_ref_end = 1 + num_children;
×
2049
    for (size_t i = child_ref_begin; i != child_ref_end; ++i) {
×
2050
        subnode.init_from_ref(node.get_as_ref(i));
×
2051
        dump_node_structure(subnode, out, level + 1);
×
2052
    }
×
2053
}
×
2054

2055

2056
void StringIndex::do_dump_node_structure(std::ostream& out, int level) const
2057
{
×
2058
    dump_node_structure(*m_array, out, level);
×
2059
}
×
2060

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