• 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

94.1
/src/realm/query_engine.hpp
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
/*
20
A query consists of node objects, one for each query condition. Each node contains pointers to all other nodes:
21

22
node1        node2         node3
23
------       -----         -----
24
node2*       node1*        node1*
25
node3*       node3*        node2*
26

27
The construction of all this takes part in query.cpp. Each node has two important functions:
28

29
    aggregate(start, end)
30
    aggregate_local(start, end)
31

32
The aggregate() function executes the aggregate of a query. You can call the method on any of the nodes
33
(except children nodes of OrNode and SubtableNode) - it has the same behaviour. The function contains
34
scheduling that calls aggregate_local(start, end) on different nodes with different start/end ranges,
35
depending on what it finds is most optimal.
36

37
The aggregate_local() function contains a tight loop that tests the condition of its own node, and upon match
38
it tests all other conditions at that index to report a full match or not. It will remain in the tight loop
39
after a full match.
40

41
So a call stack with 2 and 9 being local matches of a node could look like this:
42

43
aggregate(0, 10)
44
    node1->aggregate_local(0, 3)
45
        node2->find_first_local(2, 3)
46
        node3->find_first_local(2, 3)
47
    node3->aggregate_local(3, 10)
48
        node1->find_first_local(4, 5)
49
        node2->find_first_local(4, 5)
50
        node1->find_first_local(7, 8)
51
        node2->find_first_local(7, 8)
52

53
find_first_local(n, n + 1) is a function that can be used to test a single row of another condition. Note that
54
this is very simplified. There are other statistical arguments to the methods, and also, find_first_local() can be
55
called from a callback function called by an integer Array.
56

57

58
Template arguments in methods:
59
----------------------------------------------------------------------------------------------------
60

61
TConditionFunction: Each node has a condition from query_conditions.c such as Equal, GreaterEqual, etc
62

63
TConditionValue:    Type of values in condition column. That is, int64_t, float, int, bool, etc
64
*/
65

66
#ifndef REALM_QUERY_ENGINE_HPP
67
#define REALM_QUERY_ENGINE_HPP
68

69
#include <algorithm>
70
#include <functional>
71
#include <sstream>
72
#include <string>
73
#include <array>
74

75
#include <realm/array_basic.hpp>
76
#include <realm/array_key.hpp>
77
#include <realm/array_string.hpp>
78
#include <realm/array_binary.hpp>
79
#include <realm/array_timestamp.hpp>
80
#include <realm/array_decimal128.hpp>
81
#include <realm/array_fixed_bytes.hpp>
82
#include <realm/array_mixed.hpp>
83
#include <realm/array_list.hpp>
84
#include <realm/array_bool.hpp>
85
#include <realm/array_backlink.hpp>
86
#include <realm/column_type_traits.hpp>
87
#include <realm/query_conditions.hpp>
88
#include <realm/table.hpp>
89
#include <realm/column_integer.hpp>
90
#include <realm/unicode.hpp>
91
#include <realm/util/miscellaneous.hpp>
92
#include <realm/util/serializer.hpp>
93
#include <realm/utilities.hpp>
94
#include <realm/index_string.hpp>
95

96
#include <map>
97
#include <unordered_set>
98

99
#if REALM_X86_OR_X64_TRUE && defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 160040219
100
#include <immintrin.h>
101
#endif
102

103
namespace realm {
104

105
class IndexEvaluator;
106

107
class ParentNode {
108
    typedef ParentNode ThisType;
109

110
public:
111
    ParentNode() = default;
2,370,837✔
112
    virtual ~ParentNode() = default;
5,416,464✔
113

114
    virtual bool has_search_index() const
115
    {
288,336✔
116
        return false;
288,336✔
117
    }
288,336✔
118
    virtual const IndexEvaluator* index_based_keys()
119
    {
×
120
        return nullptr;
×
121
    }
×
122

123
    void gather_children(std::vector<ParentNode*>& v)
124
    {
2,555,469✔
125
        m_children.clear();
2,555,469✔
126
        size_t i = v.size();
2,555,469✔
127
        v.push_back(this);
2,555,469✔
128

1,258,554✔
129
        if (m_child)
2,555,469✔
130
            m_child->gather_children(v);
32,964✔
131

1,258,554✔
132
        m_children = v;
2,555,469✔
133
        m_children.erase(m_children.begin() + i);
2,555,469✔
134
        m_children.insert(m_children.begin(), this);
2,555,469✔
135
    }
2,555,469✔
136

137
    double cost() const
138
    {
6,673,503✔
139
        constexpr size_t bitwidth_time_unit = 64;
6,673,503✔
140
        // dt = 1/64 to 1. Match dist is 8 times more important than bitwidth
3,233,403✔
141
        return 8 * bitwidth_time_unit / m_dD + m_dT;
6,673,503✔
142
    }
6,673,503✔
143

144
    size_t find_first(size_t start, size_t end);
145

146
    bool match(const Obj& obj);
147

148
    virtual void init(bool will_query_ranges)
149
    {
2,521,038✔
150
        m_dD = 100.0;
2,521,038✔
151

1,220,841✔
152
        if (m_child)
2,521,038✔
153
            m_child->init(will_query_ranges);
32,964✔
154
    }
2,521,038✔
155

156
    void get_link_dependencies(std::vector<TableKey>& tables) const
157
    {
1,620,108✔
158
        collect_dependencies(tables);
1,620,108✔
159
        if (m_child)
1,620,108✔
160
            m_child->get_link_dependencies(tables);
32,898✔
161
    }
1,620,108✔
162

163
    void set_table(ConstTableRef table)
164
    {
3,176,010✔
165
        if (table == m_table)
3,176,010✔
166
            return;
718,632✔
167

1,207,185✔
168
        m_table = table;
2,457,378✔
169
        if (m_child)
2,457,378✔
170
            m_child->set_table(table);
3,990✔
171
        table_changed();
2,457,378✔
172
    }
2,457,378✔
173

174
    void set_cluster(const Cluster* cluster)
175
    {
7,976,523✔
176
        m_cluster = cluster;
7,976,523✔
177
        if (m_child)
7,976,523✔
178
            m_child->set_cluster(cluster);
127,233✔
179
        cluster_changed();
7,976,523✔
180
    }
7,976,523✔
181

182
    virtual void collect_dependencies(std::vector<TableKey>&) const
183
    {
1,594,737✔
184
    }
1,594,737✔
185

186
    virtual size_t find_first_local(size_t start, size_t end) = 0;
187
    virtual size_t find_all_local(size_t start, size_t end);
188

189
    virtual size_t aggregate_local(QueryStateBase* st, size_t start, size_t end, size_t local_limit,
190
                                   ArrayPayload* source_column);
191

192
    virtual std::string validate()
193
    {
6✔
194
        return m_child ? m_child->validate() : "";
6✔
195
    }
6✔
196

197
    ParentNode(const ParentNode& from);
198

199
    void add_child(std::unique_ptr<ParentNode> child)
200
    {
46,365✔
201
        if (m_child)
46,365✔
202
            m_child->add_child(std::move(child));
14,319✔
203
        else
32,046✔
204
            m_child = std::move(child);
32,046✔
205
    }
46,365✔
206

207
    virtual std::unique_ptr<ParentNode> clone() const = 0;
208

209
    virtual std::string describe(util::serializer::SerialisationState&) const
210
    {
×
211
        return "";
×
212
    }
×
213

214
    virtual std::string describe_condition() const
215
    {
×
216
        return "matches";
×
217
    }
×
218

219
    virtual std::string describe_expression(util::serializer::SerialisationState& state) const
220
    {
29,784✔
221
        std::string s;
29,784✔
222
        s = describe(state);
29,784✔
223
        if (m_child) {
29,784✔
224
            s = s + " and " + m_child->describe_expression(state);
804✔
225
        }
804✔
226
        return s;
29,784✔
227
    }
29,784✔
228

229
    bool consume_condition(ParentNode& other, bool ignore_indexes)
230
    {
663,480✔
231
        // We can only combine conditions if they're the same operator on the
332,016✔
232
        // same column and there's no additional conditions ANDed on
332,016✔
233
        if (m_condition_column_key != other.m_condition_column_key)
663,480✔
234
            return false;
852✔
235
        if (m_child || other.m_child)
662,628✔
236
            return false;
204✔
237
        if (typeid(*this) != typeid(other))
662,424✔
238
            return false;
30✔
239

331,473✔
240
        // If a search index is present, don't try to combine conditions since index search is most likely faster.
331,473✔
241
        // Assuming N elements to search and M conditions to check:
331,473✔
242
        // 1) search index present:                     O(log(N)*M)
331,473✔
243
        // 2) no search index, combine conditions:      O(N)
331,473✔
244
        // 3) no search index, conditions not combined: O(N*M)
331,473✔
245
        // In practice N is much larger than M, so if we have a search index, choose 1, otherwise if possible
331,473✔
246
        // choose 2. The exception is if we're inside a Not group or if the query is restricted to a view, as in those
331,473✔
247
        // cases end will always be start+1 and we'll have O(N*M) runtime even with a search index, so we want to
331,473✔
248
        // combine even with an index.
331,473✔
249
        if (has_search_index() && !ignore_indexes)
662,394✔
250
            return false;
643,362✔
251
        return do_consume_condition(other);
19,032✔
252
    }
19,032✔
253

254
    std::unique_ptr<ParentNode> m_child;
255
    std::vector<ParentNode*> m_children;
256
    mutable ColKey m_condition_column_key = ColKey(); // Column of search criteria
257
    ArrayPayload* m_source_column = nullptr;
258

259
    double m_dD;       // Average row distance between each local match at current position
260
    double m_dT = 1.0; // Time overhead of testing index i + 1 if we have just tested index i. > 1 for linear scans, 0
261
    // for index/tableview
262

263
    size_t m_probes = 0;
264
    size_t m_matches = 0;
265

266
protected:
267
    ConstTableRef m_table = ConstTableRef();
268
    const Cluster* m_cluster = nullptr;
269
    QueryStateBase* m_state = nullptr;
270

271
    ColumnType get_real_column_type(ColKey key)
272
    {
×
273
        return m_table.unchecked_ptr()->get_real_column_type(key);
×
274
    }
×
275

276
private:
277
    virtual void table_changed()
278
    {
1,596,705✔
279
    }
1,596,705✔
280
    virtual void cluster_changed()
281
    {
×
282
        // TODO: Should eventually be pure
283
    }
×
284
    virtual bool do_consume_condition(ParentNode&)
285
    {
588✔
286
        return false;
588✔
287
    }
588✔
288
};
289

290

291
class ColumnNodeBase : public ParentNode {
292
protected:
293
    ColumnNodeBase(ColKey column_key)
294
    {
1,523,430✔
295
        m_condition_column_key = column_key;
1,523,430✔
296
    }
1,523,430✔
297

298
    ColumnNodeBase(const ColumnNodeBase& from)
299
        : ParentNode(from)
300
    {
1,628,319✔
301
    }
1,628,319✔
302

303
};
304

305
class IndexEvaluator {
306
public:
307
    void init(SearchIndex* index, Mixed value);
308
    void init(std::vector<ObjKey>* storage);
309

310
    size_t do_search_index(const Cluster* cluster, size_t start, size_t end);
311

312
    size_t size() const
313
    {
10,842✔
314
        if (m_matching_keys) {
10,842✔
315
            return m_matching_keys->size();
426✔
316
        }
426✔
317
        return m_results_end - m_results_start;
10,416✔
318
    }
10,416✔
319
    ObjKey get(size_t ndx) const
320
    {
262,431✔
321
        return get_internal(ndx + m_results_start);
262,431✔
322
    }
262,431✔
323

324
private:
325
    ObjKey get_internal(size_t ndx) const
326
    {
72,387,537✔
327
        if (m_matching_keys) {
72,387,537✔
328
            return m_matching_keys->at(ndx);
606✔
329
        }
606✔
330
        if (m_index_matches) {
72,386,931✔
331
            return ObjKey(m_index_matches->get(ndx));
72,378,990✔
332
        }
72,378,990✔
333
        else if (m_results_end == 1) {
7,941✔
334
            REALM_ASSERT_EX(ndx == 0, ndx);
7,059✔
335
            return m_actual_key;
7,059✔
336
        }
7,059✔
337
        return ObjKey();
882✔
338
    }
882✔
339

340
    std::shared_ptr<IntegerColumn> m_index_matches;
341
    ObjKey m_actual_key;
342
    ObjKey m_last_start_key;
343
    size_t m_results_start = 0;
344
    size_t m_results_ndx = 0;
345
    size_t m_results_end = 0;
346

347
    std::vector<ObjKey>* m_matching_keys = nullptr;
348
};
349

350
template <class LeafType>
351
class IntegerNodeBase : public ColumnNodeBase {
352
public:
353
    using TConditionValue = typename LeafType::value_type;
354

355
protected:
356
    IntegerNodeBase(TConditionValue value, ColKey column_key)
357
        : ColumnNodeBase(column_key)
358
        , m_value(std::move(value))
359
    {
1,534,056✔
360
    }
1,534,056✔
361

362
    IntegerNodeBase(const IntegerNodeBase& from)
363
        : ColumnNodeBase(from)
364
        , m_value(from.m_value)
365
    {
1,647,246✔
366
    }
1,647,246✔
367

368
    void cluster_changed() override
369
    {
6,596,817✔
370
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
6,596,817✔
371
        m_cluster->init_leaf(this->m_condition_column_key, &*m_leaf);
6,596,817✔
372
    }
6,596,817✔
373

374
    void init(bool will_query_ranges) override
375
    {
1,569,903✔
376
        ColumnNodeBase::init(will_query_ranges);
1,569,903✔
377

738,660✔
378
        m_dT = .25;
1,569,903✔
379
    }
1,569,903✔
380

381
    bool run_single() const
382
    {
5,871,069✔
383
        if (m_source_column == nullptr)
5,871,069✔
384
            return true;
5,387,145✔
385
        // Compare leafs to see if they are the same
375,813✔
386
        auto leaf = dynamic_cast<LeafType*>(m_source_column);
483,924✔
387
        return leaf && leaf->get_ref() == m_leaf->get_ref();
484,884✔
388
    }
483,924✔
389

390
    template <class TConditionFunction>
391
    size_t find_all_local(size_t start, size_t end)
392
    {
5,701,860✔
393
        if (run_single()) {
5,916,504✔
394
            m_leaf->template find<TConditionFunction>(m_value, start, end, m_state, nullptr);
5,916,504✔
395
        }
5,916,504✔
396
        else {
4,294,967,294✔
397
            auto callback = [this](size_t index) {
2,150,481,043✔
398
                auto val = m_source_column->get_any(index);
5,994,792✔
399
                return m_state->match(index, val);
5,994,792✔
400
            };
5,994,792✔
401
            m_leaf->template find<TConditionFunction>(m_value, start, end, m_state, callback);
4,294,967,294✔
402
        }
4,294,967,294✔
403

2,536,878✔
404
        return end;
5,701,860✔
405
    }
5,701,860✔
406

407
    std::string describe(util::serializer::SerialisationState& state) const override
408
    {
720✔
409
        return state.describe_column(ParentNode::m_table, ColumnNodeBase::m_condition_column_key) + " " +
720✔
410
               describe_condition() + " " + util::serializer::print_value(this->m_value);
720✔
411
    }
720✔
412

413

414
    // Search value:
415
    TConditionValue m_value;
416

417
    // Leaf cache
418
    std::optional<LeafType> m_leaf;
419
};
420

421

422
template <class LeafType, class TConditionFunction>
423
class IntegerNode : public IntegerNodeBase<LeafType> {
424
    using BaseType = IntegerNodeBase<LeafType>;
425
    using ThisType = IntegerNode<LeafType, TConditionFunction>;
426

427
public:
428
    using TConditionValue = typename BaseType::TConditionValue;
429

430
    IntegerNode(TConditionValue value, ColKey column_key)
431
        : BaseType(value, column_key)
432
    {
91,941✔
433
    }
91,941✔
434
    IntegerNode(const IntegerNode& from)
435
        : BaseType(from)
436
    {
135,177✔
437
    }
135,177✔
438

439
    size_t find_first_local(size_t start, size_t end) override
440
    {
223,569✔
441
        return this->m_leaf->template find_first<TConditionFunction>(this->m_value, start, end);
223,569✔
442
    }
223,569✔
443

444
    size_t find_all_local(size_t start, size_t end) override
445
    {
310,923✔
446
        return BaseType::template find_all_local<TConditionFunction>(start, end);
310,923✔
447
    }
310,923✔
448

449
    std::string describe_condition() const override
450
    {
720✔
451
        return TConditionFunction::description();
720✔
452
    }
720✔
453

454
    std::unique_ptr<ParentNode> clone() const override
455
    {
135,174✔
456
        return std::unique_ptr<ParentNode>(new ThisType(*this));
135,174✔
457
    }
135,174✔
458
};
459

460
template <size_t linear_search_threshold, class LeafType, class NeedleContainer>
461
static size_t find_first_haystack(LeafType& leaf, NeedleContainer& needles, size_t start, size_t end)
462
{
1,016,610✔
463
    // for a small number of conditions, it is faster to do a linear search than to compute the hash
439,905✔
464
    // the exact thresholds were found experimentally
439,905✔
465
    if (needles.size() < linear_search_threshold) {
1,016,610✔
466
        for (size_t i = start; i < end; ++i) {
1,473,129✔
467
            auto element = leaf.get(i);
1,301,517✔
468
            if (std::find(needles.begin(), needles.end(), element) != needles.end())
1,301,517✔
469
                return i;
844,356✔
470
        }
1,301,517✔
471
    }
1,015,968✔
472
    else {
642✔
473
        for (size_t i = start; i < end; ++i) {
2,454✔
474
            auto element = leaf.get(i);
2,412✔
475
            if (needles.count(element))
2,412✔
476
                return i;
600✔
477
        }
2,412✔
478
    }
642✔
479
    return realm::npos;
535,905✔
480
}
1,016,610✔
481

482
template <class LeafType>
483
class IntegerNode<LeafType, Equal> : public IntegerNodeBase<LeafType> {
484
public:
485
    using BaseType = IntegerNodeBase<LeafType>;
486
    using TConditionValue = typename BaseType::TConditionValue;
487
    using ThisType = IntegerNode<LeafType, Equal>;
488

489
    IntegerNode(TConditionValue value, ColKey column_key)
490
        : BaseType(value, column_key)
491
    {
1,429,104✔
492
    }
1,429,104✔
493

494
    void init(bool will_query_ranges) override
495
    {
1,469,835✔
496
        BaseType::init(will_query_ranges);
1,469,835✔
497
        m_nb_needles = m_needles.size();
1,469,835✔
498

714,708✔
499
        if (has_search_index() && m_nb_needles == 0) {
1,469,835✔
500
            SearchIndex* index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
7,692✔
501
            m_index_evaluator = IndexEvaluator();
7,692✔
502
            m_index_evaluator->init(index, BaseType::m_value);
7,692✔
503
            IntegerNodeBase<LeafType>::m_dT = 0;
7,692✔
504
        }
7,692✔
505
    }
1,469,835✔
506

507
    bool do_consume_condition(ParentNode& node) override
508
    {
17,988✔
509
        auto& other = static_cast<ThisType&>(node);
17,988✔
510
        REALM_ASSERT(this->m_condition_column_key == other.m_condition_column_key);
17,988✔
511
        REALM_ASSERT(other.m_needles.empty());
17,988✔
512
        if (m_needles.empty()) {
17,988✔
513
            m_needles.insert(this->m_value);
17,700✔
514
        }
17,700✔
515
        m_needles.insert(other.m_value);
17,988✔
516
        return true;
17,988✔
517
    }
17,988✔
518

519
    bool has_search_index() const override
520
    {
2,815,083✔
521
        return this->m_table->search_index_type(IntegerNodeBase<LeafType>::m_condition_column_key) ==
2,815,083✔
522
               IndexType::General;
2,815,083✔
523
    }
2,815,083✔
524

525
    const IndexEvaluator* index_based_keys() override
526
    {
6,858✔
527
        return m_index_evaluator ? &(*m_index_evaluator) : nullptr;
6,858✔
528
    }
6,858✔
529

530
    size_t find_first_local(size_t start, size_t end) override
531
    {
4,050,357✔
532
        REALM_ASSERT(this->m_table);
4,050,357✔
533
        size_t s = realm::npos;
4,050,357✔
534

1,962,210✔
535
        if (start < end) {
4,050,357✔
536
            if (m_nb_needles) {
4,046,247✔
537
                s = find_first_haystack<22>(*this->m_leaf, m_needles, start, end);
1,015,734✔
538
            }
1,015,734✔
539
            else if (m_index_evaluator) {
3,030,513✔
540
                return m_index_evaluator->do_search_index(BaseType::m_cluster, start, end);
1,764✔
541
            }
1,764✔
542
            else if (end - start == 1) {
3,028,749✔
543
                if (this->m_leaf->get(start) == this->m_value) {
1,262,496✔
544
                    s = start;
557,535✔
545
                }
557,535✔
546
            }
1,262,496✔
547
            else {
1,766,253✔
548
                s = this->m_leaf->template find_first<Equal>(this->m_value, start, end);
1,766,253✔
549
            }
1,766,253✔
550
        }
4,046,247✔
551

1,962,210✔
552
        return s;
4,049,478✔
553
    }
4,050,357✔
554

555
    size_t find_all_local(size_t start, size_t end) override
556
    {
5,704,113✔
557
        return BaseType::template find_all_local<Equal>(start, end);
5,704,113✔
558
    }
5,704,113✔
559

560
    std::string describe(util::serializer::SerialisationState& state) const override
561
    {
1,176✔
562
        REALM_ASSERT(this->m_condition_column_key);
1,176✔
563
        std::string col_descr = state.describe_column(this->m_table, this->m_condition_column_key);
1,176✔
564

588✔
565
        if (m_needles.empty()) {
1,176✔
566
            return col_descr + " " + Equal::description() + " " +
1,122✔
567
                   util::serializer::print_value(IntegerNodeBase<LeafType>::m_value);
1,122✔
568
        }
1,122✔
569

27✔
570
        std::string list_contents;
54✔
571
        bool is_first = true;
54✔
572
        for (auto it : m_needles) {
390✔
573
            list_contents +=
390✔
574
                util::format("%1%2", is_first ? "" : ", ", util::serializer::print_value(it)); // "it" may be null
363✔
575
            is_first = false;
390✔
576
        }
390✔
577
        std::string desc = util::format("%1 IN {%2}", col_descr, list_contents);
54✔
578
        return desc;
54✔
579
    }
54✔
580

581
    std::unique_ptr<ParentNode> clone() const override
582
    {
1,409,973✔
583
        return std::unique_ptr<ParentNode>(new ThisType(*this));
1,409,973✔
584
    }
1,409,973✔
585

586
private:
587
    std::unordered_set<TConditionValue> m_needles;
588
    size_t m_nb_needles = 0;
589
    std::optional<IndexEvaluator> m_index_evaluator;
590

591
    IntegerNode(const IntegerNode<LeafType, Equal>& from)
592
        : BaseType(from)
593
        , m_needles(from.m_needles)
594
    {
1,422,849✔
595
    }
1,422,849✔
596
};
597

598

599
// This node is currently used for floats and doubles only
600
template <class LeafType, class TConditionFunction>
601
class FloatDoubleNode : public ParentNode {
602
public:
603
    using TConditionValue = typename LeafType::value_type;
604
    static const bool special_null_node = false;
605

606
    FloatDoubleNode(TConditionValue v, ColKey column_key)
607
        : m_value(v)
608
    {
11,904✔
609
        m_condition_column_key = column_key;
11,904✔
610
        m_dT = 1.0;
11,904✔
611
    }
11,904✔
612
    FloatDoubleNode(null, ColKey column_key)
613
        : m_value(null::get_null_float<TConditionValue>())
614
    {
588✔
615
        m_condition_column_key = column_key;
588✔
616
        m_dT = 1.0;
588✔
617
    }
588✔
618

619
    void cluster_changed() override
620
    {
28,464✔
621
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
28,464✔
622
        m_cluster->init_leaf(this->m_condition_column_key, &*m_leaf);
28,464✔
623
    }
28,464✔
624

625
    size_t find_first_local(size_t start, size_t end) override
626
    {
355,908✔
627
        TConditionFunction cond;
355,908✔
628

177,954✔
629
        auto find = [&](bool nullability) {
355,908✔
630
            bool value_nan = nullability ? null::is_null_float(m_value) : false;
350,406✔
631
            for (size_t s = start; s < end; ++s) {
1,105,920✔
632
                TConditionValue v = m_leaf->get(s);
1,033,848✔
633
                REALM_ASSERT(!(null::is_null_float(v) && !nullability));
1,033,848!
634
                if (cond(v, m_value, nullability ? null::is_null_float<TConditionValue>(v) : false, value_nan))
1,033,848✔
635
                    return s;
283,836✔
636
            }
1,033,848✔
637
            return not_found;
213,990✔
638
        };
355,908✔
639

177,954✔
640
        // This will inline the second case but no the first. Todo, use templated lambda when switching to c++14
177,954✔
641
        if (m_table->is_nullable(m_condition_column_key))
355,908✔
642
            return find(true);
11,004✔
643
        else
344,904✔
644
            return find(false);
344,904✔
645
    }
355,908✔
646

647
    std::string describe(util::serializer::SerialisationState& state) const override
648
    {
270✔
649
        REALM_ASSERT(m_condition_column_key);
270!
650
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " + describe_condition() + " " +
270✔
651
               util::serializer::print_value(FloatDoubleNode::m_value);
270✔
652
    }
270✔
653
    std::string describe_condition() const override
654
    {
270✔
655
        return TConditionFunction::description();
270✔
656
    }
270✔
657

658
    std::unique_ptr<ParentNode> clone() const override
659
    {
11,334✔
660
        return std::unique_ptr<ParentNode>(new FloatDoubleNode(*this));
11,334✔
661
    }
11,334✔
662

663
    FloatDoubleNode(const FloatDoubleNode& from)
664
        : ParentNode(from)
665
        , m_value(from.m_value)
666
    {
11,334✔
667
    }
11,334✔
668

669
protected:
670
    TConditionValue m_value;
671
    std::optional<LeafType> m_leaf;
672
};
673

674
template <class T, class TConditionFunction>
675
class SizeNode : public ParentNode {
676
public:
677
    SizeNode(int64_t v, ColKey column)
678
        : m_value(v)
679
    {
12✔
680
        m_condition_column_key = column;
12✔
681
        m_dT = 20.0;
12✔
682
    }
12✔
683

684
    void cluster_changed() override
685
    {
12✔
686
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
12✔
687
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
12✔
688
    }
12✔
689

690
    size_t find_first_local(size_t start, size_t end) override
691
    {
18✔
692
        for (size_t s = start; s < end; ++s) {
78!
693
            if (T v = m_leaf->get(s)) {
72!
694
                int64_t sz = v.size();
24✔
695
                if (TConditionFunction()(sz, m_value))
24!
696
                    return s;
12✔
697
            }
24✔
698
        }
72✔
699
        return not_found;
12✔
700
    }
18✔
701

702
    std::unique_ptr<ParentNode> clone() const override
703
    {
18✔
704
        return std::unique_ptr<ParentNode>(new SizeNode(*this));
18✔
705
    }
18✔
706

707
    SizeNode(const SizeNode& from)
708
        : ParentNode(from)
709
        , m_value(from.m_value)
710
    {
18✔
711
    }
18✔
712

713
private:
714
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
715
    std::optional<LeafType> m_leaf;
716
    int64_t m_value;
717
};
718

719
extern size_t size_of_list_from_ref(ref_type ref, Allocator& alloc, ColumnType col_type, bool nullable);
720

721
template <class TConditionFunction>
722
class SizeListNode : public ParentNode {
723
public:
724
    SizeListNode(int64_t v, ColKey column)
725
        : m_value(v)
726
    {
144✔
727
        m_condition_column_key = column;
144✔
728
        m_dT = 30.0;
144✔
729
    }
144✔
730

731
    void reset_cache()
732
    {
288✔
733
        m_cached_col_type = m_condition_column_key.get_type();
288✔
734
        m_cached_nullable = m_condition_column_key.is_nullable();
288✔
735
        REALM_ASSERT_DEBUG(m_condition_column_key.is_list());
288!
736
    }
288✔
737

738
    void cluster_changed() override
739
    {
144✔
740
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
144✔
741
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
144✔
742
        reset_cache();
144✔
743
    }
144✔
744

745
    void init(bool will_query_ranges) override
746
    {
144✔
747
        ParentNode::init(will_query_ranges);
144✔
748
        reset_cache();
144✔
749
    }
144✔
750

751
    size_t find_first_local(size_t start, size_t end) override
752
    {
204✔
753
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
204✔
754
        for (size_t s = start; s < end; ++s) {
294!
755
            if (ref_type ref = m_leaf->get(s)) {
276!
756
                int64_t sz = size_of_list_from_ref(ref, alloc, m_cached_col_type, m_cached_nullable);
204✔
757
                if (TConditionFunction()(sz, m_value))
204!
758
                    return s;
186✔
759
            }
204✔
760
        }
276✔
761
        return not_found;
111✔
762
    }
204✔
763

764
    std::unique_ptr<ParentNode> clone() const override
765
    {
162✔
766
        return std::unique_ptr<ParentNode>(new SizeListNode(*this));
162✔
767
    }
162✔
768

769
    SizeListNode(const SizeListNode& from)
770
        : ParentNode(from)
771
        , m_value(from.m_value)
772
    {
162✔
773
    }
162✔
774

775
private:
776
    std::optional<ArrayList> m_leaf;
777

778
    int64_t m_value;
779

780
    ColumnType m_cached_col_type;
781
    bool m_cached_nullable;
782
};
783

784

785
template <class TConditionFunction>
786
class BinaryNode : public ParentNode {
787
public:
788
    using TConditionValue = BinaryData;
789
    static const bool special_null_node = false;
790

791
    BinaryNode(BinaryData v, ColKey column)
792
        : m_value(v)
793
    {
7,356✔
794
        m_condition_column_key = column;
7,356✔
795
        m_dT = 100.0;
7,356✔
796
    }
7,356✔
797

798
    BinaryNode(null, ColKey column)
799
        : BinaryNode(BinaryData{}, column)
800
    {
384✔
801
    }
384✔
802

803
    void cluster_changed() override
804
    {
12,666✔
805
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
12,666✔
806
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
12,666✔
807
    }
12,666✔
808

809
    size_t find_first_local(size_t start, size_t end) override
810
    {
57,174✔
811
        TConditionFunction condition;
57,174✔
812
        for (size_t s = start; s < end; ++s) {
1,550,250!
813
            BinaryData value = m_leaf->get(s);
1,539,912✔
814
            if (condition(m_value.get(), value))
1,539,912!
815
                return s;
46,836✔
816
        }
1,539,912✔
817
        return not_found;
33,756✔
818
    }
57,174✔
819

820
    std::string describe(util::serializer::SerialisationState& state) const override
821
    {
2,274✔
822
        REALM_ASSERT(m_condition_column_key);
2,274!
823
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " +
2,274✔
824
               TConditionFunction::description() + " " + util::serializer::print_value(BinaryNode::m_value.get());
2,274✔
825
    }
2,274✔
826

827
    std::unique_ptr<ParentNode> clone() const override
828
    {
9,300✔
829
        return std::unique_ptr<ParentNode>(new BinaryNode(*this));
9,300✔
830
    }
9,300✔
831

832
    BinaryNode(const BinaryNode& from)
833
        : ParentNode(from)
834
        , m_value(from.m_value)
835
    {
9,300✔
836
    }
9,300✔
837

838
private:
839
    OwnedBinaryData m_value;
840
    std::optional<ArrayBinary> m_leaf;
841
};
842

843
template <class TConditionFunction>
844
class BoolNode : public ParentNode {
845
public:
846
    using TConditionValue = bool;
847

848
    BoolNode(util::Optional<bool> v, ColKey column)
849
        : m_value(v)
850
    {
2,070✔
851
        m_condition_column_key = column;
2,070✔
852
    }
2,070✔
853

854
    BoolNode(const BoolNode& from)
855
        : ParentNode(from)
856
        , m_value(from.m_value)
857
        , m_index_evaluator(from.m_index_evaluator)
858
    {
2,016✔
859
    }
2,016✔
860

861
    void init(bool will_query_ranges) override
862
    {
3,126✔
863
        ParentNode::init(will_query_ranges);
3,126✔
864

1,563✔
865
        if constexpr (std::is_same_v<TConditionFunction, Equal>) {
3,126✔
866
            if (m_index_evaluator) {
1,890✔
NEW
867
                SearchIndex* index = m_table->get_search_index(m_condition_column_key);
×
868
                m_index_evaluator->init(index, m_value);
×
869
                this->m_dT = 0;
×
870
            }
×
871
        }
1,890✔
872
    }
3,126✔
873

874
    void table_changed() override
875
    {
2,538✔
876
        if constexpr (std::is_same_v<TConditionFunction, Equal>) {
2,538✔
877
            const bool has_index = m_table->search_index_type(m_condition_column_key) == IndexType::General;
1,482✔
878
            m_index_evaluator = has_index ? std::make_optional(IndexEvaluator{}) : std::nullopt;
1,482✔
879
        }
1,482✔
880
    }
2,538✔
881

882
    const IndexEvaluator* index_based_keys() override
883
    {
×
884
        return m_index_evaluator ? &(*m_index_evaluator) : nullptr;
×
885
    }
×
886

887
    bool has_search_index() const override
888
    {
3,024✔
889
        return bool(m_index_evaluator);
3,024✔
890
    }
3,024✔
891

892
    void cluster_changed() override
893
    {
3,480✔
894
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
3,480✔
895
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
3,480✔
896
    }
3,480✔
897

898
    size_t find_first_local(size_t start, size_t end) override
899
    {
31,236✔
900
        if constexpr (std::is_same_v<TConditionFunction, Equal>) {
31,236✔
901
            if (m_index_evaluator) {
29,964✔
902
                return m_index_evaluator->do_search_index(m_cluster, start, end);
×
903
            }
×
904
        }
29,964✔
905

14,982✔
906
        TConditionFunction condition;
29,964✔
907
        bool m_value_is_null = !m_value;
29,964✔
908
        for (size_t s = start; s < end; ++s) {
56,664✔
909
            auto value = m_leaf->get(s);
54,942✔
910
            if (condition(value, m_value, !value, m_value_is_null))
54,942✔
911
                return s;
29,514✔
912
        }
54,942✔
913
        return not_found;
15,843✔
914
    }
29,964✔
915

916
    std::string describe(util::serializer::SerialisationState& state) const override
917
    {
96✔
918
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " +
96✔
919
               TConditionFunction::description() + " " + util::serializer::print_value(m_value);
96✔
920
    }
96✔
921

922
    std::unique_ptr<ParentNode> clone() const override
923
    {
2,016✔
924
        return std::unique_ptr<ParentNode>(new BoolNode(*this));
2,016✔
925
    }
2,016✔
926

927
private:
928
    std::optional<bool> m_value;
929
    std::optional<ArrayBoolNull> m_leaf;
930
    std::optional<IndexEvaluator> m_index_evaluator;
931
};
932

933
class TimestampNodeBase : public ParentNode {
934
public:
935
    using TConditionValue = Timestamp;
936
    static const bool special_null_node = false;
937

938
    TimestampNodeBase(Timestamp v, ColKey column)
939
        : m_value(v)
940
    {
6,060✔
941
        m_condition_column_key = column;
6,060✔
942
        m_dT = 2.0;
6,060✔
943
    }
6,060✔
944

945
    TimestampNodeBase(null, ColKey column)
946
        : TimestampNodeBase(Timestamp{}, column)
947
    {
282✔
948
    }
282✔
949

950
    void cluster_changed() override
951
    {
8,424✔
952
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
8,424✔
953
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
8,424✔
954
    }
8,424✔
955

956
protected:
957
    TimestampNodeBase(const TimestampNodeBase& from)
958
        : ParentNode(from)
959
        , m_value(from.m_value)
960
    {
5,298✔
961
    }
5,298✔
962

963
    Timestamp m_value;
964
    std::optional<ArrayTimestamp> m_leaf;
965
};
966

967
template <class TConditionFunction>
968
class TimestampNode : public TimestampNodeBase {
969
public:
970
    using TimestampNodeBase::TimestampNodeBase;
971

972
    void init(bool will_query_ranges) override
973
    {
8,556✔
974
        TimestampNodeBase::init(will_query_ranges);
8,556✔
975

4,278✔
976
        if constexpr (std::is_same_v<TConditionFunction, Equal>) {
8,556✔
977
            if (m_index_evaluator) {
1,992✔
978
                SearchIndex* index =
132✔
979
                    TimestampNodeBase::m_table->get_search_index(TimestampNodeBase::m_condition_column_key);
132✔
980
                m_index_evaluator->init(index, TimestampNodeBase::m_value);
132✔
981
                this->m_dT = 0;
132✔
982
            }
132✔
983
        }
1,992✔
984
    }
8,556✔
985

986
    void table_changed() override
987
    {
7,428✔
988
        if constexpr (std::is_same_v<TConditionFunction, Equal>) {
7,428✔
989
            const bool has_index =
1,794✔
990
                this->m_table->search_index_type(TimestampNodeBase::m_condition_column_key) == IndexType::General;
1,794✔
991
            m_index_evaluator = has_index ? std::make_optional(IndexEvaluator{}) : std::nullopt;
1,728✔
992
        }
1,794✔
993
    }
7,428✔
994

995
    const IndexEvaluator* index_based_keys() override
996
    {
132✔
997
        return m_index_evaluator ? &*m_index_evaluator : nullptr;
132!
998
    }
132✔
999

1000
    bool has_search_index() const override
1001
    {
8,436✔
1002
        return bool(m_index_evaluator);
8,436✔
1003
    }
8,436✔
1004

1005
    size_t find_first_local(size_t start, size_t end) override
1006
    {
44,316✔
1007
        if constexpr (std::is_same_v<TConditionFunction, Equal>) {
44,316✔
1008
            if (m_index_evaluator) {
6,300✔
1009
                return m_index_evaluator->do_search_index(this->m_cluster, start, end);
×
1010
            }
×
1011
        }
6,300✔
1012
        return m_leaf->find_first<TConditionFunction>(m_value, start, end);
6,300✔
1013
    }
6,300✔
1014

1015
    std::string describe(util::serializer::SerialisationState& state) const override
1016
    {
156✔
1017
        REALM_ASSERT(m_condition_column_key);
156!
1018
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " +
156✔
1019
               TConditionFunction::description() + " " + util::serializer::print_value(TimestampNode::m_value);
156✔
1020
    }
156✔
1021

1022
    std::unique_ptr<ParentNode> clone() const override
1023
    {
5,298✔
1024
        return std::unique_ptr<ParentNode>(new TimestampNode(*this));
5,298✔
1025
    }
5,298✔
1026

1027
protected:
1028
    std::optional<IndexEvaluator> m_index_evaluator;
1029
};
1030

1031
class DecimalNodeBase : public ParentNode {
1032
public:
1033
    using TConditionValue = Decimal128;
1034
    static const bool special_null_node = false;
1035

1036
    DecimalNodeBase(Decimal128 v, ColKey column)
1037
        : m_value(v)
1038
    {
1,164✔
1039
        m_condition_column_key = column;
1,164✔
1040
    }
1,164✔
1041

1042
    DecimalNodeBase(null, ColKey column)
1043
        : DecimalNodeBase(Decimal128{null()}, column)
1044
    {
6✔
1045
    }
6✔
1046

1047
    void cluster_changed() override
1048
    {
1,158✔
1049
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
1,158✔
1050
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
1,158✔
1051
    }
1,158✔
1052

1053
    void init(bool will_query_ranges) override
1054
    {
1,158✔
1055
        ParentNode::init(will_query_ranges);
1,158✔
1056

579✔
1057
        m_dD = 100.0;
1,158✔
1058
    }
1,158✔
1059

1060
protected:
1061
    DecimalNodeBase(const DecimalNodeBase& from)
1062
        : ParentNode(from)
1063
        , m_value(from.m_value)
1064
    {
2,238✔
1065
    }
2,238✔
1066

1067
    Decimal128 m_value;
1068
    std::optional<ArrayDecimal128> m_leaf;
1069
};
1070

1071
template <class TConditionFunction>
1072
class DecimalNode : public DecimalNodeBase {
1073
public:
1074
    using DecimalNodeBase::DecimalNodeBase;
1075

1076
    size_t find_first_local(size_t start, size_t end) override
1077
    {
4,404✔
1078
        TConditionFunction cond;
4,404✔
1079
        bool value_is_null = m_value.is_null();
4,404✔
1080
        for (size_t i = start; i < end; i++) {
24,168!
1081
            Decimal128 val = m_leaf->get(i);
22,950✔
1082
            if (cond(val, m_value, val.is_null(), value_is_null))
22,950!
1083
                return i;
3,186✔
1084
        }
22,950✔
1085
        return realm::npos;
2,811✔
1086
    }
4,404✔
1087

1088
    std::string describe(util::serializer::SerialisationState& state) const override
1089
    {
528✔
1090
        REALM_ASSERT(m_condition_column_key);
528!
1091
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " +
528✔
1092
               TConditionFunction::description() + " " + util::serializer::print_value(DecimalNode::m_value);
528✔
1093
    }
528✔
1094

1095
    std::unique_ptr<ParentNode> clone() const override
1096
    {
2,238✔
1097
        return std::unique_ptr<ParentNode>(new DecimalNode(*this));
2,238✔
1098
    }
2,238✔
1099
};
1100

1101
template <class ObjectType, class ArrayType>
1102
class FixedBytesNodeBase : public ParentNode {
1103
public:
1104
    using TConditionValue = ObjectType;
1105
    static const bool special_null_node = false;
1106

1107
    FixedBytesNodeBase(ObjectType v, ColKey column)
1108
        : m_value(v)
1109
    {
644,676✔
1110
        m_condition_column_key = column;
644,676✔
1111
    }
644,676✔
1112

1113
    FixedBytesNodeBase(null, ColKey column)
1114
        : FixedBytesNodeBase(ObjectType{}, column)
1115
    {
78✔
1116
        m_value_is_null = true;
78✔
1117
    }
78✔
1118

1119
    void cluster_changed() override
1120
    {
644,496✔
1121
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
644,496✔
1122
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
644,496✔
1123
    }
644,496✔
1124

1125
    void init(bool will_query_ranges) override
1126
    {
644,364✔
1127
        ParentNode::init(will_query_ranges);
644,364✔
1128

322,182✔
1129
        m_dD = 100.0;
644,364✔
1130
    }
644,364✔
1131

1132
protected:
1133
    FixedBytesNodeBase(const FixedBytesNodeBase& from)
1134
        : ParentNode(from)
1135
        , m_value(from.m_value)
1136
        , m_value_is_null(from.m_value_is_null)
1137
    {
1,287,330✔
1138
    }
1,287,330✔
1139

1140
    ObjectType m_value;
1141
    std::optional<ArrayType> m_leaf;
1142
    bool m_value_is_null = false;
1143
};
1144

1145
template <class TConditionFunction, class ObjectType, class ArrayType>
1146
class FixedBytesNode : public FixedBytesNodeBase<ObjectType, ArrayType> {
1147
public:
1148
    using FixedBytesNodeBase<ObjectType, ArrayType>::FixedBytesNodeBase;
1149

1150
    size_t find_first_local(size_t start, size_t end) override
1151
    {
100,812✔
1152
        TConditionFunction cond;
100,812✔
1153
        for (size_t i = start; i < end; i++) {
208,584✔
1154
            util::Optional<ObjectType> val = this->m_leaf->get(i);
207,696✔
1155
            if (val) {
207,696✔
1156
                if (cond(*val, this->m_value, false, this->m_value_is_null))
172,524✔
1157
                    return i;
88,248✔
1158
            }
35,172✔
1159
            else {
35,172✔
1160
                if (cond(ObjectType{}, this->m_value, true, this->m_value_is_null))
35,172✔
1161
                    return i;
11,676✔
1162
            }
35,172✔
1163
        }
207,696✔
1164
        return realm::npos;
50,850✔
1165
    }
100,812✔
1166

1167
    std::string describe(util::serializer::SerialisationState& state) const override
1168
    {
396✔
1169
        REALM_ASSERT(this->m_condition_column_key);
396✔
1170
        return state.describe_column(ParentNode::m_table, this->m_condition_column_key) + " " +
396✔
1171
               TConditionFunction::description() + " " +
396✔
1172
               (this->m_value_is_null ? util::serializer::print_value(realm::null())
198✔
1173
                                      : util::serializer::print_value(this->m_value));
396✔
1174
    }
396✔
1175

1176
    std::unique_ptr<ParentNode> clone() const override
1177
    {
1,632✔
1178
        return std::unique_ptr<ParentNode>(new FixedBytesNode(*this));
1,632✔
1179
    }
1,632✔
1180
};
1181

1182
template <class ObjectType, class ArrayType>
1183
class FixedBytesNode<Equal, ObjectType, ArrayType> : public FixedBytesNodeBase<ObjectType, ArrayType> {
1184
public:
1185
    using FixedBytesNodeBase<ObjectType, ArrayType>::FixedBytesNodeBase;
1186
    using BaseType = FixedBytesNodeBase<ObjectType, ArrayType>;
1187

1188
    void init(bool will_query_ranges) override
1189
    {
643,368✔
1190
        BaseType::init(will_query_ranges);
643,368✔
1191

321,684✔
1192
        if (!this->m_value_is_null) {
643,368✔
1193
            m_optional_value = this->m_value;
643,290✔
1194
        }
643,290✔
1195

321,684✔
1196
        if (m_index_evaluator) {
643,368✔
1197
            SearchIndex* index = BaseType::m_table->get_search_index(BaseType::m_condition_column_key);
642,876✔
1198
            m_index_evaluator->init(index, m_optional_value);
642,876✔
1199
            this->m_dT = 0;
642,876✔
1200
        }
642,876✔
1201
    }
643,368✔
1202

1203
    void table_changed() override
1204
    {
643,692✔
1205
        const bool has_index =
643,692✔
1206
            this->m_table->search_index_type(BaseType::m_condition_column_key) == IndexType::General;
643,692✔
1207
        m_index_evaluator = has_index ? std::make_optional(IndexEvaluator{}) : std::nullopt;
643,440✔
1208
    }
643,692✔
1209

1210
    const IndexEvaluator* index_based_keys() override
1211
    {
618✔
1212
        return m_index_evaluator ? &(*m_index_evaluator) : nullptr;
618✔
1213
    }
618✔
1214

1215
    bool has_search_index() const override
1216
    {
643,194✔
1217
        return bool(m_index_evaluator);
643,194✔
1218
    }
643,194✔
1219

1220
    size_t find_first_local(size_t start, size_t end) override
1221
    {
655,452✔
1222
        REALM_ASSERT(this->m_table);
655,452✔
1223
        size_t s = realm::npos;
655,452✔
1224

327,729✔
1225
        if (start < end) {
655,452✔
1226
            if (m_index_evaluator) {
655,446✔
1227
                return m_index_evaluator->do_search_index(this->m_cluster, start, end);
642,216✔
1228
            }
642,216✔
1229

6,621✔
1230
            if (end - start == 1) {
13,230✔
1231
                if (this->m_leaf->get(start) == m_optional_value) {
138✔
1232
                    s = start;
72✔
1233
                }
72✔
1234
            }
138✔
1235
            else {
13,092✔
1236
                s = this->m_leaf->find_first(m_optional_value, start, end);
13,092✔
1237
            }
13,092✔
1238
        }
13,230✔
1239

327,729✔
1240
        return s;
334,344✔
1241
    }
655,452✔
1242

1243
    std::string describe(util::serializer::SerialisationState& state) const override
1244
    {
654✔
1245
        REALM_ASSERT(this->m_condition_column_key);
654✔
1246
        return state.describe_column(ParentNode::m_table, this->m_condition_column_key) + " " + Equal::description() +
654✔
1247
               " " +
654✔
1248
               (this->m_value_is_null ? util::serializer::print_value(realm::null())
327✔
1249
                                      : util::serializer::print_value(this->m_value));
654✔
1250
    }
654✔
1251

1252
    std::unique_ptr<ParentNode> clone() const override
1253
    {
1,285,698✔
1254
        return std::unique_ptr<ParentNode>(new FixedBytesNode(*this));
1,285,698✔
1255
    }
1,285,698✔
1256

1257
protected:
1258
    std::optional<ObjectType> m_optional_value;
1259
    std::optional<IndexEvaluator> m_index_evaluator;
1260
};
1261

1262

1263
template <typename T>
1264
using ObjectIdNode = FixedBytesNode<T, ObjectId, ArrayObjectIdNull>;
1265
template <typename T>
1266
using UUIDNode = FixedBytesNode<T, UUID, ArrayUUIDNull>;
1267

1268
class MixedNodeBase : public ParentNode {
1269
public:
1270
    using TConditionValue = Mixed;
1271
    static const bool special_null_node = false;
1272

1273
    MixedNodeBase(Mixed v, ColKey column)
1274
        : m_value(v)
1275
        , m_value_is_null(v.is_null())
1276
    {
1,398✔
1277
        REALM_ASSERT(column.get_type() == col_type_Mixed);
1,398✔
1278
        get_ownership();
1,398✔
1279
        m_condition_column_key = column;
1,398✔
1280
    }
1,398✔
1281

1282
    MixedNodeBase(null, ColKey column)
1283
        : MixedNodeBase(Mixed{}, column)
1284
    {
×
1285
        m_value_is_null = true;
×
1286
    }
×
1287

1288
    void cluster_changed() override
1289
    {
978✔
1290
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
978✔
1291
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
978✔
1292
    }
978✔
1293

1294
    void init(bool will_query_ranges) override
1295
    {
1,398✔
1296
        ParentNode::init(will_query_ranges);
1,398✔
1297

699✔
1298
        m_dD = 100.0;
1,398✔
1299
    }
1,398✔
1300

1301
    std::string describe(util::serializer::SerialisationState& state) const override
1302
    {
246✔
1303
        REALM_ASSERT(m_condition_column_key);
246✔
1304
        std::string value;
246✔
1305
        if (m_value.is_type(type_TypedLink)) {
246✔
1306
            value = util::serializer::print_value(m_value.get<ObjLink>(), state.group);
12✔
1307
        }
12✔
1308
        else {
234✔
1309
            value = util::serializer::print_value(m_value);
234✔
1310
        }
234✔
1311
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " + this->describe_condition() +
246✔
1312
               " " + value;
246✔
1313
    }
246✔
1314

1315
protected:
1316
    MixedNodeBase(const MixedNodeBase& from)
1317
        : ParentNode(from)
1318
        , m_value(from.m_value)
1319
        , m_value_is_null(from.m_value_is_null)
1320
    {
1,764✔
1321
        get_ownership();
1,764✔
1322
    }
1,764✔
1323

1324
    void get_ownership()
1325
    {
3,162✔
1326
        if (m_value.is_type(type_String, type_Binary)) {
3,162✔
1327
            auto bin = m_value.export_to_type<BinaryData>();
1,710✔
1328
            m_buffer = OwnedBinaryData(bin.data(), bin.size());
1,710✔
1329
            auto tmp = m_buffer.get();
1,710✔
1330
            if (m_value.is_type(type_String)) {
1,710✔
1331
                m_value = Mixed(StringData(tmp.data(), tmp.size()));
1,182✔
1332
            }
1,182✔
1333
            else {
528✔
1334
                m_value = Mixed(tmp);
528✔
1335
            }
528✔
1336
        }
1,710✔
1337
    }
3,162✔
1338

1339
    QueryValue m_value;
1340
    OwnedBinaryData m_buffer;
1341
    std::optional<ArrayMixed> m_leaf;
1342
    bool m_value_is_null = false;
1343
};
1344

1345
template <class TConditionFunction>
1346
class MixedNode : public MixedNodeBase {
1347
public:
1348
    using MixedNodeBase::MixedNodeBase;
1349

1350
    size_t find_first_local(size_t start, size_t end) override
1351
    {
21,300✔
1352
        TConditionFunction cond;
21,300✔
1353
        for (size_t i = start; i < end; i++) {
70,068✔
1354
            QueryValue val(m_leaf->get(i));
69,600✔
1355
            if constexpr (realm::is_any_v<TConditionFunction, BeginsWith, BeginsWithIns, EndsWith, EndsWithIns, Like,
69,600✔
1356
                                          LikeIns, NotEqualIns, Contains, ContainsIns>) {
34,800✔
1357
                // For some strange reason the parameters are swapped for string conditions
13,800✔
1358
                if (cond(m_value, val))
42,000✔
1359
                    return i;
7,704✔
1360
            }
42,000✔
1361
            else {
27,600✔
1362
                if (cond(val, m_value))
27,600✔
1363
                    return i;
13,128✔
1364
            }
27,600✔
1365
        }
69,600✔
1366
        return realm::npos;
10,884✔
1367
    }
21,300✔
1368

1369
    std::string describe_condition() const override
1370
    {
168✔
1371
        return TConditionFunction::description();
168✔
1372
    }
168✔
1373

1374
    std::unique_ptr<ParentNode> clone() const override
1375
    {
1,056✔
1376
        return std::unique_ptr<ParentNode>(new MixedNode(*this));
1,056✔
1377
    }
1,056✔
1378
};
1379

1380
template <>
1381
class MixedNode<Equal> : public MixedNodeBase {
1382
public:
1383
    MixedNode(Mixed v, ColKey column)
1384
        : MixedNodeBase(v, column)
1385
    {
606✔
1386
    }
606✔
1387
    MixedNode(const MixedNode<Equal>& other)
1388
        : MixedNodeBase(other)
1389
        , m_index_evaluator(other.m_index_evaluator)
1390
    {
576✔
1391
    }
576✔
1392
    void init(bool will_query_ranges) override;
1393

1394
    void cluster_changed() override
1395
    {
216✔
1396
        // If we use searchindex, we do not need further access to clusters
108✔
1397
        if (!has_search_index()) {
216✔
1398
            MixedNodeBase::cluster_changed();
216✔
1399
        }
216✔
1400
    }
216✔
1401

1402
    void table_changed() override
1403
    {
606✔
1404
        const bool has_index =
606✔
1405
            m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General;
606✔
1406
        m_index_evaluator = has_index ? std::make_optional(IndexEvaluator{}) : std::nullopt;
498✔
1407
    }
606✔
1408

1409
    bool has_search_index() const override
1410
    {
822✔
1411
        return bool(m_index_evaluator);
822✔
1412
    }
822✔
1413

1414
    size_t find_first_local(size_t start, size_t end) override;
1415

1416
    std::string describe_condition() const override
1417
    {
60✔
1418
        return Equal::description();
60✔
1419
    }
60✔
1420

1421
    std::unique_ptr<ParentNode> clone() const override
1422
    {
576✔
1423
        return std::unique_ptr<ParentNode>(new MixedNode<Equal>(*this));
576✔
1424
    }
576✔
1425

1426
protected:
1427
    std::optional<IndexEvaluator> m_index_evaluator;
1428

1429
    const IndexEvaluator* index_based_keys() override
1430
    {
390✔
1431
        return m_index_evaluator ? &(*m_index_evaluator) : nullptr;
390✔
1432
    }
390✔
1433
};
1434

1435
template <>
1436
class MixedNode<EqualIns> : public MixedNodeBase {
1437
public:
1438
    MixedNode(Mixed v, ColKey column)
1439
        : MixedNodeBase(v, column)
1440
    {
96✔
1441
    }
96✔
1442
    MixedNode(const MixedNode<EqualIns>& other)
1443
        : MixedNodeBase(other)
1444
        , m_index_evaluator(other.m_index_evaluator)
1445
    {
132✔
1446
    }
132✔
1447
    void init(bool will_query_ranges) override;
1448

1449
    size_t find_first_local(size_t start, size_t end) override;
1450

1451
    void cluster_changed() override
1452
    {
66✔
1453
        // If we use searchindex, we do not need further access to clusters
33✔
1454
        if (!has_search_index()) {
66✔
1455
            MixedNodeBase::cluster_changed();
66✔
1456
        }
66✔
1457
    }
66✔
1458

1459
    void table_changed() override
1460
    {
96✔
1461
        const bool has_index =
96✔
1462
            m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General;
96✔
1463
        m_index_evaluator = has_index ? std::make_optional(IndexEvaluator{}) : std::nullopt;
81✔
1464
    }
96✔
1465

1466
    bool has_search_index() const override
1467
    {
162✔
1468
        return bool(m_index_evaluator);
162✔
1469
    }
162✔
1470

1471
    std::string describe_condition() const override
1472
    {
18✔
1473
        return EqualIns::description();
18✔
1474
    }
18✔
1475

1476
    std::unique_ptr<ParentNode> clone() const override
1477
    {
132✔
1478
        return std::unique_ptr<ParentNode>(new MixedNode<EqualIns>(*this));
132✔
1479
    }
132✔
1480

1481
protected:
1482
    std::string m_ucase;
1483
    std::string m_lcase;
1484
    std::optional<IndexEvaluator> m_index_evaluator;
1485
    std::vector<ObjKey> m_index_matches;
1486

1487
    const IndexEvaluator* index_based_keys() override
1488
    {
30✔
1489
        return m_index_evaluator ? &(*m_index_evaluator) : nullptr;
30✔
1490
    }
30✔
1491
};
1492

1493
class StringNodeBase : public ParentNode {
1494
public:
1495
    using TConditionValue = StringData;
1496
    static const bool special_null_node = true;
1497

1498
    StringNodeBase(StringData v, ColKey column)
1499
        : m_value(v.is_null() ? util::none : util::make_optional(std::string(v)))
1500
    {
54,891✔
1501
        m_condition_column_key = column;
54,891✔
1502
        m_dT = 10.0;
54,891✔
1503
    }
54,891✔
1504

1505
    void table_changed() override
1506
    {
58,443✔
1507
        m_is_string_enum = m_table.unchecked_ptr()->is_enumerated(m_condition_column_key);
58,443✔
1508
    }
58,443✔
1509

1510
    void cluster_changed() override
1511
    {
125,565✔
1512
        m_leaf.emplace(m_table.unchecked_ptr()->get_alloc());
125,565✔
1513
        m_cluster->init_leaf(m_condition_column_key, &*m_leaf);
125,565✔
1514
    }
125,565✔
1515

1516
    void init(bool will_query_ranges) override
1517
    {
94,968✔
1518
        ParentNode::init(will_query_ranges);
94,968✔
1519

47,169✔
1520
        m_probes = 0;
94,968✔
1521
        m_matches = 0;
94,968✔
1522
        m_end_s = 0;
94,968✔
1523
        m_leaf_start = 0;
94,968✔
1524
        m_leaf_end = 0;
94,968✔
1525
    }
94,968✔
1526

1527
    virtual void clear_leaf_state()
1528
    {
16,506✔
1529
        m_leaf.reset();
16,506✔
1530
    }
16,506✔
1531

1532
    StringNodeBase(const StringNodeBase& from)
1533
        : ParentNode(from)
1534
        , m_value(from.m_value)
1535
        , m_is_string_enum(from.m_is_string_enum)
1536
    {
90,192✔
1537
    }
90,192✔
1538

1539
    std::string describe(util::serializer::SerialisationState& state) const override
1540
    {
3,372✔
1541
        REALM_ASSERT(m_condition_column_key);
3,372✔
1542
        StringData sd;
3,372✔
1543
        if (bool(StringNodeBase::m_value)) {
3,372✔
1544
            sd = StringData(*StringNodeBase::m_value);
3,270✔
1545
        }
3,270✔
1546
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " + describe_condition() + " " +
3,372✔
1547
               util::serializer::print_value(sd);
3,372✔
1548
    }
3,372✔
1549

1550
protected:
1551
    std::optional<std::string> m_value;
1552
    std::optional<ArrayString> m_leaf;
1553

1554
    bool m_is_string_enum = false;
1555

1556
    size_t m_end_s = 0;
1557
    size_t m_leaf_start = 0;
1558
    size_t m_leaf_end = 0;
1559

1560
    StringData get_string(size_t s)
1561
    {
407,094✔
1562
        return m_leaf->get(s);
407,094✔
1563
    }
407,094✔
1564
};
1565

1566
// Conditions for strings. Note that Equal is specialized later in this file!
1567
template <class TConditionFunction>
1568
class StringNode : public StringNodeBase {
1569
public:
1570
    StringNode(StringData v, ColKey column)
1571
        : StringNodeBase(v, column)
1572
    {
9,180✔
1573
        auto upper = case_map(v, true);
9,180✔
1574
        auto lower = case_map(v, false);
9,180✔
1575
        if (!upper || !lower) {
9,180✔
1576
            throw InvalidArgument(util::format("Malformed UTF-8: %1", v));
×
1577
        }
×
1578
        else {
9,180✔
1579
            m_ucase = std::move(*upper);
9,180✔
1580
            m_lcase = std::move(*lower);
9,180✔
1581
        }
9,180✔
1582
    }
9,180✔
1583

1584
    void init(bool will_query_ranges) override
1585
    {
13,326✔
1586
        StringNodeBase::init(will_query_ranges);
13,326✔
1587
        clear_leaf_state();
13,326✔
1588
    }
13,326✔
1589

1590
    size_t find_first_local(size_t start, size_t end) override
1591
    {
235,362✔
1592
        TConditionFunction cond;
235,362✔
1593

121,605✔
1594
        for (size_t s = start; s < end; ++s) {
328,956✔
1595
            StringData t = get_string(s);
316,656✔
1596

162,591✔
1597
            if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), t))
316,656✔
1598
                return s;
223,062✔
1599
        }
316,656✔
1600
        return not_found;
126,900✔
1601
    }
235,362✔
1602

1603
    std::string describe_condition() const override
1604
    {
126✔
1605
        return TConditionFunction::description();
126✔
1606
    }
126✔
1607

1608
    std::unique_ptr<ParentNode> clone() const override
1609
    {
8,256✔
1610
        return std::unique_ptr<ParentNode>(new StringNode<TConditionFunction>(*this));
8,256✔
1611
    }
8,256✔
1612

1613
    StringNode(const StringNode& from)
1614
        : StringNodeBase(from)
1615
        , m_ucase(from.m_ucase)
1616
        , m_lcase(from.m_lcase)
1617
    {
8,256✔
1618
    }
8,256✔
1619

1620
protected:
1621
    std::string m_ucase;
1622
    std::string m_lcase;
1623
};
1624

1625
// Specialization for Contains condition on Strings - we specialize because we can utilize Boyer-Moore
1626
template <>
1627
class StringNode<Contains> : public StringNodeBase {
1628
public:
1629
    StringNode(StringData v, ColKey column)
1630
        : StringNodeBase(v, column)
1631
        , m_charmap()
1632
    {
1,206✔
1633
        if (v.size() == 0)
1,206✔
1634
            return;
1,140✔
1635

33✔
1636
        // Build a dictionary of char-to-last distances in the search string
33✔
1637
        // (zero indicates that the char is not in needle)
33✔
1638
        size_t last_char_pos = v.size() - 1;
66✔
1639
        for (size_t i = 0; i < last_char_pos; ++i) {
3,090✔
1640
            // we never jump longer increments than 255 chars, even if needle is longer (to fit in one byte)
1,512✔
1641
            uint8_t jump = last_char_pos - i < 255 ? static_cast<uint8_t>(last_char_pos - i) : 255;
2,313✔
1642

1,512✔
1643
            unsigned char c = v[i];
3,024✔
1644
            m_charmap[c] = jump;
3,024✔
1645
        }
3,024✔
1646
        m_dT = 50.0;
66✔
1647
    }
66✔
1648

1649
    void init(bool will_query_ranges) override
1650
    {
1,746✔
1651
        StringNodeBase::init(will_query_ranges);
1,746✔
1652
        clear_leaf_state();
1,746✔
1653
    }
1,746✔
1654

1655

1656
    size_t find_first_local(size_t start, size_t end) override
1657
    {
33,174✔
1658
        Contains cond;
33,174✔
1659

16,587✔
1660
        for (size_t s = start; s < end; ++s) {
33,498✔
1661
            StringData t = get_string(s);
33,384✔
1662

16,692✔
1663
            if (cond(StringData(m_value), m_charmap, t))
33,384✔
1664
                return s;
33,060✔
1665
        }
33,384✔
1666
        return not_found;
16,644✔
1667
    }
33,174✔
1668

1669
    std::string describe_condition() const override
1670
    {
24✔
1671
        return Contains::description();
24✔
1672
    }
24✔
1673

1674

1675
    std::unique_ptr<ParentNode> clone() const override
1676
    {
1,140✔
1677
        return std::unique_ptr<ParentNode>(new StringNode<Contains>(*this));
1,140✔
1678
    }
1,140✔
1679

1680
    StringNode(const StringNode& from)
1681
        : StringNodeBase(from)
1682
        , m_charmap(from.m_charmap)
1683
    {
1,140✔
1684
    }
1,140✔
1685

1686
protected:
1687
    std::array<uint8_t, 256> m_charmap;
1688
};
1689

1690
// Specialization for ContainsIns condition on Strings - we specialize because we can utilize Boyer-Moore
1691
template <>
1692
class StringNode<ContainsIns> : public StringNodeBase {
1693
public:
1694
    StringNode(StringData v, ColKey column)
1695
        : StringNodeBase(v, column)
1696
        , m_charmap()
1697
    {
1,020✔
1698
        auto upper = case_map(v, true);
1,020✔
1699
        auto lower = case_map(v, false);
1,020✔
1700
        if (!upper || !lower) {
1,020✔
1701
            throw query_parser::InvalidQueryError(util::format("Malformed UTF-8: %1", v));
×
1702
        }
×
1703
        else {
1,020✔
1704
            m_ucase = std::move(*upper);
1,020✔
1705
            m_lcase = std::move(*lower);
1,020✔
1706
        }
1,020✔
1707

510✔
1708
        if (v.size() == 0)
1,020✔
1709
            return;
864✔
1710

78✔
1711
        // Build a dictionary of char-to-last distances in the search string
78✔
1712
        // (zero indicates that the char is not in needle)
78✔
1713
        size_t last_char_pos = m_ucase.size() - 1;
156✔
1714
        for (size_t i = 0; i < last_char_pos; ++i) {
3,474✔
1715
            // we never jump longer increments than 255 chars, even if needle is longer (to fit in one byte)
1,659✔
1716
            uint8_t jump = last_char_pos - i < 255 ? static_cast<uint8_t>(last_char_pos - i) : 255;
2,607✔
1717

1,659✔
1718
            unsigned char uc = m_ucase[i];
3,318✔
1719
            unsigned char lc = m_lcase[i];
3,318✔
1720
            m_charmap[uc] = jump;
3,318✔
1721
            m_charmap[lc] = jump;
3,318✔
1722
        }
3,318✔
1723
        m_dT = 75.0;
156✔
1724
    }
156✔
1725

1726
    void init(bool will_query_ranges) override
1727
    {
1,434✔
1728
        StringNodeBase::init(will_query_ranges);
1,434✔
1729
        clear_leaf_state();
1,434✔
1730
    }
1,434✔
1731

1732

1733
    size_t find_first_local(size_t start, size_t end) override
1734
    {
25,992✔
1735
        ContainsIns cond;
25,992✔
1736

12,996✔
1737
        for (size_t s = start; s < end; ++s) {
32,616✔
1738
            StringData t = get_string(s);
32,208✔
1739
            // The current behaviour is to return all results when querying for a null string.
16,104✔
1740
            // See comment above Query_NextGen_StringConditions on why every string including "" contains null.
16,104✔
1741
            if (!bool(m_value)) {
32,208✔
1742
                return s;
24,738✔
1743
            }
24,738✔
1744
            if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), m_charmap, t))
7,470✔
1745
                return s;
846✔
1746
        }
7,470✔
1747
        return not_found;
13,200✔
1748
    }
25,992✔
1749

1750
    std::string describe_condition() const override
1751
    {
66✔
1752
        return ContainsIns::description();
66✔
1753
    }
66✔
1754

1755
    std::unique_ptr<ParentNode> clone() const override
1756
    {
1,188✔
1757
        return std::unique_ptr<ParentNode>(new StringNode<ContainsIns>(*this));
1,188✔
1758
    }
1,188✔
1759

1760
    StringNode(const StringNode& from)
1761
        : StringNodeBase(from)
1762
        , m_charmap(from.m_charmap)
1763
        , m_ucase(from.m_ucase)
1764
        , m_lcase(from.m_lcase)
1765
    {
1,188✔
1766
    }
1,188✔
1767

1768
protected:
1769
    std::array<uint8_t, 256> m_charmap;
1770
    std::string m_ucase;
1771
    std::string m_lcase;
1772
};
1773

1774
class StringNodeEqualBase : public StringNodeBase {
1775
public:
1776
    StringNodeEqualBase(StringData v, ColKey column)
1777
        : StringNodeBase(v, column)
1778
    {
43,485✔
1779
    }
43,485✔
1780
    StringNodeEqualBase(const StringNodeEqualBase& from)
1781
        : StringNodeBase(from)
1782
        , m_index_evaluator(from.m_index_evaluator)
1783
    {
79,608✔
1784
    }
79,608✔
1785

1786
    void init(bool) override;
1787

1788
    void table_changed() override
1789
    {
44,289✔
1790
        StringNodeBase::table_changed();
44,289✔
1791
        const bool has_index =
44,289✔
1792
            m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General;
44,289✔
1793
        m_index_evaluator = has_index ? std::make_optional(IndexEvaluator{}) : std::nullopt;
42,561✔
1794
    }
44,289✔
1795

1796
    bool has_search_index() const override
1797
    {
155,460✔
1798
        return bool(m_index_evaluator);
155,460✔
1799
    }
155,460✔
1800

1801
    void cluster_changed() override
1802
    {
318,939✔
1803
        // If we use searchindex, we do not need further access to clusters
162,564✔
1804
        if (!m_index_evaluator) {
318,939✔
1805
            StringNodeBase::cluster_changed();
107,037✔
1806
        }
107,037✔
1807
    }
318,939✔
1808

1809
    size_t find_first_local(size_t start, size_t end) override;
1810

1811
    std::string describe_condition() const override
1812
    {
3,144✔
1813
        return Equal::description();
3,144✔
1814
    }
3,144✔
1815

1816
    const IndexEvaluator* index_based_keys() override
1817
    {
2,814✔
1818
        return m_index_evaluator ? &(*m_index_evaluator) : nullptr;
2,814✔
1819
    }
2,814✔
1820

1821
protected:
1822
    std::optional<IndexEvaluator> m_index_evaluator;
1823

1824
    inline BinaryData str_to_bin(const StringData& s) noexcept
1825
    {
×
1826
        return BinaryData(s.data(), s.size());
×
1827
    }
×
1828

1829
    virtual void _search_index_init() = 0;
1830
    virtual size_t _find_first_local(size_t start, size_t end) = 0;
1831
};
1832

1833
// Specialization for Equal condition on Strings - we specialize because we can utilize indexes (if they exist) for
1834
// Equal. This specialisation also supports combining other StringNode<Equal> conditions into itself in order to
1835
// optimise the non-indexed linear search that can be happen when many conditions are OR'd together in an "IN" query.
1836
// Future optimization: make specialization for greater, notequal, etc
1837
template <>
1838
class StringNode<Equal> : public StringNodeEqualBase {
1839
public:
1840
    StringNode(StringData v, ColKey column)
1841
        : StringNodeEqualBase(v, column)
1842
    {
42,201✔
1843
    }
42,201✔
1844

1845
    void _search_index_init() override;
1846

1847
    bool do_consume_condition(ParentNode& other) override;
1848

1849
    std::unique_ptr<ParentNode> clone() const override
1850
    {
78,246✔
1851
        return std::unique_ptr<ParentNode>(new StringNode<Equal>(*this));
78,246✔
1852
    }
78,246✔
1853

1854
    std::string describe(util::serializer::SerialisationState& state) const override;
1855

1856
    StringNode(const StringNode& from)
1857
        : StringNodeEqualBase(from)
1858
    {
78,246✔
1859
        for (auto& needle : from.m_needles) {
38,820✔
1860
            if (needle.is_null()) {
24✔
1861
                m_needles.emplace();
6✔
1862
            }
6✔
1863
            else {
18✔
1864
                m_needle_storage.push_back(std::make_unique<char[]>(needle.size()));
18✔
1865
                std::copy(needle.data(), needle.data() + needle.size(), m_needle_storage.back().get());
18✔
1866
                m_needles.insert(StringData(m_needle_storage.back().get(), needle.size()));
18✔
1867
            }
18✔
1868
        }
24✔
1869
    }
78,246✔
1870

1871
private:
1872
    size_t _find_first_local(size_t start, size_t end) override;
1873
    std::unordered_set<StringData> m_needles;
1874
    std::vector<std::unique_ptr<char[]>> m_needle_storage;
1875
};
1876

1877

1878
// Specialization for EqualIns condition on Strings - we specialize because we can utilize indexes (if they exist) for
1879
// EqualIns.
1880
template <>
1881
class StringNode<EqualIns> : public StringNodeEqualBase {
1882
public:
1883
    StringNode(StringData v, ColKey column)
1884
        : StringNodeEqualBase(v, column)
1885
    {
960✔
1886
        auto upper = case_map(v, true);
960✔
1887
        auto lower = case_map(v, false);
960✔
1888
        if (!upper || !lower) {
960✔
1889
            throw query_parser::InvalidQueryError(util::format("Malformed UTF-8: %1", v));
×
1890
        }
×
1891
        else {
960✔
1892
            m_ucase = std::move(*upper);
960✔
1893
            m_lcase = std::move(*lower);
960✔
1894
        }
960✔
1895
    }
960✔
1896

1897
    void _search_index_init() override;
1898

1899
    std::string describe_condition() const override
1900
    {
12✔
1901
        return EqualIns::description();
12✔
1902
    }
12✔
1903

1904
    std::unique_ptr<ParentNode> clone() const override
1905
    {
930✔
1906
        return std::unique_ptr<ParentNode>(new StringNode(*this));
930✔
1907
    }
930✔
1908

1909
    StringNode(const StringNode& from)
1910
        : StringNodeEqualBase(from)
1911
        , m_ucase(from.m_ucase)
1912
        , m_lcase(from.m_lcase)
1913
    {
930✔
1914
    }
930✔
1915

1916
private:
1917
    std::vector<ObjKey> m_index_matches;
1918
    std::string m_ucase;
1919
    std::string m_lcase;
1920
    std::vector<ObjKey> storage;
1921
    size_t _find_first_local(size_t start, size_t end) override;
1922
};
1923

1924

1925
class LinkMap;
1926
class StringNodeFulltext : public StringNodeEqualBase {
1927
public:
1928
    StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm = {});
1929

1930
    void table_changed() override;
1931

1932
    void _search_index_init() override;
1933

1934
    bool has_search_index() const override
1935
    {
660✔
1936
        return true; // it's a required precondition for fulltext queries
660✔
1937
    }
660✔
1938

1939
    std::unique_ptr<ParentNode> clone() const override
1940
    {
432✔
1941
        return std::unique_ptr<ParentNode>(new StringNodeFulltext(*this));
432✔
1942
    }
432✔
1943

1944
    std::string describe_condition() const override
UNCOV
1945
    {
×
UNCOV
1946
        return "FULLTEXT";
×
UNCOV
1947
    }
×
1948

1949
private:
1950
    std::vector<ObjKey> m_index_matches;
1951
    std::unique_ptr<LinkMap> m_link_map;
1952
    StringNodeFulltext(const StringNodeFulltext&);
1953

1954
    size_t _find_first_local(size_t, size_t) override
1955
    {
×
1956
        REALM_UNREACHABLE();
×
1957
    }
×
1958
};
1959

1960
// OR node contains at least two node pointers: Two or more conditions to OR
1961
// together in m_conditions, and the next AND condition (if any) in m_child.
1962
//
1963
// For 'second.equal(23).begin_group().first.equal(111).Or().first.equal(222).end_group().third().equal(555)', this
1964
// will first set m_conditions[0] = left-hand-side through constructor, and then later, when .first.equal(222) is
1965
// invoked, invocation will set m_conditions[1] = right-hand-side through Query& Query::Or() (see query.cpp).
1966
// In there, m_child is also set to next AND condition (if any exists) following the OR.
1967
class OrNode : public ParentNode {
1968
public:
1969
    OrNode(std::unique_ptr<ParentNode> condition)
1970
    {
19,404✔
1971
        m_dT = 50.0;
19,404✔
1972
        if (condition)
19,404✔
1973
            m_conditions.emplace_back(std::move(condition));
18,546✔
1974
    }
19,404✔
1975

1976
    OrNode(const OrNode& other)
1977
        : ParentNode(other)
1978
    {
20,418✔
1979
        for (const auto& condition : other.m_conditions) {
684,870✔
1980
            m_conditions.emplace_back(condition->clone());
684,870✔
1981
        }
684,870✔
1982
    }
20,418✔
1983

1984
    void table_changed() override
1985
    {
19,452✔
1986
        for (auto& condition : m_conditions) {
19,047✔
1987
            condition->set_table(m_table);
18,642✔
1988
        }
18,642✔
1989
    }
19,452✔
1990

1991
    void cluster_changed() override
1992
    {
46,872✔
1993
        for (auto& condition : m_conditions) {
711,690✔
1994
            condition->set_cluster(m_cluster);
711,690✔
1995
        }
711,690✔
1996

24,252✔
1997
        m_start.clear();
46,872✔
1998
        m_start.resize(m_conditions.size(), 0);
46,872✔
1999

24,252✔
2000
        m_last.clear();
46,872✔
2001
        m_last.resize(m_conditions.size(), 0);
46,872✔
2002

24,252✔
2003
        m_was_match.clear();
46,872✔
2004
        m_was_match.resize(m_conditions.size(), false);
46,872✔
2005
    }
46,872✔
2006

2007
    std::string describe(util::serializer::SerialisationState& state) const override
2008
    {
468✔
2009
        std::string s;
468✔
2010
        for (size_t i = 0; i < m_conditions.size(); ++i) {
2,058✔
2011
            if (m_conditions[i]) {
1,590✔
2012
                s += m_conditions[i]->describe_expression(state);
1,590✔
2013
                if (i != m_conditions.size() - 1) {
1,590✔
2014
                    s += " or ";
1,122✔
2015
                }
1,122✔
2016
            }
1,590✔
2017
        }
1,590✔
2018
        if (m_conditions.size() > 1) {
468✔
2019
            s = "(" + s + ")";
432✔
2020
        }
432✔
2021
        return s;
468✔
2022
    }
468✔
2023

2024
    void collect_dependencies(std::vector<TableKey>& versions) const override
2025
    {
18,186✔
2026
        for (const auto& cond : m_conditions) {
18,762✔
2027
            cond->collect_dependencies(versions);
18,762✔
2028
        }
18,762✔
2029
    }
18,186✔
2030

2031
    void init(bool will_query_ranges) override
2032
    {
19,464✔
2033
        ParentNode::init(will_query_ranges);
19,464✔
2034
        combine_conditions(!will_query_ranges);
19,464✔
2035

9,999✔
2036
        m_start.clear();
19,464✔
2037
        m_start.resize(m_conditions.size(), 0);
19,464✔
2038

9,999✔
2039
        m_last.clear();
19,464✔
2040
        m_last.resize(m_conditions.size(), 0);
19,464✔
2041

9,999✔
2042
        m_was_match.clear();
19,464✔
2043
        m_was_match.resize(m_conditions.size(), false);
19,464✔
2044

9,999✔
2045
        std::vector<ParentNode*> v;
19,464✔
2046
        for (auto& condition : m_conditions) {
664,506✔
2047
            condition->init(will_query_ranges);
664,506✔
2048
            v.clear();
664,506✔
2049
            condition->gather_children(v);
664,506✔
2050
        }
664,506✔
2051
    }
19,464✔
2052

2053
    size_t find_first_local(size_t start, size_t end) override
2054
    {
2,611,425✔
2055
        if (start >= end)
2,611,425✔
2056
            return not_found;
825✔
2057

1,146,936✔
2058
        size_t index = not_found;
2,610,600✔
2059

1,146,936✔
2060
        for (size_t c = 0; c < m_conditions.size(); ++c) {
7,609,092✔
2061
            // out of order search; have to discard cached results
2,252,895✔
2062
            if (start < m_start[c]) {
4,998,492✔
2063
                m_last[c] = 0;
×
2064
                m_was_match[c] = false;
×
2065
            }
×
2066
            // already searched this range and didn't match
2,252,895✔
2067
            else if (m_last[c] >= end)
4,998,492✔
2068
                continue;
331,722✔
2069
            // already search this range and *did* match
2,139,711✔
2070
            else if (m_was_match[c] && m_last[c] >= start) {
4,666,770✔
2071
                if (index > m_last[c])
1,134,177✔
2072
                    index = m_last[c];
746,832✔
2073
                continue;
1,134,177✔
2074
            }
1,134,177✔
2075

1,607,502✔
2076
            m_start[c] = start;
3,532,593✔
2077
            size_t fmax = std::max(m_last[c], start);
3,532,593✔
2078
            size_t f = m_conditions[c]->find_first(fmax, end);
3,532,593✔
2079
            m_was_match[c] = f != not_found;
3,532,593✔
2080
            m_last[c] = f == not_found ? end : f;
3,020,883✔
2081
            if (f != not_found && index > m_last[c])
3,532,593✔
2082
                index = m_last[c];
2,180,061✔
2083
        }
3,532,593✔
2084

1,146,936✔
2085
        return index;
2,610,600✔
2086
    }
2,610,600✔
2087

2088
    std::string validate() override
2089
    {
18✔
2090
        if (m_conditions.size() == 0)
18✔
2091
            return "Missing both arguments of OR";
6✔
2092
        if (m_conditions.size() == 1)
12✔
2093
            return "Missing argument of OR";
12✔
2094
        std::string s;
×
2095
        if (m_child != 0)
×
2096
            s = m_child->validate();
×
2097
        if (s != "")
×
2098
            return s;
×
2099
        for (size_t i = 0; i < m_conditions.size(); ++i) {
×
2100
            s = m_conditions[i]->validate();
×
2101
            if (s != "")
×
2102
                return s;
×
2103
        }
×
2104
        return "";
×
2105
    }
×
2106

2107
    std::unique_ptr<ParentNode> clone() const override
2108
    {
20,418✔
2109
        return std::unique_ptr<ParentNode>(new OrNode(*this));
20,418✔
2110
    }
20,418✔
2111

2112
    std::vector<std::unique_ptr<ParentNode>> m_conditions;
2113

2114
private:
2115
    void combine_conditions(bool ignore_indexes)
2116
    {
19,464✔
2117
        std::sort(m_conditions.begin(), m_conditions.end(), [](auto& a, auto& b) {
4,935,984✔
2118
            return a->m_condition_column_key < b->m_condition_column_key;
4,935,984✔
2119
        });
4,935,984✔
2120

9,999✔
2121
        auto prev = m_conditions.begin()->get();
19,464✔
2122
        auto cond = [&](auto& node) {
663,480✔
2123
            if (prev->consume_condition(*node, ignore_indexes))
663,480✔
2124
                return true;
18,450✔
2125
            prev = &*node;
645,030✔
2126
            return false;
645,030✔
2127
        };
645,030✔
2128
        m_conditions.erase(std::remove_if(m_conditions.begin() + 1, m_conditions.end(), cond), m_conditions.end());
19,464✔
2129
    }
19,464✔
2130

2131
    // start index of the last find for each cond
2132
    std::vector<size_t> m_start;
2133
    // last looked at index of the lasft find for each cond
2134
    // is a matching index if m_was_match is true
2135
    std::vector<size_t> m_last;
2136
    std::vector<bool> m_was_match;
2137
};
2138

2139

2140
class NotNode : public ParentNode {
2141
public:
2142
    NotNode(std::unique_ptr<ParentNode> condition)
2143
        : m_condition(std::move(condition))
2144
    {
1,722✔
2145
        m_dT = 50.0;
1,722✔
2146
        if (!m_condition) {
1,722✔
2147
            throw query_parser::InvalidQueryError("Missing argument to Not");
×
2148
        }
×
2149
    }
1,722✔
2150

2151
    void table_changed() override
2152
    {
1,764✔
2153
        m_condition->set_table(m_table);
1,764✔
2154
    }
1,764✔
2155

2156
    void cluster_changed() override
2157
    {
2,250✔
2158
        m_condition->set_cluster(m_cluster);
2,250✔
2159
        // Heuristics bookkeeping:
1,125✔
2160
        m_known_range_start = 0;
2,250✔
2161
        m_known_range_end = 0;
2,250✔
2162
        m_first_in_known_range = not_found;
2,250✔
2163
    }
2,250✔
2164

2165
    void init(bool will_query_ranges) override
2166
    {
1,800✔
2167
        ParentNode::init(will_query_ranges);
1,800✔
2168
        std::vector<ParentNode*> v;
1,800✔
2169

900✔
2170
        m_condition->init(false);
1,800✔
2171
        v.clear();
1,800✔
2172
        m_condition->gather_children(v);
1,800✔
2173
    }
1,800✔
2174

2175
    size_t find_first_local(size_t start, size_t end) override;
2176

2177
    std::string describe(util::serializer::SerialisationState& state) const override
2178
    {
684✔
2179
        if (m_condition) {
684✔
2180
            return "!(" + m_condition->describe_expression(state) + ")";
684✔
2181
        }
684✔
2182
        return "!()";
×
2183
    }
×
2184

2185
    void collect_dependencies(std::vector<TableKey>& versions) const override
2186
    {
48✔
2187
        if (m_condition) {
48✔
2188
            m_condition->collect_dependencies(versions);
48✔
2189
        }
48✔
2190
    }
48✔
2191

2192
    std::unique_ptr<ParentNode> clone() const override
2193
    {
3,120✔
2194
        return std::unique_ptr<ParentNode>(new NotNode(*this));
3,120✔
2195
    }
3,120✔
2196

2197
    NotNode(const NotNode& from)
2198
        : ParentNode(from)
2199
        , m_condition(from.m_condition ? from.m_condition->clone() : nullptr)
2200
        , m_known_range_start(from.m_known_range_start)
2201
        , m_known_range_end(from.m_known_range_end)
2202
        , m_first_in_known_range(from.m_first_in_known_range)
2203
    {
3,120✔
2204
    }
3,120✔
2205

2206
    std::unique_ptr<ParentNode> m_condition;
2207

2208
private:
2209
    // FIXME This heuristic might as well be reused for all condition nodes.
2210
    size_t m_known_range_start;
2211
    size_t m_known_range_end;
2212
    size_t m_first_in_known_range;
2213

2214
    bool evaluate_at(size_t rowndx);
2215
    void update_known(size_t start, size_t end, size_t first);
2216
    size_t find_first_loop(size_t start, size_t end);
2217
    size_t find_first_covers_known(size_t start, size_t end);
2218
    size_t find_first_covered_by_known(size_t start, size_t end);
2219
    size_t find_first_overlap_lower(size_t start, size_t end);
2220
    size_t find_first_overlap_upper(size_t start, size_t end);
2221
    size_t find_first_no_overlap(size_t start, size_t end);
2222
};
2223

2224
// Compare two columns with eachother row-by-row
2225
class TwoColumnsNodeBase : public ParentNode {
2226
public:
2227
    TwoColumnsNodeBase(ColKey column1, ColKey column2)
2228
    {
23,568✔
2229
        m_dT = 100.0;
23,568✔
2230
        m_condition_column_key1 = column1;
23,568✔
2231
        m_condition_column_key2 = column2;
23,568✔
2232
        if (m_condition_column_key1.is_collection() || m_condition_column_key2.is_collection()) {
23,568✔
2233
            throw Exception(ErrorCodes::InvalidQuery,
×
2234
                            util::format("queries comparing two properties are not yet supported for "
×
2235
                                         "collections (list/set/dictionary) (%1 and %2)",
×
2236
                                         ParentNode::m_table->get_column_name(m_condition_column_key1),
×
2237
                                         ParentNode::m_table->get_column_name(m_condition_column_key2)));
×
2238
        }
×
2239
    }
23,568✔
2240

2241
    void table_changed() override
2242
    {
26,808✔
2243
        if (m_table) {
26,808✔
2244
            ParentNode::m_table->check_column(m_condition_column_key1);
26,808✔
2245
            ParentNode::m_table->check_column(m_condition_column_key2);
26,808✔
2246
        }
26,808✔
2247
    }
26,808✔
2248

2249
    static std::unique_ptr<ArrayPayload> update_cached_leaf_pointers_for_column(Allocator& alloc,
2250
                                                                                const ColKey& col_key);
2251
    void cluster_changed() override
2252
    {
30,870✔
2253
        if (!m_leaf1) {
30,870✔
2254
            m_leaf1 =
29,124✔
2255
                update_cached_leaf_pointers_for_column(m_table.unchecked_ptr()->get_alloc(), m_condition_column_key1);
29,124✔
2256
        }
29,124✔
2257
        if (!m_leaf2) {
30,870✔
2258
            m_leaf2 =
29,124✔
2259
                update_cached_leaf_pointers_for_column(m_table.unchecked_ptr()->get_alloc(), m_condition_column_key2);
29,124✔
2260
        }
29,124✔
2261
        m_cluster->init_leaf(m_condition_column_key1, m_leaf1.get());
30,870✔
2262
        m_cluster->init_leaf(m_condition_column_key2, m_leaf2.get());
30,870✔
2263
    }
30,870✔
2264

2265
    std::string describe(util::serializer::SerialisationState& state) const override
2266
    {
×
2267
        REALM_ASSERT(m_condition_column_key1 && m_condition_column_key2);
×
2268
        return state.describe_column(ParentNode::m_table, m_condition_column_key1) + " " + describe_condition() +
×
2269
               " " + state.describe_column(ParentNode::m_table, m_condition_column_key2);
×
2270
    }
×
2271

2272
    TwoColumnsNodeBase(const TwoColumnsNodeBase& from)
2273
        : ParentNode(from)
2274
        , m_condition_column_key1(from.m_condition_column_key1)
2275
        , m_condition_column_key2(from.m_condition_column_key2)
2276
    {
21,828✔
2277
    }
21,828✔
2278

2279
protected:
2280
    ColKey m_condition_column_key1;
2281
    ColKey m_condition_column_key2;
2282
    std::unique_ptr<ArrayPayload> m_leaf1;
2283
    std::unique_ptr<ArrayPayload> m_leaf2;
2284
};
2285

2286

2287
template <class TConditionFunction>
2288
class TwoColumnsNode : public TwoColumnsNodeBase {
2289
public:
2290
    using TwoColumnsNodeBase::TwoColumnsNodeBase;
2291
    size_t find_first_local(size_t start, size_t end) override
2292
    {
285,405✔
2293
        size_t s = start;
285,405✔
2294
        while (s < end) {
756,618✔
2295
            QueryValue v1(m_leaf1->get_any(s));
717,915✔
2296
            QueryValue v2(m_leaf2->get_any(s));
717,915✔
2297
            if (TConditionFunction()(v1, v2))
717,915✔
2298
                return s;
246,702✔
2299
            else
471,213✔
2300
                s++;
471,213✔
2301
        }
717,915✔
2302
        return not_found;
160,902✔
2303
    }
285,405✔
2304

2305
    std::string describe_condition() const override
2306
    {
×
2307
        return TConditionFunction::description();
×
2308
    }
×
2309

2310
    std::unique_ptr<ParentNode> clone() const override
2311
    {
21,828✔
2312
        return std::unique_ptr<ParentNode>(new TwoColumnsNode<TConditionFunction>(*this));
21,828✔
2313
    }
21,828✔
2314
};
2315

2316

2317
// For Next-Generation expressions like col1 / col2 + 123 > col4 * 100.
2318
class ExpressionNode : public ParentNode {
2319
public:
2320
    ExpressionNode(std::unique_ptr<Expression>);
2321

2322
    void init(bool) override;
2323
    size_t find_first_local(size_t start, size_t end) override;
2324

2325
    void table_changed() override;
2326
    void cluster_changed() override;
2327
    void collect_dependencies(std::vector<TableKey>&) const override;
2328

2329
    std::string describe(util::serializer::SerialisationState& state) const override;
2330

2331
    std::unique_ptr<ParentNode> clone() const override;
2332

2333
private:
2334
    ExpressionNode(const ExpressionNode& from);
2335

2336
    std::unique_ptr<Expression> m_expression;
2337
};
2338

2339

2340
class LinksToNodeBase : public ParentNode {
2341
public:
2342
    LinksToNodeBase(ColKey origin_column_key, ObjKey target_key)
2343
        : LinksToNodeBase(origin_column_key, std::vector<ObjKey>{target_key})
2344
    {
13,620✔
2345
    }
13,620✔
2346

2347
    LinksToNodeBase(ColKey origin_column_key, const std::vector<ObjKey>& target_keys)
2348
        : m_target_keys(target_keys)
2349
    {
14,052✔
2350
        m_dT = 50.0;
14,052✔
2351
        m_condition_column_key = origin_column_key;
14,052✔
2352
        auto column_type = origin_column_key.get_type();
14,052✔
2353
        REALM_ASSERT(column_type == col_type_Link || column_type == col_type_LinkList);
14,052✔
2354
        REALM_ASSERT(!m_target_keys.empty());
14,052✔
2355
    }
14,052✔
2356

2357
    void cluster_changed() override
2358
    {
50,892✔
2359
        if (m_condition_column_key.is_collection()) {
50,892✔
2360
            m_linklist.emplace(m_table.unchecked_ptr()->get_alloc());
24,912✔
2361
            m_leaf = &*m_linklist;
24,912✔
2362
        }
24,912✔
2363
        else {
25,980✔
2364
            m_list.emplace(m_table.unchecked_ptr()->get_alloc());
25,980✔
2365
            m_leaf = &*m_list;
25,980✔
2366
        }
25,980✔
2367
        m_cluster->init_leaf(this->m_condition_column_key, m_leaf);
50,892✔
2368
    }
50,892✔
2369

2370
    std::string describe(util::serializer::SerialisationState& state) const override
2371
    {
228✔
2372
        REALM_ASSERT(m_condition_column_key);
228✔
2373
        std::string links = m_target_keys.size() > 1 ? "{" : "";
222✔
2374
        Group* g = m_table->get_parent_group();
228✔
2375
        auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
228✔
2376
        int cnt = 0;
228✔
2377
        for (auto key : m_target_keys) {
240✔
2378
            if (cnt++) {
240✔
2379
                links += ",";
12✔
2380
            }
12✔
2381
            links += util::serializer::print_value(ObjLink(target_table_key, key), g);
240✔
2382
        }
240✔
2383
        if (m_target_keys.size() > 1) {
228✔
2384
            links += "}";
12✔
2385
        }
12✔
2386
        return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " + describe_condition() + " " +
228✔
2387
               links;
228✔
2388
    }
228✔
2389

2390
protected:
2391
    std::vector<ObjKey> m_target_keys;
2392
    std::optional<ArrayKey> m_list;
2393
    std::optional<ArrayList> m_linklist;
2394
    ArrayPayload* m_leaf = nullptr;
2395

2396
    LinksToNodeBase(const LinksToNodeBase& source)
2397
        : ParentNode(source)
2398
        , m_target_keys(source.m_target_keys)
2399
    {
15,300✔
2400
    }
15,300✔
2401

2402
    ref_type get_ref(size_t i)
2403
    {
6,025,608✔
2404
        if (m_list)
6,025,608✔
2405
            return m_list->get_as_ref(i);
×
2406
        return m_linklist->get(i);
6,025,608✔
2407
    }
6,025,608✔
2408
};
2409

2410
template <class TConditionFunction>
2411
class LinksToNode : public LinksToNodeBase {
2412
public:
2413
    using LinksToNodeBase::LinksToNodeBase;
2414

2415
    std::string describe_condition() const override
2416
    {
228✔
2417
        return TConditionFunction::description();
228✔
2418
    }
228✔
2419

2420
    std::unique_ptr<ParentNode> clone() const override
2421
    {
15,300✔
2422
        return std::unique_ptr<ParentNode>(new LinksToNode<TConditionFunction>(*this));
15,300✔
2423
    }
15,300✔
2424

2425
    size_t find_first_local(size_t start, size_t end) override;
2426
};
2427

2428
} // namespace realm
2429

2430
#endif // REALM_QUERY_ENGINE_HPP
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