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

realm / realm-core / finn.schiermer-andersen_89

04 Jun 2024 02:04PM UTC coverage: 90.651% (-0.03%) from 90.685%
finn.schiermer-andersen_89

Pull #7654

Evergreen

finnschiermer
optimized string cache gc
Pull Request #7654: Fsa/string interning

102644 of 180648 branches covered (56.82%)

1005 of 1125 new or added lines in 15 files covered. (89.33%)

154 existing lines in 21 files now uncovered.

217953 of 240431 relevant lines covered (90.65%)

7671710.15 hits per line

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

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

19
#include "testsettings.hpp"
20
#ifdef TEST_QUERY
21

22
#include <cstdlib> // itoa()
23
#include <initializer_list>
24
#include <limits>
25
#include <vector>
26
#include <chrono>
27

28
#include <realm.hpp>
29
#include <realm/column_integer.hpp>
30
#include <realm/array_bool.hpp>
31
#include <realm/query_expression.hpp>
32
#include <realm/index_string.hpp>
33
#include <realm/query_expression.hpp>
34
#include "test.hpp"
35
#include "test_table_helper.hpp"
36
#include "test_types_helper.hpp"
37

38
using namespace realm;
39
using namespace realm::util;
40
using namespace realm::test_util;
41

42

43
// Test independence and thread-safety
44
// -----------------------------------
45
//
46
// All tests must be thread safe and independent of each other. This
47
// is required because it allows for both shuffling of the execution
48
// order and for parallelized testing.
49
//
50
// In particular, avoid using std::rand() since it is not guaranteed
51
// to be thread safe. Instead use the API offered in
52
// `test/util/random.hpp`.
53
//
54
// All files created in tests must use the TEST_PATH macro (or one of
55
// its friends) to obtain a suitable file system path. See
56
// `test/util/test_path.hpp`.
57
//
58
//
59
// Debugging and the ONLY() macro
60
// ------------------------------
61
//
62
// A simple way of disabling all tests except one called `Foo`, is to
63
// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the
64
// test suite. Note that you can also use filtering by setting the
65
// environment varible `UNITTEST_FILTER`. See `README.md` for more on
66
// this.
67
//
68
// Another way to debug a particular test, is to copy that test into
69
// `experiments/testcase.cpp` and then run `sh build.sh
70
// check-testcase` (or one of its friends) from the command line.
71

72

73
TEST(Query_BigString)
74
{
2✔
75
    Table ttt;
2✔
76
    auto col_int = ttt.add_column(type_Int, "1");
2✔
77
    auto col_str = ttt.add_column(type_String, "2");
2✔
78

79
    ttt.create_object().set_all(1, "a");
2✔
80
    ObjKey res1 = ttt.where().equal(col_str, "a").find();
2✔
81
    CHECK_EQUAL(ttt.get_object(res1).get<Int>(col_int), 1);
2✔
82

83
    const char* medium_string = "40 chars  40 chars  40 chars  40 chars  ";
2✔
84
    ttt.create_object().set_all(2, medium_string);
2✔
85
    ObjKey res2 = ttt.where().equal(col_str, medium_string).find();
2✔
86
    CHECK_EQUAL(ttt.get_object(res2).get<Int>(col_int), 2);
2✔
87

88
    const char* long_string = "70 chars  70 chars  70 chars  70 chars  70 chars  70 chars  70 chars  ";
2✔
89
    ttt.create_object().set_all(3, long_string);
2✔
90
    ObjKey res3 = ttt.where().equal(col_str, long_string).find();
2✔
91
    CHECK_EQUAL(ttt.get_object(res3).get<Int>(col_int), 3);
2✔
92
}
2✔
93

94

95
TEST(Query_Limit)
96
{
2✔
97
    Table ttt;
2✔
98
    auto col_id = ttt.add_column(type_Int, "id1");
2✔
99
    auto col_int = ttt.add_column(type_Int, "id2");
2✔
100
    ttt.add_column(type_String, "str");
2✔
101

102
    ttt.create_object().set_all(0, 1, "a");
2✔
103
    ttt.create_object().set_all(1, 2, "x"); //
2✔
104
    ttt.create_object().set_all(2, 3, "a");
2✔
105
    ttt.create_object().set_all(3, 1, "x");
2✔
106
    ttt.create_object().set_all(4, 2, "a"); //
2✔
107
    ttt.create_object().set_all(5, 3, "x");
2✔
108
    ttt.create_object().set_all(6, 1, "a");
2✔
109
    ttt.create_object().set_all(7, 2, "x"); //
2✔
110
    ttt.create_object().set_all(8, 3, "a");
2✔
111
    ttt.create_object().set_all(9, 1, "x");
2✔
112
    ttt.create_object().set_all(10, 2, "a"); //
2✔
113
    ttt.create_object().set_all(11, 3, "x");
2✔
114
    ttt.create_object().set_all(12, 1, "a");
2✔
115
    ttt.create_object().set_all(13, 2, "x"); //
2✔
116
    ttt.create_object().set_all(14, 3, "a");
2✔
117

118
    auto q = ttt.where();
2✔
119
    auto tv = q.find_all();
2✔
120
    CHECK_EQUAL(tv.size(), 15);
2✔
121
    tv = q.find_all(0);
2✔
122
    CHECK_EQUAL(tv.size(), 0);
2✔
123
    tv = q.find_all(1);
2✔
124
    CHECK_EQUAL(tv.size(), 1);
2✔
125

126
    q = ttt.query("id2 == 3 && str == 'a'");
2✔
127
    tv = q.find_all();
2✔
128
    CHECK_EQUAL(tv.size(), 3);
2✔
129
    tv = q.find_all(2);
2✔
130
    CHECK_EQUAL(tv.size(), 2);
2✔
131
    ttt.add_search_index(col_int);
2✔
132
    tv = q.find_all(2);
2✔
133
    CHECK_EQUAL(tv.size(), 2);
2✔
134

135
    Query q1 = ttt.where().equal(col_int, 2);
2✔
136

137
    TableView tv1 = q1.find_all(2);
2✔
138
    CHECK_EQUAL(2, tv1.size());
2✔
139
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
140
    CHECK_EQUAL(4, tv1[1].get<Int>(col_id));
2✔
141

142
    Query q2 = ttt.where();
2✔
143
    TableView tv4 = q2.find_all(3);
2✔
144
    CHECK_EQUAL(3, tv4.size());
2✔
145
}
2✔
146

147

148
TEST(Query_FindAll1)
149
{
2✔
150
    Table ttt;
2✔
151
    auto col_id = ttt.add_column(type_Int, "id");
2✔
152
    auto col_int = ttt.add_column(type_Int, "1");
2✔
153
    auto col_str = ttt.add_column(type_String, "2");
2✔
154

155
    ttt.create_object().set_all(0, 1, "a");
2✔
156
    ttt.create_object().set_all(1, 2, "a");
2✔
157
    ttt.create_object().set_all(2, 3, "X");
2✔
158
    ttt.create_object().set_all(3, 4, "a");
2✔
159
    ttt.create_object().set_all(4, 5, "a");
2✔
160
    ttt.create_object().set_all(5, 6, "X");
2✔
161
    ttt.create_object().set_all(6, 7, "X");
2✔
162

163
    Query q1 = ttt.where().equal(col_str, "a").greater(col_int, 2).not_equal(col_int, 4);
2✔
164
    TableView tv1 = q1.find_all();
2✔
165
    CHECK_EQUAL(4, tv1[0].get<Int>(col_id));
2✔
166

167
    Query q2 = ttt.where().equal(col_str, "X").greater(col_int, 4);
2✔
168
    TableView tv2 = q2.find_all();
2✔
169
    CHECK_EQUAL(tv2.size(), 2);
2✔
170
    CHECK_EQUAL(5, tv2[0].get<Int>(col_id));
2✔
171
    CHECK_EQUAL(6, tv2[1].get<Int>(col_id));
2✔
172
}
2✔
173

174
TEST(Query_FindAll2)
175
{
2✔
176
    Table ttt;
2✔
177
    auto col_id = ttt.add_column(type_Int, "id");
2✔
178
    auto col_int = ttt.add_column(type_Int, "1");
2✔
179
    auto col_str = ttt.add_column(type_String, "2");
2✔
180

181
    ttt.create_object().set_all(0, 1, "a");
2✔
182
    ttt.create_object().set_all(1, 2, "a");
2✔
183
    ttt.create_object().set_all(2, 3, "X");
2✔
184
    ttt.create_object().set_all(3, 4, "a");
2✔
185
    ttt.create_object().set_all(4, 5, "a");
2✔
186
    ttt.create_object().set_all(5, 11, "X");
2✔
187
    ttt.create_object().set_all(6, 0, "X");
2✔
188

189
    Query q2 = ttt.where().not_equal(col_str, "a").less(col_int, 3);
2✔
190
    TableView tv2 = q2.find_all();
2✔
191
    CHECK_EQUAL(tv2.size(), 1);
2✔
192
    CHECK_EQUAL(6, tv2[0].get<Int>(col_id));
2✔
193
}
2✔
194

195
TEST(Query_FindAllBetween)
196
{
2✔
197
    Table ttt;
2✔
198
    auto col_id = ttt.add_column(type_Int, "id");
2✔
199
    auto col_int = ttt.add_column(type_Int, "1");
2✔
200
    ttt.add_column(type_String, "2");
2✔
201

202
    ttt.create_object().set_all(0, 1, "a");
2✔
203
    ttt.create_object().set_all(1, 2, "a");
2✔
204
    ttt.create_object().set_all(2, 3, "X");
2✔
205
    ttt.create_object().set_all(3, 4, "a");
2✔
206
    ttt.create_object().set_all(4, 5, "a");
2✔
207
    ttt.create_object().set_all(5, 11, "X");
2✔
208
    ttt.create_object().set_all(6, 3, "X");
2✔
209

210
    Query q2 = ttt.where().between(col_int, 3, 5);
2✔
211
    TableView tv2 = q2.find_all();
2✔
212
    CHECK_EQUAL(tv2.size(), 4);
2✔
213
    CHECK_EQUAL(2, tv2[0].get<Int>(col_id));
2✔
214
    CHECK_EQUAL(3, tv2[1].get<Int>(col_id));
2✔
215
    CHECK_EQUAL(4, tv2[2].get<Int>(col_id));
2✔
216
    CHECK_EQUAL(6, tv2[3].get<Int>(col_id));
2✔
217
}
2✔
218

219

220
TEST(Query_FindAllOr)
221
{
2✔
222
    Table ttt;
2✔
223
    auto col_id = ttt.add_column(type_Int, "id");
2✔
224
    auto col_int = ttt.add_column(type_Int, "1");
2✔
225
    auto col_str = ttt.add_column(type_String, "2");
2✔
226

227
    ttt.create_object().set_all(0, 1, "a");
2✔
228
    ttt.create_object().set_all(1, 2, "a");
2✔
229
    ttt.create_object().set_all(2, 3, "X");
2✔
230
    ttt.create_object().set_all(3, 4, "a");
2✔
231
    ttt.create_object().set_all(4, 5, "a");
2✔
232
    ttt.create_object().set_all(5, 6, "a");
2✔
233
    ttt.create_object().set_all(6, 7, "X");
2✔
234
    ttt.create_object().set_all(7, 8, "z");
2✔
235

236
    // first == 5 || second == X
237
    Query q1 = ttt.where().equal(col_int, 5).Or().equal(col_str, "X");
2✔
238
    TableView tv1 = q1.find_all();
2✔
239
    CHECK_EQUAL(3, tv1.size());
2✔
240
    CHECK_EQUAL(2, tv1[0].get<Int>(col_id));
2✔
241
    CHECK_EQUAL(4, tv1[1].get<Int>(col_id));
2✔
242
    CHECK_EQUAL(6, tv1[2].get<Int>(col_id));
2✔
243

244
    // second == X || second == b || second == z || first == -1
245
    Query q2 =
2✔
246
        ttt.where().equal(col_str, "X").Or().equal(col_str, "b").Or().equal(col_str, "z").Or().equal(col_int, -1);
2✔
247
    TableView tv2 = q2.find_all();
2✔
248
    CHECK_EQUAL(3, tv2.size());
2✔
249
    CHECK_EQUAL(2, tv2[0].get<Int>(col_id));
2✔
250
    CHECK_EQUAL(6, tv2[1].get<Int>(col_id));
2✔
251
    CHECK_EQUAL(7, tv2[2].get<Int>(col_id));
2✔
252
}
2✔
253

254

255
TEST(Query_FindAllParens1)
256
{
2✔
257
    Table ttt;
2✔
258
    auto col_id = ttt.add_column(type_Int, "id");
2✔
259
    auto col_int = ttt.add_column(type_Int, "1");
2✔
260
    auto col_str = ttt.add_column(type_String, "2");
2✔
261

262
    ttt.create_object().set_all(0, 1, "a");
2✔
263
    ttt.create_object().set_all(1, 2, "a");
2✔
264
    ttt.create_object().set_all(2, 3, "X");
2✔
265
    ttt.create_object().set_all(3, 3, "X");
2✔
266
    ttt.create_object().set_all(4, 4, "a");
2✔
267
    ttt.create_object().set_all(5, 5, "a");
2✔
268
    ttt.create_object().set_all(6, 11, "X");
2✔
269

270
    // first > 3 && (second == X)
271
    Query q1 = ttt.where().greater(col_int, 3).group().equal(col_str, "X").end_group();
2✔
272
    TableView tv1 = q1.find_all();
2✔
273
    CHECK_EQUAL(1, tv1.size());
2✔
274
    CHECK_EQUAL(6, tv1[0].get<Int>(col_id));
2✔
275
}
2✔
276

277

278
TEST(Query_FindAllOrParan)
279
{
2✔
280
    Table ttt;
2✔
281
    auto col_id = ttt.add_column(type_Int, "id");
2✔
282
    auto col_int = ttt.add_column(type_Int, "1");
2✔
283
    auto col_str = ttt.add_column(type_String, "2");
2✔
284

285
    ttt.create_object().set_all(0, 1, "a");
2✔
286
    ttt.create_object().set_all(1, 2, "a");
2✔
287
    ttt.create_object().set_all(2, 3, "X"); //
2✔
288
    ttt.create_object().set_all(3, 4, "a");
2✔
289
    ttt.create_object().set_all(4, 5, "a"); //
2✔
290
    ttt.create_object().set_all(5, 6, "a");
2✔
291
    ttt.create_object().set_all(6, 7, "X"); //
2✔
292
    ttt.create_object().set_all(7, 2, "X");
2✔
293

294
    // (first == 5 || second == X && first > 2)
295
    Query q1 = ttt.where().group().equal(col_int, 5).Or().equal(col_str, "X").greater(col_int, 2).end_group();
2✔
296
    TableView tv1 = q1.find_all();
2✔
297
    CHECK_EQUAL(3, tv1.size());
2✔
298
    CHECK_EQUAL(2, tv1[0].get<Int>(col_id));
2✔
299
    CHECK_EQUAL(4, tv1[1].get<Int>(col_id));
2✔
300
    CHECK_EQUAL(6, tv1[2].get<Int>(col_id));
2✔
301
}
2✔
302

303

304
TEST(Query_FindAllOrNested0)
305
{
2✔
306
    Table ttt;
2✔
307
    auto col_id = ttt.add_column(type_Int, "id");
2✔
308
    auto col_int = ttt.add_column(type_Int, "1");
2✔
309
    auto col_str = ttt.add_column(type_String, "2");
2✔
310

311
    ttt.create_object().set_all(0, 1, "a");
2✔
312
    ttt.create_object().set_all(1, 2, "a");
2✔
313
    ttt.create_object().set_all(2, 3, "X");
2✔
314
    ttt.create_object().set_all(3, 3, "X");
2✔
315
    ttt.create_object().set_all(4, 4, "a");
2✔
316
    ttt.create_object().set_all(5, 5, "a");
2✔
317
    ttt.create_object().set_all(6, 11, "X");
2✔
318
    ttt.create_object().set_all(7, 8, "Y");
2✔
319

320
    // first > 3 && (first == 5 || second == X)
321
    Query q1 = ttt.where().greater(col_int, 3).group().equal(col_int, 5).Or().equal(col_str, "X").end_group();
2✔
322
    TableView tv1 = q1.find_all();
2✔
323
    CHECK_EQUAL(2, tv1.size());
2✔
324
    CHECK_EQUAL(5, tv1[0].get<Int>(col_id));
2✔
325
    CHECK_EQUAL(6, tv1[1].get<Int>(col_id));
2✔
326
}
2✔
327

328
TEST(Query_FindAllOrNested)
329
{
2✔
330
    Table ttt;
2✔
331
    auto col_id = ttt.add_column(type_Int, "id");
2✔
332
    auto col_int = ttt.add_column(type_Int, "1");
2✔
333
    auto col_str = ttt.add_column(type_String, "2");
2✔
334

335
    ttt.create_object().set_all(0, 1, "a");
2✔
336
    ttt.create_object().set_all(1, 2, "a");
2✔
337
    ttt.create_object().set_all(2, 3, "X");
2✔
338
    ttt.create_object().set_all(3, 3, "X");
2✔
339
    ttt.create_object().set_all(4, 4, "a");
2✔
340
    ttt.create_object().set_all(5, 5, "a");
2✔
341
    ttt.create_object().set_all(6, 11, "X");
2✔
342
    ttt.create_object().set_all(7, 8, "Y");
2✔
343

344
    // first > 3 && (first == 5 || second == X || second == Y)
345
    Query q1 = ttt.where()
2✔
346
                   .greater(col_int, 3)
2✔
347
                   .group()
2✔
348
                   .equal(col_int, 5)
2✔
349
                   .Or()
2✔
350
                   .equal(col_str, "X")
2✔
351
                   .Or()
2✔
352
                   .equal(col_str, "Y")
2✔
353
                   .end_group();
2✔
354
    TableView tv1 = q1.find_all();
2✔
355
    CHECK_EQUAL(3, tv1.size());
2✔
356
    CHECK_EQUAL(5, tv1[0].get<Int>(col_id));
2✔
357
    CHECK_EQUAL(6, tv1[1].get<Int>(col_id));
2✔
358
    CHECK_EQUAL(7, tv1[2].get<Int>(col_id));
2✔
359
}
2✔
360

361
TEST(Query_FindAllOrNestedInnerGroup)
362
{
2✔
363
    Table ttt;
2✔
364
    auto col_id = ttt.add_column(type_Int, "id");
2✔
365
    auto col_int = ttt.add_column(type_Int, "1");
2✔
366
    auto col_str = ttt.add_column(type_String, "2");
2✔
367

368
    ttt.create_object().set_all(0, 1, "a");
2✔
369
    ttt.create_object().set_all(1, 2, "a");
2✔
370
    ttt.create_object().set_all(2, 3, "X");
2✔
371
    ttt.create_object().set_all(3, 3, "X");
2✔
372
    ttt.create_object().set_all(4, 4, "a");
2✔
373
    ttt.create_object().set_all(5, 5, "a");
2✔
374
    ttt.create_object().set_all(6, 11, "X");
2✔
375
    ttt.create_object().set_all(7, 8, "Y");
2✔
376

377
    // first > 3 && (first == 5 || (second == X || second == Y))
378
    Query q1 = ttt.where()
2✔
379
                   .greater(col_int, 3)
2✔
380
                   .group()
2✔
381
                   .equal(col_int, 5)
2✔
382
                   .Or()
2✔
383
                   .group()
2✔
384
                   .equal(col_str, "X")
2✔
385
                   .Or()
2✔
386
                   .equal(col_str, "Y")
2✔
387
                   .end_group()
2✔
388
                   .end_group();
2✔
389
    TableView tv1 = q1.find_all();
2✔
390
    CHECK_EQUAL(3, tv1.size());
2✔
391
    CHECK_EQUAL(5, tv1[0].get<Int>(col_id));
2✔
392
    CHECK_EQUAL(6, tv1[1].get<Int>(col_id));
2✔
393
    CHECK_EQUAL(7, tv1[2].get<Int>(col_id));
2✔
394
}
2✔
395

396
TEST(Query_FindAllOrPHP)
397
{
2✔
398
    Table ttt;
2✔
399
    auto col_id = ttt.add_column(type_Int, "id");
2✔
400
    auto col_int = ttt.add_column(type_Int, "1");
2✔
401
    auto col_str = ttt.add_column(type_String, "2");
2✔
402

403
    ttt.create_object().set_all(0, 1, "Joe");
2✔
404
    ttt.create_object().set_all(1, 2, "Sara");
2✔
405
    ttt.create_object().set_all(2, 3, "Jim");
2✔
406

407
    // (second == Jim || second == Joe) && first = 1
408
    Query q1 = ttt.where().group().equal(col_str, "Jim").Or().equal(col_str, "Joe").end_group().equal(col_int, 1);
2✔
409
    TableView tv1 = q1.find_all();
2✔
410
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
411

412
    q1 = ttt.where().group().equal(col_str, "Jim").Or().equal(col_str, "Joe").end_group().equal(col_int, 3);
2✔
413
    tv1 = q1.find_all();
2✔
414
    CHECK_EQUAL(2, tv1[0].get<Int>(col_id));
2✔
415
}
2✔
416

417
TEST(Query_FindAllParens2)
418
{
2✔
419
    Table ttt;
2✔
420
    auto col_id = ttt.add_column(type_Int, "id");
2✔
421
    auto col_int = ttt.add_column(type_Int, "1");
2✔
422

423
    ttt.create_object().set_all(0, 1);
2✔
424
    ttt.create_object().set_all(1, 2);
2✔
425
    ttt.create_object().set_all(2, 3);
2✔
426
    ttt.create_object().set_all(3, 3);
2✔
427
    ttt.create_object().set_all(4, 4);
2✔
428
    ttt.create_object().set_all(5, 5);
2✔
429
    ttt.create_object().set_all(6, 11);
2✔
430

431
    // ()
432
    Query q1 = ttt.where().group().end_group();
2✔
433
    TableView tv1 = q1.find_all();
2✔
434
    CHECK_EQUAL(7, tv1.size());
2✔
435

436
    // ()((first > 3()) && (()))
437
    q1 = ttt.where()
2✔
438
             .group()
2✔
439
             .end_group()
2✔
440
             .group()
2✔
441
             .group()
2✔
442
             .greater(col_int, 3)
2✔
443
             .group()
2✔
444
             .end_group()
2✔
445
             .end_group()
2✔
446
             .group()
2✔
447
             .group()
2✔
448
             .end_group()
2✔
449
             .end_group()
2✔
450
             .end_group();
2✔
451
    tv1 = q1.find_all();
2✔
452
    CHECK_EQUAL(3, tv1.size());
2✔
453
    CHECK_EQUAL(4, tv1[0].get<Int>(col_id));
2✔
454
    CHECK_EQUAL(5, tv1[1].get<Int>(col_id));
2✔
455
    CHECK_EQUAL(6, tv1[2].get<Int>(col_id));
2✔
456
}
2✔
457

458

459
TEST(Query_FindAllBool)
460
{
2✔
461
    Table table;
2✔
462
    auto col_id = table.add_column(type_Int, "id");
2✔
463
    auto col_bool = table.add_column(type_Bool, "2");
2✔
464

465
    table.create_object().set_all(0, true);
2✔
466
    table.create_object().set_all(1, false);
2✔
467
    table.create_object().set_all(2, true);
2✔
468
    table.create_object().set_all(3, false);
2✔
469

470
    Query q1 = table.where().equal(col_bool, true);
2✔
471
    TableView tv1 = q1.find_all();
2✔
472
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
473
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
2✔
474

475
    Query q2 = table.where().equal(col_bool, false);
2✔
476
    TableView tv2 = q2.find_all();
2✔
477
    CHECK_EQUAL(1, tv2[0].get<Int>(col_id));
2✔
478
    CHECK_EQUAL(3, tv2[1].get<Int>(col_id));
2✔
479
}
2✔
480

481
TEST(Query_FindAllBegins)
482
{
2✔
483
    Table table;
2✔
484
    auto col_id = table.add_column(type_Int, "id");
2✔
485
    auto col_str = table.add_column(type_String, "2");
2✔
486

487
    table.create_object().set_all(0, "fo");
2✔
488
    table.create_object().set_all(1, "foo");
2✔
489
    table.create_object().set_all(2, "foobar");
2✔
490

491
    Query q1 = table.where().begins_with(col_str, StringData("foo"));
2✔
492
    TableView tv1 = q1.find_all();
2✔
493
    CHECK_EQUAL(2, tv1.size());
2✔
494
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
495
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
2✔
496
}
2✔
497

498
TEST(Query_FindAllEnds)
499
{
2✔
500
    Table table;
2✔
501
    auto col_id = table.add_column(type_Int, "id");
2✔
502
    auto col_str = table.add_column(type_String, "2");
2✔
503

504
    table.create_object().set_all(0, "barfo");
2✔
505
    table.create_object().set_all(1, "barfoo");
2✔
506
    table.create_object().set_all(2, "barfoobar");
2✔
507

508
    Query q1 = table.where().ends_with(col_str, StringData("foo"));
2✔
509
    TableView tv1 = q1.find_all();
2✔
510
    CHECK_EQUAL(1, tv1.size());
2✔
511
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
512
}
2✔
513

514

515
TEST(Query_FindAllContains)
516
{
2✔
517
    Table table;
2✔
518
    auto col_id = table.add_column(type_Int, "id");
2✔
519
    auto col_str = table.add_column(type_String, "2");
2✔
520

521
    table.create_object().set_all(0, "foo");
2✔
522
    table.create_object().set_all(1, "foobar");
2✔
523
    table.create_object().set_all(2, "barfoo");
2✔
524
    table.create_object().set_all(3, "barfoobaz");
2✔
525
    table.create_object().set_all(4, "fo");
2✔
526
    table.create_object().set_all(5, "fobar");
2✔
527
    table.create_object().set_all(6, "barfo");
2✔
528

529
    Query q1 = table.where().contains(col_str, StringData("foo"));
2✔
530
    TableView tv1 = q1.find_all();
2✔
531
    CHECK_EQUAL(4, tv1.size());
2✔
532
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
533
    CHECK_EQUAL(1, tv1[1].get<Int>(col_id));
2✔
534
    CHECK_EQUAL(2, tv1[2].get<Int>(col_id));
2✔
535
    CHECK_EQUAL(3, tv1[3].get<Int>(col_id));
2✔
536

537
    q1 = table.where().like(col_str, StringData("*foo*"));
2✔
538
    tv1 = q1.find_all();
2✔
539
    CHECK_EQUAL(4, tv1.size());
2✔
540
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
541
    CHECK_EQUAL(1, tv1[1].get<Int>(col_id));
2✔
542
    CHECK_EQUAL(2, tv1[2].get<Int>(col_id));
2✔
543
    CHECK_EQUAL(3, tv1[3].get<Int>(col_id));
2✔
544
}
2✔
545

546
TEST(Query_FindAllLikeStackOverflow)
547
{
2✔
548
    std::string str(100000, 'x');
2✔
549
    StringData sd(str);
2✔
550

551
    Table table;
2✔
552
    auto col = table.add_column(type_String, "strings");
2✔
553
    ObjKey k = table.create_object().set(col, sd).get_key();
2✔
554

555
    auto res = table.where().like(col, sd).find();
2✔
556
    CHECK_EQUAL(res, k);
2✔
557
}
2✔
558

559
TEST(Query_FindAllLikeCaseInsensitive)
560
{
2✔
561
    Table table;
2✔
562
    auto col_id = table.add_column(type_Int, "id");
2✔
563
    auto col_str = table.add_column(type_String, "2");
2✔
564

565
    table.create_object().set_all(0, "Foo");
2✔
566
    table.create_object().set_all(1, "FOOBAR");
2✔
567
    table.create_object().set_all(2, "BaRfOo");
2✔
568
    table.create_object().set_all(3, "barFOObaz");
2✔
569
    table.create_object().set_all(4, "Fo");
2✔
570
    table.create_object().set_all(5, "Fobar");
2✔
571
    table.create_object().set_all(6, "baRFo");
2✔
572

573
    Query q1 = table.where().like(col_str, StringData("*foo*"), false);
2✔
574
    TableView tv1 = q1.find_all();
2✔
575
    CHECK_EQUAL(4, tv1.size());
2✔
576
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
577
    CHECK_EQUAL(1, tv1[1].get<Int>(col_id));
2✔
578
    CHECK_EQUAL(2, tv1[2].get<Int>(col_id));
2✔
579
    CHECK_EQUAL(3, tv1[3].get<Int>(col_id));
2✔
580
}
2✔
581

582
TEST(Query_Binary)
583
{
2✔
584
    Table t;
2✔
585
    t.add_column(type_Int, "1");
2✔
586
    auto c1 = t.add_column(type_Binary, "2");
2✔
587

588
    const char bin[64] = {6, 3, 9, 5, 9, 7, 6, 3, 2, 6, 0, 0, 5, 4, 2, 4, 5, 7, 9, 5, 7, 1,
2✔
589
                          1, 2, 0, 8, 3, 8, 0, 9, 6, 8, 4, 7, 3, 4, 9, 5, 2, 3, 6, 2, 7, 4,
2✔
590
                          0, 3, 7, 6, 2, 3, 5, 9, 3, 1, 2, 1, 0, 5, 5, 2, 9, 4, 5, 9};
2✔
591

592
    const char bin_2[4] = {6, 6, 6, 6}; // Not occuring above
2✔
593

594
    std::vector<ObjKey> keys;
2✔
595
    t.create_objects(9, keys);
2✔
596

597
    t.get_object(keys[0]).set_all(0, BinaryData(bin + 0, 16));
2✔
598
    t.get_object(keys[1]).set_all(0, BinaryData(bin + 0, 32));
2✔
599
    t.get_object(keys[2]).set_all(0, BinaryData(bin + 0, 48));
2✔
600
    t.get_object(keys[3]).set_all(0, BinaryData(bin + 0, 64));
2✔
601
    t.get_object(keys[4]).set_all(0, BinaryData(bin + 16, 48));
2✔
602
    t.get_object(keys[5]).set_all(0, BinaryData(bin + 32, 32));
2✔
603
    t.get_object(keys[6]).set_all(0, BinaryData(bin + 48, 16));
2✔
604
    t.get_object(keys[7]).set_all(0, BinaryData(bin + 24, 16)); // The "odd ball"
2✔
605
    t.get_object(keys[8]).set_all(0, BinaryData(bin + 0, 32));  // Repeat an entry
2✔
606

607
    CHECK_EQUAL(0, t.where().equal(c1, BinaryData(bin + 16, 16)).count());
2✔
608
    CHECK_EQUAL(1, t.where().equal(c1, BinaryData(bin + 0, 16)).count());
2✔
609
    CHECK_EQUAL(1, t.where().equal(c1, BinaryData(bin + 48, 16)).count());
2✔
610
    CHECK_EQUAL(2, t.where().equal(c1, BinaryData(bin + 0, 32)).count());
2✔
611

612
    CHECK_EQUAL(9, t.where().not_equal(c1, BinaryData(bin + 16, 16)).count());
2✔
613
    CHECK_EQUAL(8, t.where().not_equal(c1, BinaryData(bin + 0, 16)).count());
2✔
614

615
    CHECK_EQUAL(0, t.where().begins_with(c1, BinaryData(bin + 8, 16)).count());
2✔
616
    CHECK_EQUAL(1, t.where().begins_with(c1, BinaryData(bin + 16, 16)).count());
2✔
617
    CHECK_EQUAL(4, t.where().begins_with(c1, BinaryData(bin + 0, 32)).count());
2✔
618
    CHECK_EQUAL(5, t.where().begins_with(c1, BinaryData(bin + 0, 16)).count());
2✔
619
    CHECK_EQUAL(1, t.where().begins_with(c1, BinaryData(bin + 48, 16)).count());
2✔
620
    CHECK_EQUAL(9, t.where().begins_with(c1, BinaryData(bin + 0, 0)).count());
2✔
621

622
    CHECK_EQUAL(0, t.where().ends_with(c1, BinaryData(bin + 40, 16)).count());
2✔
623
    CHECK_EQUAL(1, t.where().ends_with(c1, BinaryData(bin + 32, 16)).count());
2✔
624
    CHECK_EQUAL(3, t.where().ends_with(c1, BinaryData(bin + 32, 32)).count());
2✔
625
    CHECK_EQUAL(4, t.where().ends_with(c1, BinaryData(bin + 48, 16)).count());
2✔
626
    CHECK_EQUAL(1, t.where().ends_with(c1, BinaryData(bin + 0, 16)).count());
2✔
627
    CHECK_EQUAL(9, t.where().ends_with(c1, BinaryData(bin + 64, 0)).count());
2✔
628

629
    CHECK_EQUAL(0, t.where().contains(c1, BinaryData(bin_2)).count());
2✔
630
    CHECK_EQUAL(5, t.where().contains(c1, BinaryData(bin + 0, 16)).count());
2✔
631
    CHECK_EQUAL(5, t.where().contains(c1, BinaryData(bin + 16, 16)).count());
2✔
632
    CHECK_EQUAL(4, t.where().contains(c1, BinaryData(bin + 24, 16)).count());
2✔
633
    CHECK_EQUAL(4, t.where().contains(c1, BinaryData(bin + 32, 16)).count());
2✔
634
    CHECK_EQUAL(9, t.where().contains(c1, BinaryData(bin + 0, 0)).count());
2✔
635

636
    {
2✔
637
        TableView tv = t.where().equal(c1, BinaryData(bin + 0, 32)).find_all();
2✔
638
        if (tv.size() == 2) {
2✔
639
            CHECK_EQUAL(keys[1], tv.get_key(0));
2✔
640
            CHECK_EQUAL(keys[8], tv.get_key(1));
2✔
641
        }
2✔
642
        else
×
643
            CHECK(false);
×
644
    }
2✔
645

646
    {
2✔
647
        TableView tv = t.where().contains(c1, BinaryData(bin + 24, 16)).find_all();
2✔
648
        if (tv.size() == 4) {
2✔
649
            CHECK_EQUAL(keys[2], tv.get_key(0));
2✔
650
            CHECK_EQUAL(keys[3], tv.get_key(1));
2✔
651
            CHECK_EQUAL(keys[4], tv.get_key(2));
2✔
652
            CHECK_EQUAL(keys[7], tv.get_key(3));
2✔
653
        }
2✔
654
        else
×
655
            CHECK(false);
×
656
    }
2✔
657
}
2✔
658

659
TEST(Query_Enums)
660
{
2✔
661
    Table table;
2✔
662
    auto col_int = table.add_column(type_Int, "1");
2✔
663
    auto col_str = table.add_column(type_String, "2");
2✔
664

665

666
    for (size_t i = 0; i < 5; ++i) {
12✔
667
        table.create_object().set_all(1, "abd");
10✔
668
        table.create_object().set_all(2, "eftg");
10✔
669
        table.create_object().set_all(5, "hijkl");
10✔
670
        table.create_object().set_all(8, "mnopqr");
10✔
671
        table.create_object().set_all(9, "stuvxyz");
10✔
672
    }
10✔
673

674
    table.enumerate_string_column(col_str);
2✔
675

676
    Query q1 = table.where().equal(col_str, "eftg");
2✔
677
    TableView tv1 = q1.find_all();
2✔
678

679
    CHECK_EQUAL(5, tv1.size());
2✔
680
    CHECK_EQUAL(2, tv1[0].get<Int>(col_int));
2✔
681
    CHECK_EQUAL(2, tv1[1].get<Int>(col_int));
2✔
682
    CHECK_EQUAL(2, tv1[2].get<Int>(col_int));
2✔
683
    CHECK_EQUAL(2, tv1[3].get<Int>(col_int));
2✔
684
    CHECK_EQUAL(2, tv1[4].get<Int>(col_int));
2✔
685
}
2✔
686

687

688
TEST_TYPES(Query_CaseSensitivity, std::true_type, std::false_type)
689
{
4✔
690
    constexpr bool nullable = TEST_TYPE::value;
4✔
691

692
    Table ttt;
4✔
693
    auto col = ttt.add_column(type_String, "2", nullable);
4✔
694

695
    ObjKey k = ttt.create_object().set(col, "BLAAbaergroed").get_key();
4✔
696
    ttt.create_object().set(col, "BLAAbaergroedandMORE");
4✔
697
    ttt.create_object().set(col, "BLAAbaergroedZ");
4✔
698
    ttt.create_object().set(col, "BLAAbaergroedZ");
4✔
699
    ttt.create_object().set(col, "BLAAbaergroedZ");
4✔
700

701
    Query q1 = ttt.where().equal(col, "blaabaerGROED", false);
4✔
702
    TableView tv1 = q1.find_all();
4✔
703
    CHECK_EQUAL(1, tv1.size());
4✔
704
    CHECK_EQUAL(k, tv1.get_key(0));
4✔
705

706
    Query q2 = ttt.where().equal(col, "blaabaerGROEDz", false);
4✔
707
    TableView tv2 = q2.find_all();
4✔
708
    CHECK_EQUAL(3, tv2.size());
4✔
709

710
    ttt.add_search_index(col);
4✔
711

712
    Query q3 = ttt.where().equal(col, "blaabaerGROEDz", false);
4✔
713
    TableView tv3 = q3.find_all();
4✔
714
    CHECK_EQUAL(3, tv3.size());
4✔
715
}
4✔
716

717
#define uY "\x0CE\x0AB"            // greek capital letter upsilon with dialytika (U+03AB)
718
#define uYd "\x0CE\x0A5\x0CC\x088" // decomposed form (Y followed by two dots)
719
#define uy "\x0CF\x08B"            // greek small letter upsilon with dialytika (U+03AB)
720
#define uyd "\x0cf\x085\x0CC\x088" // decomposed form (Y followed by two dots)
721

722
#define uA "\x0c3\x085"       // danish capital A with ring above (as in BLAABAERGROED)
723
#define uAd "\x041\x0cc\x08a" // decomposed form (A (41) followed by ring)
4✔
724
#define ua "\x0c3\x0a5"       // danish lower case a with ring above (as in blaabaergroed)
725
#define uad "\x061\x0cc\x08a" // decomposed form (a (41) followed by ring)
26✔
726

727
#if (defined(_WIN32) || defined(__WIN32__) || defined(_WIN64))
728

729
TEST(Query_Unicode2)
730
{
731
    Table table;
732
    auto col_id = table.add_column(type_Int, "id");
733
    auto col_str = table.add_column(type_String, "2");
734

735
    table.create_object().set_all(0, uY);
736
    table.create_object().set_all(1, uYd);
737
    table.create_object().set_all(2, uy);
738
    table.create_object().set_all(3, uyd);
739

740
    Query q1 = table.where().equal(col_str, uY, false);
741
    TableView tv1 = q1.find_all();
742
    CHECK_EQUAL(2, tv1.size());
743
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
744
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
745

746
    Query q2 = table.where().equal(col_str, uYd, false);
747
    TableView tv2 = q2.find_all();
748
    CHECK_EQUAL(2, tv2.size());
749
    CHECK_EQUAL(1, tv2[0].get<Int>(col_id));
750
    CHECK_EQUAL(3, tv2[1].get<Int>(col_id));
751

752
    Query q3 = table.where().equal(col_str, uYd, true);
753
    TableView tv3 = q3.find_all();
754
    CHECK_EQUAL(1, tv3.size());
755
    CHECK_EQUAL(1, tv3[0].get<Int>(col_id));
756
}
757

758

759
TEST(Query_Unicode3)
760
{
761
    Table table;
762
    auto col_id = table.add_column(type_Int, "id");
763
    auto col_str = table.add_column(type_String, "2");
764

765
    table.create_object().set_all(0, uA);
766
    table.create_object().set_all(1, uAd);
767
    table.create_object().set_all(2, ua);
768
    table.create_object().set_all(3, uad);
769

770
    Query q1 = table.where().equal(col_str, uA, false);
771
    TableView tv1 = q1.find_all();
772
    CHECK_EQUAL(2, tv1.size());
773
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
774
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
775

776
    Query q2 = table.where().equal(col_str, ua, false);
777
    TableView tv2 = q2.find_all();
778
    CHECK_EQUAL(2, tv2.size());
779
    CHECK_EQUAL(0, tv2[0].get<Int>(col_id));
780
    CHECK_EQUAL(2, tv2[1].get<Int>(col_id));
781

782
    Query q3 = table.where().equal(col_str, uad, false);
783
    TableView tv3 = q3.find_all();
784
    CHECK_EQUAL(2, tv3.size());
785
    CHECK_EQUAL(1, tv3[0].get<Int>(col_id));
786
    CHECK_EQUAL(3, tv3[1].get<Int>(col_id));
787

788
    Query q4 = table.where().equal(col_str, uad, true);
789
    TableView tv4 = q4.find_all();
790
    CHECK_EQUAL(1, tv4.size());
791
    CHECK_EQUAL(3, tv4[0].get<Int>(col_id));
792
}
793

794
#endif
795

796
TEST(Query_FindAllBeginsUnicode)
797
{
2✔
798
    Table table;
2✔
799
    auto col_id = table.add_column(type_Int, "id");
2✔
800
    auto col_str = table.add_column(type_String, "2");
2✔
801

802
    table.create_object().set_all(0, uad "fo");
2✔
803
    table.create_object().set_all(1, uad "foo");
2✔
804
    table.create_object().set_all(2, uad "foobar");
2✔
805

806
    Query q1 = table.where().begins_with(col_str, StringData(uad "foo"));
2✔
807
    TableView tv1 = q1.find_all();
2✔
808
    CHECK_EQUAL(2, tv1.size());
2✔
809
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
810
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
2✔
811
}
2✔
812

813

814
TEST(Query_FindAllEndsUnicode)
815
{
2✔
816
    Table table;
2✔
817
    auto col_id = table.add_column(type_Int, "id");
2✔
818
    auto col_str = table.add_column(type_String, "2");
2✔
819

820
    table.create_object().set_all(0, "barfo");
2✔
821
    table.create_object().set_all(1, "barfoo" uad);
2✔
822
    table.create_object().set_all(2, "barfoobar");
2✔
823

824
    Query q1 = table.where().ends_with(col_str, StringData("foo" uad));
2✔
825
    TableView tv1 = q1.find_all();
2✔
826
    CHECK_EQUAL(1, tv1.size());
2✔
827
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
828

829
    Query q2 = table.where().ends_with(col_str, StringData("foo" uAd), false);
2✔
830
    TableView tv2 = q2.find_all();
2✔
831
    CHECK_EQUAL(1, tv2.size());
2✔
832
    CHECK_EQUAL(1, tv2[0].get<Int>(col_id));
2✔
833
}
2✔
834

835

836
TEST(Query_FindAllContainsUnicode)
837
{
2✔
838
    Table table;
2✔
839
    auto col_id = table.add_column(type_Int, "id");
2✔
840
    auto col_str = table.add_column(type_String, "2");
2✔
841

842
    table.create_object().set_all(0, uad "foo");
2✔
843
    table.create_object().set_all(1, uad "foobar");
2✔
844
    table.create_object().set_all(2, "bar" uad "foo");
2✔
845
    table.create_object().set_all(3, uad "bar" uad "foobaz");
2✔
846
    table.create_object().set_all(4, uad "fo");
2✔
847
    table.create_object().set_all(5, uad "fobar");
2✔
848
    table.create_object().set_all(6, uad "barfo");
2✔
849

850
    Query q1 = table.where().contains(col_str, StringData(uad "foo"));
2✔
851
    TableView tv1 = q1.find_all();
2✔
852
    CHECK_EQUAL(4, tv1.size());
2✔
853
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
854
    CHECK_EQUAL(1, tv1[1].get<Int>(col_id));
2✔
855
    CHECK_EQUAL(2, tv1[2].get<Int>(col_id));
2✔
856
    CHECK_EQUAL(3, tv1[3].get<Int>(col_id));
2✔
857

858
    Query q2 = table.where().contains(col_str, StringData(uAd "foo"), false);
2✔
859
    TableView tv2 = q2.find_all();
2✔
860
    CHECK_EQUAL(4, tv2.size());
2✔
861
    CHECK_EQUAL(0, tv2[0].get<Int>(col_id));
2✔
862
    CHECK_EQUAL(1, tv2[1].get<Int>(col_id));
2✔
863
    CHECK_EQUAL(2, tv2[2].get<Int>(col_id));
2✔
864
    CHECK_EQUAL(3, tv2[3].get<Int>(col_id));
2✔
865
}
2✔
866

867
TEST(Query_TestTV_where)
868
{
2✔
869
    // When using .where(&tv), tv can have any order, and the resulting view will retain its order
870
    Table table;
2✔
871
    auto col_int = table.add_column(type_Int, "1");
2✔
872
    auto col_str = table.add_column(type_String, "2");
2✔
873

874
    table.create_object().set_all(1, "a");
2✔
875
    table.create_object().set_all(2, "a");
2✔
876
    table.create_object().set_all(3, "c");
2✔
877

878
    TableView v = table.where().greater(col_int, 1).find_all();
2✔
879

880
    Query q1 = table.where(&v);
2✔
881
    CHECK_EQUAL(2, q1.count());
2✔
882

883
    Query q3 = table.where(&v).equal(col_str, "a");
2✔
884
    CHECK_EQUAL(1, q3.count());
2✔
885

886
    Query q4 = table.where(&v).between(col_int, 3, 6);
2✔
887
    CHECK_EQUAL(1, q4.count());
2✔
888
}
2✔
889

890

891
TEST(Query_SumMinMaxAvg)
892
{
2✔
893
    Table t;
2✔
894

895
    auto int_col = t.add_column(type_Int, "1");
2✔
896
    auto date_col = t.add_column(type_Timestamp, "3");
2✔
897
    auto float_col = t.add_column(type_Float, "4");
2✔
898
    auto double_col = t.add_column(type_Double, "5");
2✔
899
    auto decimal_col = t.add_column(type_Decimal, "6");
2✔
900
    auto mixed_col = t.add_column(type_Mixed, "7");
2✔
901

902
    std::vector<ObjKey> keys;
2✔
903
    t.create_objects(9, keys);
2✔
904
    t.get_object(keys[0]).set_all(1, Timestamp{200, 0}, 1.0f, 2.0, Decimal128{1.1}, Mixed{Decimal128{1.0}});
2✔
905
    t.get_object(keys[1]).set_all(1, Timestamp{100, 0}, 1.0f, 1.0, Decimal128{2.2}, Mixed{1.0f});
2✔
906
    t.get_object(keys[2]).set_all(1, Timestamp{100, 0}, 1.0f, 1.0, Decimal128{3.3}, Mixed{2.2f});
2✔
907
    t.get_object(keys[3]).set_all(1, Timestamp{100, 0}, 1.0f, 1.0, Decimal128{4.4}, Mixed{Decimal128{2.2}});
2✔
908
    t.get_object(keys[4]).set_all(2, Timestamp{300, 0}, 3.0f, 3.0, Decimal128{5.5}, Mixed{StringData("foo")});
2✔
909
    t.get_object(keys[5]).set_all(3, Timestamp{50, 0}, 5.0f, 5.0, Decimal128{6.6}, Mixed{Timestamp()});
2✔
910
    t.get_object(keys[6]).set_all(0, Timestamp{100, 0}, 1.0f, 1.0, Decimal128{7.7}, Mixed{});
2✔
911
    t.get_object(keys[7]).set_all(0, Timestamp{3000, 0}, 30.0f, 30.0, Decimal128{8.8}, Mixed{42});
2✔
912
    t.get_object(keys[8]).set_all(0, Timestamp{5, 0}, 0.5f, 0.5, Decimal128{9.9}, Mixed{0.1});
2✔
913

914
    CHECK_EQUAL(9, *t.where().sum(int_col));
2✔
915

916
    CHECK_EQUAL(0, *t.where().min(int_col));
2✔
917
    CHECK_EQUAL(3, *t.where().max(int_col));
2✔
918
    CHECK_EQUAL(Decimal128{9.9}, t.where().max(decimal_col));
2✔
919
    CHECK_EQUAL(Mixed{"foo"}, t.where().max(mixed_col));
2✔
920
    CHECK_EQUAL(Decimal128{1.1}, t.where().min(decimal_col));
2✔
921
    CHECK_EQUAL(Mixed{0.1}, t.where().min(mixed_col));
2✔
922
    CHECK_EQUAL(Decimal128{49.5}, t.where().sum(decimal_col));
2✔
923
    CHECK_EQUAL(Mixed{48.5}, t.where().sum(mixed_col));
2✔
924
    CHECK_EQUAL(Decimal128{49.5 / 9}, t.where().avg(decimal_col));
2✔
925
    Decimal128 avg_mixed = t.where().avg(mixed_col)->get_decimal();
2✔
926
    Decimal128 expected_avg_mixed = Decimal128{48.5 / 6};
2✔
927
    Decimal128 allowed_epsilon{0.001};
2✔
928
    CHECK(avg_mixed <= (expected_avg_mixed + allowed_epsilon) && avg_mixed >= (expected_avg_mixed - allowed_epsilon));
2✔
929
    t.get_object(keys[6]).set<Mixed>(mixed_col, Mixed{false});
2✔
930
    CHECK_EQUAL(Mixed{false}, t.where().min(mixed_col));
2✔
931

932
    ObjKey resindex;
2✔
933

934
    t.where().max(int_col, &resindex);
2✔
935
    CHECK_EQUAL(keys[5], resindex);
2✔
936

937
    t.where().min(int_col, &resindex);
2✔
938
    CHECK_EQUAL(keys[6], resindex);
2✔
939

940
    t.where().max(float_col, &resindex);
2✔
941
    CHECK_EQUAL(keys[7], resindex);
2✔
942

943
    t.where().min(float_col, &resindex);
2✔
944
    CHECK_EQUAL(keys[8], resindex);
2✔
945

946
    t.where().max(double_col, &resindex);
2✔
947
    CHECK_EQUAL(keys[7], resindex);
2✔
948

949
    t.where().min(double_col, &resindex);
2✔
950
    CHECK_EQUAL(keys[8], resindex);
2✔
951

952
    t.where().max(date_col, &resindex);
2✔
953
    CHECK_EQUAL(keys[7], resindex);
2✔
954

955
    t.where().min(date_col, &resindex);
2✔
956
    CHECK_EQUAL(keys[8], resindex);
2✔
957

958
    // Now with condition (tests another code path in Array::minmax())
959
    t.where().not_equal(int_col, 0).min(double_col, &resindex);
2✔
960
    CHECK_EQUAL(keys[1], resindex);
2✔
961

962
    t.where().not_equal(int_col, 0).min(float_col, &resindex);
2✔
963
    CHECK_EQUAL(keys[0], resindex);
2✔
964

965
    t.where().not_equal(int_col, 0).min(date_col, &resindex);
2✔
966
    CHECK_EQUAL(keys[5], resindex);
2✔
967

968
    t.where().not_equal(int_col, 0).max(date_col, &resindex);
2✔
969
    CHECK_EQUAL(keys[4], resindex);
2✔
970

971
    CHECK_APPROXIMATELY_EQUAL(1, t.where().avg(int_col)->get_double(), 0.001);
2✔
972

973
    CHECK_EQUAL(t.where().max(date_col), Timestamp(3000, 0));
2✔
974
    CHECK_EQUAL(t.where().min(date_col), Timestamp(5, 0));
2✔
975
}
2✔
976

977

978
TEST(Query_Avg)
979
{
2✔
980
    Table t;
2✔
981
    auto col = t.add_column(type_Int, "1");
2✔
982

983
    t.create_object().set(col, 10);
2✔
984
    CHECK_EQUAL(10, t.where().avg(col));
2✔
985
    t.create_object().set(col, 30);
2✔
986
    CHECK_EQUAL(20, t.where().avg(col));
2✔
987
}
2✔
988

989
TEST(Query_Avg2)
990
{
2✔
991
    Table t;
2✔
992
    auto col_int = t.add_column(type_Int, "1");
2✔
993
    auto col_str = t.add_column(type_String, "2");
2✔
994

995
    size_t cnt;
2✔
996

997
    t.create_object().set_all(10, "a");
2✔
998
    t.create_object().set_all(100, "b");
2✔
999
    t.create_object().set_all(20, "a");
2✔
1000
    t.create_object().set_all(100, "b");
2✔
1001
    t.create_object().set_all(100, "b");
2✔
1002
    t.create_object().set_all(30, "a");
2✔
1003

1004
    CHECK_EQUAL(60, t.where().equal(col_str, "a").sum(col_int));
2✔
1005

1006
    CHECK_EQUAL(20, t.where().equal(col_str, "a").avg(col_int, &cnt));
2✔
1007
    CHECK_EQUAL(3, cnt);
2✔
1008
    CHECK_EQUAL(100, t.where().equal(col_str, "b").avg(col_int, &cnt));
2✔
1009
    CHECK_EQUAL(3, cnt);
2✔
1010
}
2✔
1011

1012

1013
TEST(Query_OfByOne)
1014
{
2✔
1015
    Table t;
2✔
1016
    auto col_int = t.add_column(type_Int, "1");
2✔
1017
    t.add_column(type_String, "2");
2✔
1018
    size_t cluster_size = (REALM_MAX_BPNODE_SIZE > 256) ? 256 : 4;
2✔
1019
    for (size_t i = 0; i < cluster_size * 2; ++i) {
1,026✔
1020
        t.create_object().set_all(1, "a");
1,024✔
1021
    }
1,024✔
1022

1023
    // Top
1024
    Obj obj0 = t.get_object(0);
2✔
1025
    obj0.set(col_int, 0);
2✔
1026
    ObjKey res = t.where().equal(col_int, 0).find();
2✔
1027
    CHECK_EQUAL(obj0.get_key(), res);
2✔
1028
    obj0.set(col_int, 1); // reset
2✔
1029

1030
    // Before split
1031
    Obj obj1 = t.get_object(cluster_size - 1);
2✔
1032
    obj1.set(col_int, 0);
2✔
1033
    res = t.where().equal(col_int, 0).find();
2✔
1034
    CHECK_EQUAL(obj1.get_key(), res);
2✔
1035
    obj1.set(col_int, 1); // reset
2✔
1036

1037
    // After split
1038
    Obj obj2 = t.get_object(cluster_size);
2✔
1039
    obj2.set(col_int, 0);
2✔
1040
    res = t.where().equal(col_int, 0).find();
2✔
1041
    CHECK_EQUAL(obj2.get_key(), res);
2✔
1042
    obj2.set(col_int, 1); // reset
2✔
1043

1044
    // Before end
1045
    Obj obj3 = t.get_object((cluster_size * 2) - 1);
2✔
1046
    obj3.set(col_int, 0);
2✔
1047
    res = t.where().equal(col_int, 0).find();
2✔
1048
    CHECK_EQUAL(obj3.get_key(), res);
2✔
1049
    obj3.set(col_int, 1); // reset
2✔
1050
}
2✔
1051

1052

1053
TEST(Query_AllTypesDynamicallyTyped)
1054
{
2✔
1055
    for (int nullable = 0; nullable < 2; nullable++) {
6✔
1056
        bool n = (nullable == 1);
4✔
1057

1058
        Table table;
4✔
1059
        auto col_boo = table.add_column(type_Bool, "boo", n);
4✔
1060
        auto col_int = table.add_column(type_Int, "int", n);
4✔
1061
        auto col_flt = table.add_column(type_Float, "flt", n);
4✔
1062
        auto col_dbl = table.add_column(type_Double, "dbl", n);
4✔
1063
        auto col_str = table.add_column(type_String, "str", n);
4✔
1064
        auto col_bin = table.add_column(type_Binary, "bin", n);
4✔
1065
        auto col_dat = table.add_column(type_Timestamp, "dat", n);
4✔
1066
        auto col_lst = table.add_column_list(type_Int, "int_list");
4✔
1067

1068
        const char bin[4] = {0, 1, 2, 3};
4✔
1069
        BinaryData bin1(bin, sizeof bin / 2);
4✔
1070
        BinaryData bin2(bin, sizeof bin);
4✔
1071
        Timestamp time_now(time(nullptr), 0);
4✔
1072

1073
        Obj obj0 = table.create_object().set_all(false, 54, 0.7f, 0.8, StringData("foo"), bin1, Timestamp(0, 0));
4✔
1074
        Obj obj1 = table.create_object().set_all(true, 506, 7.7f, 8.8, StringData("banach"), bin2, time_now);
4✔
1075
        obj1.get_list<Int>(col_lst).add(100);
4✔
1076

1077
        CHECK_EQUAL(1, table.where().equal(col_boo, false).count());
4✔
1078
        CHECK_EQUAL(1, table.where().equal(col_int, int64_t(54)).count());
4✔
1079
        CHECK_EQUAL(1, table.where().equal(col_flt, 0.7f).count());
4✔
1080
        CHECK_EQUAL(1, table.where().equal(col_dbl, 0.8).count());
4✔
1081
        CHECK_EQUAL(1, table.where().equal(col_str, "foo").count());
4✔
1082
        CHECK_EQUAL(1, table.where().equal(col_bin, bin1).count());
4✔
1083
        CHECK_EQUAL(1, table.where().equal(col_dat, Timestamp(0, 0)).count());
4✔
1084
        //    CHECK_EQUAL(1, table.where().equal(7, subtab).count());
1085
        //    CHECK_EQUAL(1, table.where().equal(8, mix_int).count());
1086

1087
        Query query = table.where().equal(col_boo, false);
4✔
1088

1089
        ObjKey ndx;
4✔
1090

1091
        CHECK_EQUAL(54, query.min(col_int));
4✔
1092
        query.min(col_int, &ndx);
4✔
1093
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1094

1095
        CHECK_EQUAL(54, query.max(col_int));
4✔
1096
        query.max(col_int, &ndx);
4✔
1097
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1098

1099
        CHECK_EQUAL(54, query.sum(col_int));
4✔
1100
        CHECK_EQUAL(54, query.avg(col_int));
4✔
1101

1102
        CHECK_EQUAL(0.7f, query.min(col_flt));
4✔
1103
        query.min(col_flt, &ndx);
4✔
1104
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1105

1106
        CHECK_EQUAL(0.7f, query.max(col_flt));
4✔
1107
        query.max(col_flt, &ndx);
4✔
1108
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1109

1110
        CHECK_EQUAL(0.7f, query.sum(col_flt));
4✔
1111
        CHECK_EQUAL(0.7f, query.avg(col_flt));
4✔
1112

1113
        CHECK_EQUAL(0.8, query.min(col_dbl));
4✔
1114
        query.min(col_dbl, &ndx);
4✔
1115
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1116

1117
        CHECK_EQUAL(0.8, query.max(col_dbl));
4✔
1118
        query.max(col_dbl, &ndx);
4✔
1119
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1120

1121
        CHECK_EQUAL(0.8, query.sum(col_dbl));
4✔
1122
        CHECK_EQUAL(0.8, query.avg(col_dbl));
4✔
1123
    }
4✔
1124
}
2✔
1125

1126

1127
TEST(Query_AggregateSortedView)
1128
{
2✔
1129
    Table table;
2✔
1130
    auto col = table.add_column(type_Double, "col");
2✔
1131

1132
    const int count = REALM_MAX_BPNODE_SIZE * 2;
2✔
1133
    for (int i = 0; i < count; ++i)
4,002✔
1134
        table.create_object().set(col, double(i + 1)); // no 0s to reduce chance of passing by coincidence
4,000✔
1135

1136
    TableView tv = table.where().greater(col, 1.0).find_all();
2✔
1137
    tv.sort(col, false);
2✔
1138

1139
    CHECK_EQUAL(2.0, tv.min(col));
2✔
1140
    CHECK_EQUAL(count, tv.max(col));
2✔
1141
    CHECK_APPROXIMATELY_EQUAL((count + 1) * count / 2, tv.sum(col)->get_double(), .1);
2✔
1142
}
2✔
1143

1144

1145
TEST(Query_DeepCopy)
1146
{
2✔
1147
    // NOTE: You can only create a copy of a fully constructed; i.e. you cannot copy a query which is missing an
1148
    // end_group(). Run Query::validate() to see if it's fully constructed.
1149

1150
    Table t;
2✔
1151
    auto col_int = t.add_column(type_Int, "1");
2✔
1152
    auto col_str = t.add_column(type_String, "2");
2✔
1153
    auto col_dbl = t.add_column(type_Double, "3");
2✔
1154

1155
    ObjKey k0 = t.create_object().set_all(1, "1", 1.1).get_key();
2✔
1156
    t.create_object().set_all(2, "2", 2.2);
2✔
1157
    ObjKey k2 = t.create_object().set_all(3, "3", 3.3).get_key();
2✔
1158
    ObjKey k3 = t.create_object().set_all(4, "4", 4.4).get_key();
2✔
1159

1160
    // Explicit use of Value<>() makes query_expression node instead of query_engine.
1161
    Query q = t.column<Int>(col_int) > Value<Int>(2);
2✔
1162

1163

1164
    // Test if we can execute a copy
1165
    Query q2(q);
2✔
1166

1167
    CHECK_EQUAL(k2, q2.find());
2✔
1168

1169

1170
    // See if we can execute a copy of a deleted query. The copy should not contain references to the original.
1171
    Query* q3 = new Query(q);
2✔
1172
    Query* q4 = new Query(*q3);
2✔
1173
    delete q3;
2✔
1174

1175

1176
    // Attempt to overwrite memory of the deleted q3 by allocating various sized objects so that a spurious execution
1177
    // of methods on q3 can be detected (by making unit test crash).
1178
    char* tmp[1000];
2✔
1179
    for (size_t i = 0; i < sizeof(tmp) / sizeof(tmp[0]); i++) {
2,002✔
1180
        tmp[i] = new char[i];
2,000✔
1181
        memset(tmp[i], 0, i);
2,000✔
1182
    }
2,000✔
1183
    for (size_t i = 0; i < sizeof(tmp) / sizeof(tmp[0]); i++) {
2,002✔
1184
        delete[] tmp[i];
2,000✔
1185
    }
2,000✔
1186

1187
    CHECK_EQUAL(k2, q4->find());
2✔
1188
    delete q4;
2✔
1189

1190
    // See if we can append a criteria to a query
1191
    // Explicit use of Value<>() makes query_expression node instead of query_engine
1192
    Query q5 = t.column<Int>(col_int) > Value<Int>(2);
2✔
1193
    q5.greater(col_dbl, 4.0);
2✔
1194
    CHECK_EQUAL(k3, q5.find());
2✔
1195

1196
    // See if we can append a criteria to a copy without modifying the original (copy should not contain references
1197
    // to original). Tests query_expression integer node.
1198
    // Explicit use of Value<>() makes query_expression node instead of query_engine
1199
    Query q6 = t.column<Int>(col_int) > Value<Int>(2);
2✔
1200
    Query q7(q6);
2✔
1201

1202
    q7.greater(col_dbl, 4.0);
2✔
1203
    CHECK_EQUAL(k3, q7.find());
2✔
1204
    CHECK_EQUAL(k2, q6.find());
2✔
1205

1206

1207
    // See if we can append a criteria to a copy without modifying the original (copy should not contain references
1208
    // to original). Tests query_engine integer node.
1209
    Query q8 = t.column<Int>(col_int) > 2;
2✔
1210
    Query q9(q8);
2✔
1211

1212
    q9.greater(col_dbl, 4.0);
2✔
1213
    CHECK_EQUAL(k3, q9.find());
2✔
1214
    CHECK_EQUAL(k2, q8.find());
2✔
1215

1216

1217
    // See if we can append a criteria to a copy without modifying the original (copy should not contain references
1218
    // to original). Tests query_engine string node.
1219
    Query q10 = t.column<String>(col_str) != "2";
2✔
1220
    Query q11(q10);
2✔
1221

1222
    q11.greater(col_dbl, 4.0);
2✔
1223
    CHECK_EQUAL(k3, q11.find());
2✔
1224
    CHECK_EQUAL(k0, q10.find());
2✔
1225

1226
    // Test and_query() on a copy
1227
    Query q12 = t.column<Int>(col_int) > 2;
2✔
1228
    Query q13(q12);
2✔
1229

1230
    q13.and_query(t.column<String>(col_str) != "3");
2✔
1231
    CHECK_EQUAL(k3, q13.find());
2✔
1232
    CHECK_EQUAL(k2, q12.find());
2✔
1233
}
2✔
1234

1235
TEST(Query_TableViewMoveAssign1)
1236
{
2✔
1237
    Table t;
2✔
1238
    auto col_int = t.add_column(type_Int, "1");
2✔
1239

1240
    t.create_object().set(col_int, 1);
2✔
1241
    t.create_object().set(col_int, 2);
2✔
1242
    t.create_object().set(col_int, 3);
2✔
1243
    t.create_object().set(col_int, 4);
2✔
1244

1245
    // temporary query is created, then q makes and stores a deep copy and then temporary is destructed
1246
    // Explicit use of Value<>() makes query_expression node instead of query_engine
1247
    Query q = t.column<Int>(col_int) > Value<Int>(2);
2✔
1248

1249
    // now deep copy should be destructed and replaced by new temporary
1250
    TableView tv = q.find_all();
2✔
1251

1252
    // the original should still work; destruction of temporaries and deep copies should have no references
1253
    // to original
1254
    tv = q.find_all();
2✔
1255
}
2✔
1256

1257
TEST(Query_TableViewMoveAssignLeak2)
1258
{
2✔
1259
    Table t;
2✔
1260
    auto col_int = t.add_column(type_Int, "1");
2✔
1261
    auto col_str = t.add_column(type_String, "2");
2✔
1262
    auto col_dbl = t.add_column(type_Double, "3");
2✔
1263

1264
    Query q = t.column<Int>(col_int) < t.column<double>(col_dbl) && t.column<String>(col_str) == "4";
2✔
1265
    TableView tv = q.find_all();
2✔
1266

1267
    // Upon each find_all() call, tv copies the query 'q' into itself. See if this copying works
1268
    tv = q.find_all();
2✔
1269
    tv = q.find_all();
2✔
1270
    tv = q.find_all();
2✔
1271
    tv = q.find_all();
2✔
1272
    tv = q.find_all();
2✔
1273

1274
    tv.sort(col_int, true);
2✔
1275

1276
    tv = q.find_all();
2✔
1277

1278
    Query q2 = t.column<Int>(col_int) <= t.column<double>(col_dbl);
2✔
1279
    tv = q2.find_all();
2✔
1280
    q.and_query(q2);
2✔
1281
    tv = q.find_all();
2✔
1282

1283
    tv.sync_if_needed();
2✔
1284

1285
    ObjKey t2 = q.find();
2✔
1286
    static_cast<void>(t2);
2✔
1287
    tv = q.find_all();
2✔
1288
    tv.sync_if_needed();
2✔
1289
    t2 = q.find();
2✔
1290
    tv.sync_if_needed();
2✔
1291
    tv = q.find_all();
2✔
1292
    tv.sync_if_needed();
2✔
1293
    t2 = q.find();
2✔
1294
    tv.sync_if_needed();
2✔
1295
    tv = q.find_all();
2✔
1296
    tv.sync_if_needed();
2✔
1297
    tv = q.find_all();
2✔
1298
    tv.sync_if_needed();
2✔
1299

1300
    Query q3;
2✔
1301

1302
    q2 = t.column<Int>(col_int) <= t.column<double>(col_dbl);
2✔
1303
    q3 = q2;
2✔
1304

1305
    q3.find();
2✔
1306
    q2.find();
2✔
1307
}
2✔
1308

1309

1310
TEST(Query_DeepCopyLeak1)
1311
{
2✔
1312
    // NOTE: You can only create a copy of a fully constructed; i.e. you cannot copy a query which is missing an
1313
    // end_group(). Run Query::validate() to see if it's fully constructed.
1314

1315
    Table t;
2✔
1316
    auto col_int = t.add_column(type_Int, "1");
2✔
1317
    auto col_dbl = t.add_column(type_Double, "3");
2✔
1318

1319
    // See if copying of a mix of query_expression and query_engine nodes will leak
1320
    Query q =
2✔
1321
        !(t.column<Int>(col_int) > Value<Int>(2) && t.column<Int>(col_int) > 2 && t.column<double>(col_dbl) > 2.2) ||
2✔
1322
        t.column<Int>(col_int) == 4 || t.column<Int>(col_int) == Value<Int>(4);
2✔
1323
    Query q2(q);
2✔
1324
    Query q3(q2);
2✔
1325
}
2✔
1326

1327
TEST(Query_DeepCopyTest)
1328
{
2✔
1329
    // If Query::first vector was relocated because of push_back, then Query would crash, because referenced
1330
    // pointers were pointing into it.
1331
    Table table;
2✔
1332
    table.add_column(type_Int, "first");
2✔
1333

1334
    Query q1 = table.where();
2✔
1335

1336
    Query q2(q1);
2✔
1337

1338
    q2.group();
2✔
1339
    q2.end_group();
2✔
1340
}
2✔
1341

1342
TEST(Query_StringIndexCrash)
1343
{
2✔
1344
    // Test for a crash which occurred when a query testing for equality on a
1345
    // string index was deep-copied after being run
1346
    Table table;
2✔
1347
    auto col = table.add_column(type_String, "s", true);
2✔
1348
    table.add_search_index(col);
2✔
1349

1350
    Query q = table.where().equal(col, StringData(""));
2✔
1351
    q.count();
2✔
1352
    Query q2(q);
2✔
1353
}
2✔
1354

1355
TEST(Query_NullStrings)
1356
{
2✔
1357
    Table table;
2✔
1358
    auto col = table.add_column(type_String, "s", true);
2✔
1359

1360
    Query q;
2✔
1361
    TableView v;
2✔
1362

1363
    // Short strings
1364
    auto k0 = table.create_object().set<String>(col, "Albertslund").get_key(); // Normal non-empty string
2✔
1365
    auto k1 = table.create_object().set<String>(col, realm::null()).get_key(); // NULL string
2✔
1366
    auto k2 = table.create_object().set<String>(col, "").get_key();            // Empty string
2✔
1367

1368
    q = table.column<StringData>(col) == realm::null();
2✔
1369
    v = q.find_all();
2✔
1370
    CHECK_EQUAL(1, v.size());
2✔
1371
    CHECK_EQUAL(k1, v.get_key(0));
2✔
1372

1373
    q = table.column<StringData>(col) != realm::null();
2✔
1374
    v = q.find_all();
2✔
1375
    CHECK_EQUAL(2, v.size());
2✔
1376
    CHECK_EQUAL(k0, v.get_key(0));
2✔
1377
    CHECK_EQUAL(k2, v.get_key(1));
2✔
1378

1379
    // contrary to SQL, comparisons with realm::null() can be true in Realm (todo, discuss if we want this behaviour)
1380
    q = table.column<StringData>(col) != StringData("Albertslund");
2✔
1381
    v = q.find_all();
2✔
1382
    CHECK_EQUAL(2, v.size());
2✔
1383
    CHECK_EQUAL(k1, v.get_key(0));
2✔
1384
    CHECK_EQUAL(k2, v.get_key(1));
2✔
1385

1386
    q = table.column<StringData>(col) == "";
2✔
1387
    v = q.find_all();
2✔
1388
    CHECK_EQUAL(1, v.size());
2✔
1389
    CHECK_EQUAL(k2, v.get_key(0));
2✔
1390

1391
    // Medium strings (16+)
1392
    table.get_object(k0).set<String>(col, "AlbertslundAlbertslundAlbert");
2✔
1393

1394
    q = table.column<StringData>(col) == realm::null();
2✔
1395
    v = q.find_all();
2✔
1396
    CHECK_EQUAL(1, v.size());
2✔
1397
    CHECK_EQUAL(k1, v.get_key(0));
2✔
1398

1399
    q = table.column<StringData>(col) == "";
2✔
1400
    v = q.find_all();
2✔
1401
    CHECK_EQUAL(1, v.size());
2✔
1402
    CHECK_EQUAL(k2, v.get_key(0));
2✔
1403

1404
    // Long strings (64+)
1405
    table.get_object(k0).set<String>(col,
2✔
1406
                                     "AlbertslundAlbertslundAlbertslundAlbertslundAlbertslundAlbertslundAlbertslund");
2✔
1407
    q = table.column<StringData>(col) == realm::null();
2✔
1408
    v = q.find_all();
2✔
1409
    CHECK_EQUAL(1, v.size());
2✔
1410
    CHECK_EQUAL(k1, v.get_key(0));
2✔
1411

1412
    q = table.column<StringData>(col) == "";
2✔
1413
    v = q.find_all();
2✔
1414
    CHECK_EQUAL(1, v.size());
2✔
1415
    CHECK_EQUAL(k2, v.get_key(0));
2✔
1416
}
2✔
1417

1418
TEST(Query_Nulls_Fuzzy)
1419
{
2✔
1420
    for (int attributes = 1; attributes < 5; attributes++) {
10✔
1421
        Random random(random_int<unsigned long>());
8✔
1422

1423
        for (size_t t = 0; t < 10; t++) {
88✔
1424
            Table table;
80✔
1425
            auto col = table.add_column(type_String, "string", true);
80✔
1426

1427
            if (attributes == 0) {
80✔
1428
            }
×
1429
            if (attributes == 1) {
80✔
1430
                table.add_search_index(col);
20✔
1431
            }
20✔
1432
            else if (attributes == 2) {
60✔
1433
                table.enumerate_string_column(col);
20✔
1434
            }
20✔
1435
            else if (attributes == 3) {
40✔
1436
                table.add_search_index(col);
20✔
1437
                table.enumerate_string_column(col);
20✔
1438
            }
20✔
1439
            else if (attributes == 4) {
20✔
1440
                table.enumerate_string_column(col);
20✔
1441
                table.add_search_index(col);
20✔
1442
            }
20✔
1443

1444
            // map that is kept in sync with the column so that we can compare with it
1445
            std::map<ObjKey, std::string> v;
80✔
1446

1447
            // ArrayString capacity starts at 128 bytes, so we need lots of elements
1448
            // to test if relocation works
1449
            for (size_t i = 0; i < 100; i++) {
8,080✔
1450
                unsigned char action = static_cast<unsigned char>(random.draw_int_max<unsigned int>(100));
8,000✔
1451

1452
                if (action > 48 && table.size() < 10) {
8,000✔
1453
                    // Generate string with equal probability of being empty, null, short, medium and long, and with
1454
                    // their contents having equal proability of being either random or a duplicate of a previous
1455
                    // string. When it's random, each char must have equal probability of being 0 or non-0
1456
                    char buf[] =
3,954✔
1457
                        "This string is around 90 bytes long, which falls in the long-string type of Realm strings";
3,954✔
1458
                    char* buf1 = static_cast<char*>(malloc(sizeof(buf)));
3,954✔
1459
                    memcpy(buf1, buf, sizeof(buf));
3,954✔
1460
                    char buf2[] =
3,954✔
1461
                        "                                                                                         ";
3,954✔
1462

1463
                    StringData sd;
3,954✔
1464
                    std::string st;
3,954✔
1465

1466
                    if (fastrand(1) == 0) {
3,954✔
1467
                        // null string
1468
                        sd = realm::null();
1,995✔
1469
                        st = "null";
1,995✔
1470
                    }
1,995✔
1471
                    else {
1,959✔
1472
                        // non-null string
1473
                        size_t len = static_cast<size_t>(fastrand(3));
1,959✔
1474
                        if (len == 0)
1,959✔
1475
                            len = 0;
482✔
1476
                        else if (len == 1)
1,477✔
1477
                            len = 7;
505✔
1478
                        else if (len == 2)
972✔
1479
                            len = 27;
512✔
1480
                        else
460✔
1481
                            len = 73;
460✔
1482

1483
                        if (fastrand(1) == 0) {
1,959✔
1484
                            // duplicate string
1485
                            sd = StringData(buf1, len);
1,009✔
1486
                            st = std::string(buf1, len);
1,009✔
1487
                        }
1,009✔
1488
                        else {
950✔
1489
                            // random string
1490
                            for (size_t s = 0; s < len; s++) {
24,309✔
1491
                                if (fastrand(100) > 20)
23,359✔
1492
                                    buf2[s] = 0; // zero byte
18,504✔
1493
                                else
4,855✔
1494
                                    buf2[s] = static_cast<char>(fastrand(255)); // random byte
4,855✔
1495
                            }
23,359✔
1496
                            // no generated string can equal "null" (our vector magic value for null) because
1497
                            // len == 4 is not possible
1498
                            sd = StringData(buf2, len);
950✔
1499
                            st = std::string(buf2, len);
950✔
1500
                        }
950✔
1501
                    }
1,959✔
1502

1503
                    try {
3,954✔
1504
                        size_t pos = random.draw_int_max<size_t>(100000);
3,954✔
1505
                        auto k = table.create_object(ObjKey(int64_t(pos))).set<String>(col, sd).get_key();
3,954✔
1506

1507
                        v.emplace(k, st);
3,954✔
1508
                    }
3,954✔
1509
                    catch (...) {
3,954✔
UNCOV
1510
                    }
×
1511
                    free(buf1);
3,954✔
1512
                }
3,954✔
1513
                else if (table.size() > 0) {
4,046✔
1514
                    // delete
1515
                    size_t row = random.draw_int_max<size_t>(table.size() - 1);
3,523✔
1516
                    Obj obj = table.get_object(row);
3,523✔
1517
                    obj.remove();
3,523✔
1518
                    v.erase(obj.get_key());
3,523✔
1519
                }
3,523✔
1520

1521

1522
                CHECK_EQUAL(table.size(), v.size());
8,000✔
1523
                for (auto& o : table) {
35,292✔
1524
                    auto k = o.get_key();
35,292✔
1525
                    if (v[k] == "null") {
35,292✔
1526
                        CHECK(o.get<String>(col).is_null());
17,578✔
1527
                    }
17,578✔
1528
                    else {
17,714✔
1529
                        CHECK(o.get<String>(col) == v[k]);
17,714✔
1530
                    }
17,714✔
1531
                }
35,292✔
1532
            }
8,000✔
1533
        }
80✔
1534
    }
8✔
1535
}
2✔
1536

1537
TEST(Query_BinaryNull)
1538
{
2✔
1539
    Table table;
2✔
1540
    auto col = table.add_column(type_Binary, "first", true);
2✔
1541

1542
    auto k0 = table.create_object().set(col, BinaryData()).get_key();
2✔
1543
    auto k1 = table.create_object()
2✔
1544
                  .set(col, BinaryData("", 0))
2✔
1545
                  .get_key(); // NOTE: Specify size = 0, else size turns into 1!
2✔
1546
    auto k2 = table.create_object().set(col, BinaryData("foo")).get_key();
2✔
1547

1548
    TableView t;
2✔
1549

1550
    // Next gen syntax
1551
    t = (table.column<BinaryData>(col) == BinaryData()).find_all();
2✔
1552
    CHECK_EQUAL(1, t.size());
2✔
1553
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1554

1555
    t = (BinaryData() == table.column<BinaryData>(col)).find_all();
2✔
1556
    CHECK_EQUAL(1, t.size());
2✔
1557
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1558

1559
    t = (table.column<BinaryData>(col) == BinaryData("", 0)).find_all();
2✔
1560
    CHECK_EQUAL(1, t.size());
2✔
1561
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1562

1563
    t = (BinaryData("", 0) == table.column<BinaryData>(col)).find_all();
2✔
1564
    CHECK_EQUAL(1, t.size());
2✔
1565
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1566

1567
    t = (table.column<BinaryData>(col) != BinaryData("", 0)).find_all();
2✔
1568
    CHECK_EQUAL(2, t.size());
2✔
1569
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1570
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1571

1572
    t = (BinaryData("", 0) != table.column<BinaryData>(col)).find_all();
2✔
1573
    CHECK_EQUAL(2, t.size());
2✔
1574
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1575
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1576

1577

1578
    // Old syntax
1579
    t = table.where().equal(col, BinaryData()).find_all();
2✔
1580
    CHECK_EQUAL(1, t.size());
2✔
1581
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1582

1583
    t = table.where().equal(col, BinaryData("", 0)).find_all();
2✔
1584
    CHECK_EQUAL(1, t.size());
2✔
1585
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1586

1587
    t = table.where().equal(col, BinaryData("foo")).find_all();
2✔
1588
    CHECK_EQUAL(1, t.size());
2✔
1589
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1590

1591
    t = table.where().not_equal(col, BinaryData()).find_all();
2✔
1592
    CHECK_EQUAL(2, t.size());
2✔
1593
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1594
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1595

1596
    t = table.where().not_equal(col, BinaryData("", 0)).find_all();
2✔
1597
    CHECK_EQUAL(2, t.size());
2✔
1598
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1599
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1600

1601
    t = table.where().begins_with(col, BinaryData()).find_all();
2✔
1602
    CHECK_EQUAL(3, t.size());
2✔
1603

1604
    t = table.where().begins_with(col, BinaryData("", 0)).find_all();
2✔
1605
    CHECK_EQUAL(2, t.size());
2✔
1606
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1607
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1608

1609
    t = table.where().begins_with(col, BinaryData("foo")).find_all();
2✔
1610
    CHECK_EQUAL(1, t.size());
2✔
1611
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1612

1613
    t = table.where().ends_with(col, BinaryData()).find_all();
2✔
1614
    CHECK_EQUAL(3, t.size());
2✔
1615

1616
    t = table.where().ends_with(col, BinaryData("", 0)).find_all();
2✔
1617
    CHECK_EQUAL(2, t.size());
2✔
1618
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1619
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1620

1621
    t = table.where().ends_with(col, BinaryData("foo")).find_all();
2✔
1622
    CHECK_EQUAL(1, t.size());
2✔
1623
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1624
}
2✔
1625

1626
TEST(Query_IntegerNullOldQueryEngine)
1627
{
2✔
1628
    /*
1629
        first   second  third
1630
         null      100      1
1631
            0     null      2
1632
          123      200      3
1633
          null    null      4
1634
    */
1635
    Table table;
2✔
1636
    auto c0 = table.add_column(type_Int, "first", true);
2✔
1637
    auto c1 = table.add_column(type_Int, "second", true);
2✔
1638
    auto c2 = table.add_column(type_Int, "third", false);
2✔
1639

1640
    auto k0 = table.create_object(ObjKey(4), {/*      */ {c1, 100}, {c2, 1}}).get_key();
2✔
1641
    auto k1 = table.create_object(ObjKey(5), {{c0, 0}, /*        */ {c2, 2}}).get_key();
2✔
1642
    auto k2 = table.create_object(ObjKey(6), {{c0, 123}, {c1, 200}, {c2, 3}}).get_key();
2✔
1643
    auto k3 = table.create_object(ObjKey(7), {/*                 */ {c2, 7}}).get_key();
2✔
1644

1645
    TableView t;
2✔
1646

1647
    t = table.where().equal(c0, null{}).find_all();
2✔
1648
    CHECK_EQUAL(2, t.size());
2✔
1649
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1650
    CHECK_EQUAL(k3, t.get_key(1));
2✔
1651

1652
    t = table.where().equal(c1, null{}).find_all();
2✔
1653
    CHECK_EQUAL(2, t.size());
2✔
1654
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1655
    CHECK_EQUAL(k3, t.get_key(1));
2✔
1656

1657
    t = table.where().equal(c0, 0).find_all();
2✔
1658
    CHECK_EQUAL(1, t.size());
2✔
1659
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1660

1661
    t = table.where().equal(c0, 123).find_all();
2✔
1662
    CHECK_EQUAL(1, t.size());
2✔
1663
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1664

1665
    t = table.where().not_equal(c0, null{}).find_all();
2✔
1666
    CHECK_EQUAL(2, t.size());
2✔
1667
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1668
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1669

1670
    t = table.where().not_equal(c0, 0).find_all();
2✔
1671
    CHECK_EQUAL(3, t.size());
2✔
1672
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1673
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1674
    CHECK_EQUAL(k3, t.get_key(2));
2✔
1675

1676
    t = table.where().greater(c0, 0).find_all();
2✔
1677
    CHECK_EQUAL(1, t.size());
2✔
1678
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1679

1680
    t = table.where().greater(c2, 5).find_all();
2✔
1681
    CHECK_EQUAL(1, t.size());
2✔
1682
    CHECK_EQUAL(k3, t.get_key(0));
2✔
1683
}
2✔
1684

1685
TEST(Query_IntegerNonNull)
1686
{
2✔
1687
    Table table;
2✔
1688
    auto col = table.add_column(type_Int, "first", false);
2✔
1689

1690
    table.create_object().set(col, 123);
2✔
1691
    table.create_object().set(col, 456);
2✔
1692
    table.create_object();
2✔
1693

1694
    TableView t;
2✔
1695

1696
    // Fixme, should you be able to query a non-nullable column against null?
1697
    //    t = table.where().equal(0, null{}).find_all();
1698
    //    CHECK_EQUAL(0, t.size());
1699
}
2✔
1700

1701
TEST(Query_64BitValues)
1702
{
2✔
1703
    Group g;
2✔
1704
    ObjKey m;
2✔
1705
    TableRef table = g.add_table("table");
2✔
1706
    auto c0 = table->add_column(type_Int, "key");
2✔
1707
    auto c1 = table->add_column(type_Int, "16bit");
2✔
1708

1709
    const int64_t start = 4485019129LL;
2✔
1710
    const int64_t count = 20; // First 16 SSE-searched, four fallback
2✔
1711
    const int64_t min = std::numeric_limits<int64_t>::min();
2✔
1712
    const int64_t max = std::numeric_limits<int64_t>::max();
2✔
1713

1714
    for (size_t i = 0; i < count; ++i) {
42✔
1715
        table->create_object().set(c0, start + i);
40✔
1716
    }
40✔
1717

1718
    auto it = table->begin();
2✔
1719
    for (int64_t v = 5; v > 0; v--) {
12✔
1720
        // Insert values 5, 4, 3, 2, 1
1721
        it->set(c1, v);
10✔
1722
        ++it;
10✔
1723
    }
10✔
1724

1725
    m = table->where().less(c1, 4).find();
2✔
1726
    CHECK_EQUAL(2, m.value);
2✔
1727

1728
    m = table->where().less(c1, 5).find();
2✔
1729
    CHECK_EQUAL(1, m.value);
2✔
1730

1731
    CHECK_EQUAL(0, table->where().less(c0, min).count());
2✔
1732
    CHECK_EQUAL(0, table->where().less(c0, start).count());
2✔
1733
    CHECK_EQUAL(1, table->where().less(c0, start + 1).count());
2✔
1734
    CHECK_EQUAL(count, table->where().less(c0, start + count).count());
2✔
1735
    CHECK_EQUAL(count, table->where().less(c0, max).count());
2✔
1736

1737
    CHECK_EQUAL(0, table->where().less_equal(c0, min).count());
2✔
1738
    CHECK_EQUAL(1, table->where().less_equal(c0, start).count());
2✔
1739
    CHECK_EQUAL(count, table->where().less_equal(c0, start + count).count());
2✔
1740
    CHECK_EQUAL(count, table->where().less_equal(c0, max).count());
2✔
1741

1742
    CHECK_EQUAL(count, table->where().greater(c0, min).count());
2✔
1743
    CHECK_EQUAL(count - 1, table->where().greater(c0, start).count());
2✔
1744
    CHECK_EQUAL(1, table->where().greater(c0, start + count - 2).count());
2✔
1745
    CHECK_EQUAL(0, table->where().greater(c0, start + count - 1).count());
2✔
1746
    CHECK_EQUAL(0, table->where().greater(c0, max).count());
2✔
1747

1748
    CHECK_EQUAL(count, table->where().greater_equal(c0, min).count());
2✔
1749
    CHECK_EQUAL(count, table->where().greater_equal(c0, start).count());
2✔
1750
    CHECK_EQUAL(count - 1, table->where().greater_equal(c0, start + 1).count());
2✔
1751
    CHECK_EQUAL(1, table->where().greater_equal(c0, start + count - 1).count());
2✔
1752
    CHECK_EQUAL(0, table->where().greater_equal(c0, start + count).count());
2✔
1753
    CHECK_EQUAL(0, table->where().greater_equal(c0, max).count());
2✔
1754
}
2✔
1755

1756
namespace {
1757

1758
void create_columns(TableRef table, bool nullable = true)
1759
{
14✔
1760
    table->add_column(type_Int, "Price", nullable);
14✔
1761
    table->add_column(type_Float, "Shipping", nullable);
14✔
1762
    table->add_column(type_String, "Description", nullable);
14✔
1763
    table->add_column(type_Double, "Rating", nullable);
14✔
1764
    table->add_column(type_Bool, "Stock", nullable);
14✔
1765
    table->add_column(type_Timestamp, "Delivery date", nullable);
14✔
1766
    table->add_column(type_Binary, "Photo", nullable);
14✔
1767
}
14✔
1768

1769
bool equals(TableView& tv, const std::vector<int64_t>& keys)
1770
{
178✔
1771
    if (tv.size() != keys.size()) {
178✔
1772
        return false;
×
1773
    }
×
1774

1775
    size_t sz = tv.size();
178✔
1776
    for (size_t i = 0; i < sz; i++) {
24,372✔
1777
        if (tv.get_key(i).value != keys[i]) {
24,194✔
1778
            return false;
×
1779
        }
×
1780
    }
24,194✔
1781

1782
    return true;
178✔
1783
}
178✔
1784

1785
void fill_data(TableRef table)
1786
{
2✔
1787
    table->create_object().set_all(1, null(), null(), 1.1, true, Timestamp(12345, 0));
2✔
1788
    table->create_object().set_all(null(), null(), "foo", 2.2, null(), null());
2✔
1789
    table->create_object().set_all(3, 30.f, "bar", null(), false, Timestamp(12345, 67));
2✔
1790
}
2✔
1791
} // unnamed namespace
1792

1793
TEST(Query_NullShowcase)
1794
{
2✔
1795
    /*
1796
    Here we show how comparisons and arithmetic with null works in queries. Basic rules:
1797

1798
    null    +, -, *, /          value   ==   null
1799
    null    +, -, *, /          null    ==   null
1800

1801
    null    ==, >=, <=]         null    ==   true
1802
    null    !=, >, <            null    ==   false
1803

1804
    null    ==, >=, <=, >, <    value   ==   false
1805
    null    !=                  value   ==   true
1806

1807
    This does NOT follow SQL! In particular, (null == null) == true and
1808
    (null != value) == true.
1809

1810
    NOTE NOTE: There is currently only very little syntax checking.
1811

1812
    NOTE NOTE: For BinaryData, use BinaryData() instead of null().
1813

1814
        Price<int>      Shipping<float>     Description<String>     Rating<double>      Stock<bool>
1815
    Delivery<OldDateTime>   Photo<BinaryData>
1816
        -------------------------------------------------------------------------------------------------------------------------------------
1817
    0   null            null                null                    1.1                 true          2016-2-2 "foo"
1818
    1   10              null                "foo"                   2.2                 null          null
1819
    zero-lenght non-null
1820
    2   20              30.0                "bar"                   3.3                 false         2016-6-6 null
1821
    */
1822

1823
    Group g;
2✔
1824
    TableRef table = g.add_table("Inventory");
2✔
1825
    create_columns(table);
2✔
1826

1827
    Obj obj0 = table->create_object();
2✔
1828
    Obj obj1 = table->create_object();
2✔
1829
    Obj obj2 = table->create_object();
2✔
1830

1831
    // Default values for all nullable columns
1832
    for (auto col : table->get_column_keys()) {
14✔
1833
        CHECK(obj0.is_null(col));
14✔
1834
    }
14✔
1835

1836
    obj0.set_all(null(), null(), null(), 1.1, true, Timestamp(12345, 0), BinaryData("foo"));
2✔
1837
    obj1.set_all(10, null(), "foo", 2.2, null(), null(), BinaryData("", 0));
2✔
1838
    obj2.set_all(20, 30.f, "bar", 3.3, false, Timestamp(12345, 67), null());
2✔
1839

1840
    auto col_price = table->get_column_key("Price");
2✔
1841
    auto col_shipping = table->get_column_key("Shipping");
2✔
1842
    auto col_rating = table->get_column_key("Rating");
2✔
1843
    auto col_date = table->get_column_key("Delivery date");
2✔
1844
    Columns<Int> price = table->column<Int>(col_price);
2✔
1845
    Columns<Float> shipping = table->column<Float>(col_shipping);
2✔
1846
    Columns<Double> rating = table->column<Double>(col_rating);
2✔
1847
    Columns<Bool> stock = table->column<Bool>(table->get_column_key("Stock"));
2✔
1848
    Columns<Timestamp> delivery = table->column<Timestamp>(col_date);
2✔
1849
    Columns<BinaryData> photo = table->column<BinaryData>(table->get_column_key("Photo"));
2✔
1850

1851
    // check int/double type mismatch error handling
1852
    CHECK_THROW_ANY(table->column<Int>(table->get_column_key("Description")));
2✔
1853

1854
    TableView tv;
2✔
1855

1856
    tv = (price == null()).find_all();
2✔
1857
    CHECK(equals(tv, {0}));
2✔
1858

1859
    tv = (price != null()).find_all();
2✔
1860
    CHECK(equals(tv, {1, 2}));
2✔
1861

1862
    // Note that this returns rows with null, which differs from SQL!
1863
    tv = (price == shipping).find_all();
2✔
1864
    CHECK(equals(tv, {0})); // null == null
2✔
1865

1866
    // If you add a != null criteria, you would probably get what most users intended, like in SQL
1867
    tv = (price == shipping && price != null()).find_all();
2✔
1868
    CHECK(equals(tv, {}));
2✔
1869

1870
    tv = (price != shipping).find_all();
2✔
1871
    CHECK(equals(tv, {1, 2})); // 10 != null
2✔
1872

1873
    tv = (price < 0 || price > 0).find_all();
2✔
1874
    CHECK(equals(tv, {1, 2}));
2✔
1875

1876
    // Shows that null + null == null, and 10 + null == null, and null < 100 == false
1877
    tv = table->query("Price + Shipping < 100").find_all();
2✔
1878
    CHECK(equals(tv, {2}));
2✔
1879

1880
    //  null < 0 == false
1881
    tv = (price < 0).find_all();
2✔
1882
    CHECK(equals(tv, {}));
2✔
1883

1884
    //  null > 0 == false
1885
    tv = (price == 0).find_all();
2✔
1886
    CHECK(equals(tv, {}));
2✔
1887

1888
    // (null == 0) == false
1889
    tv = (price > 0).find_all();
2✔
1890
    CHECK(equals(tv, {1, 2}));
2✔
1891

1892
    // Doubles
1893
    // (null > double) == false
1894
    tv = (price > rating).find_all();
2✔
1895
    CHECK(equals(tv, {1, 2}));
2✔
1896

1897
    tv = table->query("Price + Rating == null").find_all();
2✔
1898
    CHECK(equals(tv, {0}));
2✔
1899

1900
    tv = table->query("Price + Rating != null").find_all();
2✔
1901
    CHECK(equals(tv, {1, 2}));
2✔
1902

1903

1904
    // Booleans
1905
    tv = (stock == true).find_all();
2✔
1906
    CHECK(equals(tv, {0}));
2✔
1907

1908
    tv = (stock == false).find_all();
2✔
1909
    CHECK(equals(tv, {2}));
2✔
1910

1911
    tv = (stock == null()).find_all();
2✔
1912
    CHECK(equals(tv, {1}));
2✔
1913

1914
    tv = (stock != null()).find_all();
2✔
1915
    CHECK(equals(tv, {0, 2}));
2✔
1916

1917
    // Dates
1918
    tv = (delivery == Timestamp(12345, 67)).find_all();
2✔
1919
    CHECK(equals(tv, {2}));
2✔
1920

1921
    tv = (delivery != Timestamp(12345, 67)).find_all();
2✔
1922
    CHECK(equals(tv, {0, 1}));
2✔
1923

1924
    tv = (delivery == null()).find_all();
2✔
1925
    CHECK(equals(tv, {1}));
2✔
1926

1927
    tv = (delivery != null()).find_all();
2✔
1928
    CHECK(equals(tv, {0, 2}));
2✔
1929

1930
    // BinaryData
1931
    //
1932
    // BinaryData only supports == and !=, and you cannot compare two columns - only a column and a constant
1933
    tv = (photo == BinaryData("foo")).find_all();
2✔
1934
    CHECK(equals(tv, {0}));
2✔
1935

1936
    tv = (photo == BinaryData("", 0)).find_all();
2✔
1937
    CHECK(equals(tv, {1}));
2✔
1938

1939
    tv = (photo == BinaryData()).find_all();
2✔
1940
    CHECK(equals(tv, {2}));
2✔
1941

1942
    tv = (photo != BinaryData("foo")).find_all();
2✔
1943
    CHECK(equals(tv, {1, 2}));
2✔
1944

1945
    // Old query syntax
1946
    tv = table->where().equal(col_price, null()).find_all();
2✔
1947
    CHECK(equals(tv, {0}));
2✔
1948

1949
    tv = table->where().not_equal(col_price, null()).find_all();
2✔
1950
    CHECK(equals(tv, {1, 2}));
2✔
1951

1952
    // You can also compare against user-given null with > and <, but only in the expression syntax!
1953
    tv = (price > null()).find_all();
2✔
1954
    CHECK(equals(tv, {}));
2✔
1955
    tv = table->query("Price + Rating > null").find_all();
2✔
1956
    CHECK(equals(tv, {}));
2✔
1957

1958
    // As stated above, if you want to use `> null()`, you cannot do it in the old syntax. This is for source
1959
    // code simplicity (would need tons of new method overloads that also need unit test testing, etc). So
1960
    // following is not possible and will not compile
1961
    // (tv = table->where().greater(0, null()).find_all());
1962

1963
    // Nullable floats in old syntax
1964
    tv = table->where().equal(col_shipping, null()).find_all();
2✔
1965
    CHECK(equals(tv, {0, 1}));
2✔
1966

1967
    tv = table->where().not_equal(col_shipping, null()).find_all();
2✔
1968
    CHECK(equals(tv, {2}));
2✔
1969

1970
    tv = table->where().greater(col_shipping, 0.0f).find_all();
2✔
1971
    CHECK(equals(tv, {2}));
2✔
1972

1973
    tv = table->where().less(col_shipping, 20.0f).find_all();
2✔
1974
    CHECK(equals(tv, {}));
2✔
1975

1976
    // TableView
1977
    size_t count;
2✔
1978
    std::optional<Mixed> m;
2✔
1979
    tv = table->where().find_all();
2✔
1980

1981
    // Integer column
1982
    m = tv.max(col_price);
2✔
1983
    CHECK_EQUAL(m, 20);
2✔
1984

1985
    m = tv.min(col_price);
2✔
1986
    CHECK_EQUAL(m, 10);
2✔
1987

1988
    count = 123;
2✔
1989
    m = tv.avg(col_price, &count);
2✔
1990
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 15., 0.001);
2✔
1991
    CHECK_EQUAL(count, 2);
2✔
1992

1993
    m = tv.sum(col_price);
2✔
1994
    CHECK_EQUAL(m, 30);
2✔
1995

1996

1997
    // Float column
1998
    m = tv.max(col_shipping);
2✔
1999
    CHECK_EQUAL(m, 30.);
2✔
2000

2001
    m = tv.min(col_shipping);
2✔
2002
    CHECK_EQUAL(m, 30.);
2✔
2003

2004
    count = 123;
2✔
2005
    m = tv.avg(col_shipping, &count);
2✔
2006
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 30., 0.001);
2✔
2007
    CHECK_EQUAL(count, 1);
2✔
2008

2009
    m = tv.sum(col_shipping);
2✔
2010
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 30., 0.001);
2✔
2011

2012
    // double column
2013
    m = tv.max(col_rating);
2✔
2014
    CHECK_EQUAL(m, 3.3);
2✔
2015
    m = tv.min(col_rating);
2✔
2016
    CHECK_EQUAL(m, 1.1);
2✔
2017
    m = tv.avg(col_rating);
2✔
2018
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), (1.1 + 2.2 + 3.3) / 3, 0.001);
2✔
2019
    m = tv.sum(col_rating);
2✔
2020
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 1.1 + 2.2 + 3.3, 0.001);
2✔
2021

2022
    // OldDateTime column
2023
    m = tv.max(col_date);
2✔
2024
    CHECK_EQUAL(m, Timestamp(12345, 67));
2✔
2025
    m = tv.min(col_date);
2✔
2026
    CHECK_EQUAL(m, Timestamp(12345, 0));
2✔
2027

2028
#if 0 // FIXME?
2029
    // NaN
2030
    // null converts to 0 when calling get_float() on it. We intentionally do not return the bit pattern
2031
    // for internal Realm representation, because that's a NaN, hence making it harder for the end user
2032
    // to distinguish between his own NaNs and null
2033
    CHECK_EQUAL(obj0.get<Float>(col_shipping), 0);
2034
#endif
2035

2036
    obj0.set<Float>(col_shipping, std::numeric_limits<float>::signaling_NaN());
2✔
2037
    obj1.set<Float>(col_shipping, std::numeric_limits<float>::quiet_NaN());
2✔
2038

2039
    // Realm may return a signalling/quiet NaN that is different from the signalling/quiet NaN you stored
2040
    // (the IEEE standard defines a sequence of bits in the NaN that can have custom contents). Realm does
2041
    // not preserve these bits.
2042
    CHECK(std::isnan(obj0.get<Float>(col_shipping)));
2✔
2043
    CHECK(std::isnan(obj1.get<Float>(col_shipping)));
2✔
2044

2045

2046
    // FIXME: std::numeric_limits<float>::signaling_NaN() seems broken in VS2015 in that it returns a non-
2047
    // signaling NaN. A bug report has been filed to Microsoft. Update: It turns out that on 32-bit Intel
2048
    // Architecture (at least on my Core i7 in 32 bit code), if you push a float-NaN (fld instruction) that
2049
    // has bit 22 clear (indicates it's signaling), and pop it back (fst instruction), the FPU will toggle
2050
    // that bit into being set. All this needs further investigation, so a P2 has been created. Note that
2051
    // IEEE just began specifying signaling vs. non-signaling NaNs in 2008. Also note that all this seems
2052
    // to work fine on ARM in both 32 and 64 bit mode.
2053

2054
#if !defined(_WIN32) && !REALM_ARCHITECTURE_X86_32
2✔
2055
    CHECK(null::is_signaling(obj0.get<Float>(col_shipping)));
2✔
2056
#endif
2✔
2057

2058
#ifndef _WIN32 // signaling_NaN() may be broken in VS2015 (see long comment above)
2✔
2059
    CHECK(!null::is_signaling(obj1.get<Float>(col_shipping)));
2✔
2060
#endif
2✔
2061

2062
    CHECK(!obj0.is_null(col_shipping));
2✔
2063
    CHECK(!obj1.is_null(col_shipping));
2✔
2064

2065
    obj0.set<Double>(col_rating, std::numeric_limits<double>::signaling_NaN());
2✔
2066
    obj1.set<Double>(col_rating, std::numeric_limits<double>::quiet_NaN());
2✔
2067
    CHECK(std::isnan(obj0.get<Double>(col_rating)));
2✔
2068
    CHECK(std::isnan(obj1.get<Double>(col_rating)));
2✔
2069

2070
// signaling_NaN() broken in VS2015, and broken in 32bit intel
2071
#if !defined(_WIN32) && !REALM_ARCHITECTURE_X86_32
2✔
2072
    CHECK(null::is_signaling(obj0.get<Double>(col_rating)));
2✔
2073
    CHECK(!null::is_signaling(obj1.get<Double>(col_rating)));
2✔
2074
#endif
2✔
2075

2076
    CHECK(!obj0.is_null(col_rating));
2✔
2077
    CHECK(!obj1.is_null(col_rating));
2✔
2078

2079
    // NOTE NOTE Queries on float/double columns that contain user-given NaNs are undefined.
2080
}
2✔
2081

2082

2083
// Test error handling and default values (user gives bad column type, is_null() returns false,
2084
// get_float() must return 0.9 for null entries, etc, etc)
2085
TEST(Query_Null_DefaultsAndErrorhandling)
2086
{
2✔
2087
    // Non-nullable columns: Tests is_nullable() and set_null()
2088
    {
2✔
2089
        Group g;
2✔
2090
        TableRef table = g.add_table("Inventory");
2✔
2091
        create_columns(table, false /* nullability */);
2✔
2092

2093
        Obj obj = table->create_object();
2✔
2094

2095
        auto all_cols = table->get_column_keys();
2✔
2096

2097
        for (auto col : all_cols) {
14✔
2098
            CHECK(!table->is_nullable(col));
14✔
2099
        }
14✔
2100

2101
        // is_null() on non-nullable column returns false. If you want it to throw, then do so
2102
        // in the language binding
2103
        for (auto col : all_cols) {
14✔
2104
            CHECK(!obj.is_null(col));
14✔
2105
        }
14✔
2106

2107
        for (auto col : all_cols) {
14✔
2108
            CHECK_THROW_ANY(obj.set_null(col));
14✔
2109
        }
14✔
2110

2111
        // verify that set_null() did not have any side effects
2112
        for (auto col : all_cols) {
14✔
2113
            CHECK(!obj.is_null(col));
14✔
2114
        }
14✔
2115
    }
2✔
2116

2117
    // Nullable columns: Tests that default value is null, and tests is_nullable() and set_null()
2118
    {
2✔
2119
        Group g;
2✔
2120
        TableRef table = g.add_table("Inventory");
2✔
2121
        create_columns(table);
2✔
2122

2123
        Obj obj = table->create_object();
2✔
2124

2125
        auto all_cols = table->get_column_keys();
2✔
2126

2127
        for (auto col : all_cols) {
14✔
2128
            CHECK(table->is_nullable(col));
14✔
2129
        }
14✔
2130

2131
        // default values should be null
2132
        for (auto col : all_cols) {
14✔
2133
            CHECK(obj.is_null(col));
14✔
2134
        }
14✔
2135

2136
#if 0
2137
        // calling get() on a numeric column must return following:
2138
        CHECK_EQUAL(table->get_int(0, 0), 0);
2139
        CHECK_EQUAL(table->get_float(1, 0), 0.0f);
2140
        CHECK_EQUAL(table->get_double(3, 0), 0.0);
2141
        CHECK_EQUAL(table->get_bool(4, 0), false);
2142
        CHECK_EQUAL(table->get_olddatetime(5, 0), OldDateTime(0));
2143
#endif
2144
        // Set everything to non-null values
2145
        char bin = 8;
2✔
2146
        obj.set_all(0, 0.f, StringData("", 0), 0., false, Timestamp(1, 2), Binary(&bin, sizeof(bin)));
2✔
2147

2148
        for (auto col : all_cols) {
14✔
2149
            CHECK(!obj.is_null(col));
14✔
2150
        }
14✔
2151

2152
        for (auto col : all_cols) {
14✔
2153
            obj.set_null(col);
14✔
2154
        }
14✔
2155

2156
        for (auto col : all_cols) {
14✔
2157
            CHECK(obj.is_null(col));
14✔
2158
        }
14✔
2159
    }
2✔
2160
}
2✔
2161

2162
// Tests queries that compare two columns with eachother in various ways. The columns have different
2163
// integral types
2164
TEST(Query_Null_Two_Columns)
2165
{
2✔
2166
    Group g;
2✔
2167
    TableRef table = g.add_table("Inventory");
2✔
2168
    create_columns(table);
2✔
2169
    fill_data(table);
2✔
2170

2171
    auto col_price = table->get_column_key("Price");
2✔
2172
    auto col_shipping = table->get_column_key("Shipping");
2✔
2173
    auto col_description = table->get_column_key("Description");
2✔
2174
    auto col_rating = table->get_column_key("Rating");
2✔
2175
    auto col_date = table->get_column_key("Delivery date");
2✔
2176
    Columns<Int> price = table->column<Int>(col_price);
2✔
2177
    Columns<Float> shipping = table->column<Float>(col_shipping);
2✔
2178
    Columns<String> description = table->column<String>(col_description);
2✔
2179
    Columns<Double> rating = table->column<Double>(col_rating);
2✔
2180
    Columns<Bool> stock = table->column<Bool>(table->get_column_key("Stock"));
2✔
2181
    Columns<Timestamp> delivery = table->column<Timestamp>(col_date);
2✔
2182
    Columns<BinaryData> photo = table->column<BinaryData>(table->get_column_key("Photo"));
2✔
2183

2184
    TableView tv;
2✔
2185

2186
    /*
2187
    Price<int>      Shipping<float>     Description<String>     Rating<double>      Stock<bool> Delivery<Timestamp>
2188
    ----------------------------------------------------------------------------------------------------------------
2189
    0   1           null                null                    1.1                 true          12345, 0
2190
    1   null        null                "foo"                   2.2                 null          null
2191
    2   3           30.0                "bar"                   null                false         12345, 67
2192
    */
2193

2194
    tv = (shipping > rating).find_all();
2✔
2195
    CHECK(equals(tv, {}));
2✔
2196

2197
    tv = (shipping < rating).find_all();
2✔
2198
    CHECK(equals(tv, {}));
2✔
2199

2200
    tv = (price == rating).find_all();
2✔
2201
    CHECK(equals(tv, {}));
2✔
2202

2203
    tv = (price != rating).find_all();
2✔
2204
    CHECK(equals(tv, {0, 1, 2}));
2✔
2205

2206
    tv = (shipping == rating).find_all();
2✔
2207
    CHECK(equals(tv, {}));
2✔
2208

2209
    tv = (shipping != rating).find_all();
2✔
2210
    CHECK(equals(tv, {0, 1, 2}));
2✔
2211

2212
    // Comparison column with itself
2213
    tv = (shipping == shipping).find_all();
2✔
2214
    CHECK(equals(tv, {0, 1, 2}));
2✔
2215

2216
    tv = (shipping > shipping).find_all();
2✔
2217
    CHECK(equals(tv, {}));
2✔
2218

2219
    tv = (shipping < shipping).find_all();
2✔
2220
    CHECK(equals(tv, {}));
2✔
2221

2222
    tv = (shipping <= shipping).find_all();
2✔
2223
    CHECK(equals(tv, {0, 1, 2}));
2✔
2224

2225
    tv = (shipping >= shipping).find_all();
2✔
2226
    CHECK(equals(tv, {0, 1, 2}));
2✔
2227

2228
    tv = (rating == rating).find_all();
2✔
2229
    CHECK(equals(tv, {0, 1, 2}));
2✔
2230

2231
    tv = (rating != rating).find_all();
2✔
2232
    CHECK(equals(tv, {}));
2✔
2233

2234
    tv = (rating > rating).find_all();
2✔
2235
    CHECK(equals(tv, {}));
2✔
2236

2237
    tv = (rating < rating).find_all();
2✔
2238
    CHECK(equals(tv, {}));
2✔
2239

2240
    tv = (rating >= rating).find_all();
2✔
2241
    CHECK(equals(tv, {0, 1, 2}));
2✔
2242

2243
    tv = (rating <= rating).find_all();
2✔
2244
    CHECK(equals(tv, {0, 1, 2}));
2✔
2245

2246
    tv = (stock == stock).find_all();
2✔
2247
    CHECK(equals(tv, {0, 1, 2}));
2✔
2248

2249
    tv = (stock != stock).find_all();
2✔
2250
    CHECK(equals(tv, {}));
2✔
2251

2252
    tv = (price == price).find_all();
2✔
2253
    CHECK(equals(tv, {0, 1, 2}));
2✔
2254

2255
    tv = (price != price).find_all();
2✔
2256
    CHECK(equals(tv, {}));
2✔
2257

2258
    tv = (price > price).find_all();
2✔
2259
    CHECK(equals(tv, {}));
2✔
2260

2261
    tv = (price < price).find_all();
2✔
2262
    CHECK(equals(tv, {}));
2✔
2263

2264
    tv = (price >= price).find_all();
2✔
2265
    CHECK(equals(tv, {0, 1, 2}));
2✔
2266

2267
    tv = (price <= price).find_all();
2✔
2268
    CHECK(equals(tv, {0, 1, 2}));
2✔
2269

2270
    tv = (delivery == delivery).find_all();
2✔
2271
    CHECK(equals(tv, {0, 1, 2}));
2✔
2272

2273
    tv = (delivery != delivery).find_all();
2✔
2274
    CHECK(equals(tv, {}));
2✔
2275

2276
    tv = (delivery > delivery).find_all();
2✔
2277
    CHECK(equals(tv, {}));
2✔
2278

2279
    tv = (delivery < delivery).find_all();
2✔
2280
    CHECK(equals(tv, {}));
2✔
2281

2282
    tv = (delivery >= delivery).find_all();
2✔
2283
    CHECK(equals(tv, {0, 1, 2}));
2✔
2284

2285
    tv = (delivery <= delivery).find_all();
2✔
2286
    CHECK(equals(tv, {0, 1, 2}));
2✔
2287

2288
    tv = (description == description).find_all();
2✔
2289
    CHECK(equals(tv, {0, 1, 2}));
2✔
2290

2291
    tv = (description != description).find_all();
2✔
2292
    CHECK(equals(tv, {}));
2✔
2293

2294
    // Test a few untested things
2295
    tv = table->where().equal(col_rating, null()).find_all();
2✔
2296
    CHECK(equals(tv, {2}));
2✔
2297

2298
    tv = table->where().equal(col_price, null()).find_all();
2✔
2299
    CHECK(equals(tv, {1}));
2✔
2300

2301
    tv = table->where().not_equal(col_rating, null()).find_all();
2✔
2302
    CHECK(equals(tv, {0, 1}));
2✔
2303

2304
    tv = table->where().between(col_price, 2, 4).find_all();
2✔
2305
    CHECK(equals(tv, {2}));
2✔
2306

2307
    // between for floats
2308
    tv = table->where().between(col_shipping, 10.f, 40.f).find_all();
2✔
2309
    CHECK(equals(tv, {2}));
2✔
2310

2311
    tv = table->where().between(col_shipping, 0.f, 20.f).find_all();
2✔
2312
    CHECK(equals(tv, {}));
2✔
2313

2314
    tv = table->where().between(col_shipping, 40.f, 100.f).find_all();
2✔
2315
    CHECK(equals(tv, {}));
2✔
2316

2317
    // between for doubles
2318
    tv = table->where().between(col_rating, 0., 100.).find_all();
2✔
2319
    CHECK(equals(tv, {0, 1}));
2✔
2320

2321
    tv = table->where().between(col_rating, 1., 2.).find_all();
2✔
2322
    CHECK(equals(tv, {0}));
2✔
2323

2324
    tv = table->where().between(col_rating, 2., 3.).find_all();
2✔
2325
    CHECK(equals(tv, {1}));
2✔
2326

2327
    tv = table->where().between(col_rating, 3., 100.).find_all();
2✔
2328
    CHECK(equals(tv, {}));
2✔
2329
}
2✔
2330

2331
// Between, count, min and max
2332
TEST(Query_Null_BetweenMinMax_Nullable)
2333
{
2✔
2334
    Group g;
2✔
2335
    TableRef table = g.add_table("Inventory");
2✔
2336
    create_columns(table);
2✔
2337
    table->create_object();
2✔
2338
    auto col_price = table->get_column_key("Price");
2✔
2339
    auto col_shipping = table->get_column_key("Shipping");
2✔
2340
    auto col_rating = table->get_column_key("Rating");
2✔
2341
    auto col_date = table->get_column_key("Delivery date");
2✔
2342

2343
    /*
2344
    Price<int>      Shipping<float>     Description<String>     Rating<double>      Stock<bool>
2345
    Delivery<OldDateTime>     ts<Timestamp>
2346
    --------------------------------------------------------------------------------------------------------------------------------------
2347
    null            null                null                    null                null            null null
2348
    */
2349

2350
    TableView tv;
2✔
2351
    ObjKey match;
2✔
2352
    size_t count;
2✔
2353

2354
    // Here we test max/min/average with 0 rows used to compute the value, either becuase all inputs are null or
2355
    // becuase 0 rows exist.
2356
    auto test_tv = [&]() {
4✔
2357
        // int
2358
        match = ObjKey(123);
4✔
2359
        tv.max(col_price, &match);
4✔
2360
        CHECK_EQUAL(match, realm::null_key);
4✔
2361

2362
        match = ObjKey(123);
4✔
2363
        tv.min(col_price, &match);
4✔
2364
        CHECK_EQUAL(match, realm::null_key);
4✔
2365

2366
        CHECK_EQUAL(tv.sum(col_price), 0);
4✔
2367
        count = 123;
4✔
2368
        CHECK(tv.avg(col_price, &count)->is_null());
4✔
2369
        CHECK_EQUAL(count, 0);
4✔
2370

2371
        // float
2372
        match = ObjKey(123);
4✔
2373
        CHECK(tv.max(col_shipping, &match)->is_null());
4✔
2374
        CHECK_EQUAL(match, realm::null_key);
4✔
2375

2376
        match = ObjKey(123);
4✔
2377
        CHECK(tv.min(col_shipping, &match)->is_null());
4✔
2378
        CHECK_EQUAL(match, realm::null_key);
4✔
2379

2380
        CHECK_EQUAL(tv.sum(col_shipping), 0.);
4✔
2381
        count = 123;
4✔
2382
        CHECK(tv.avg(col_shipping, &count)->is_null());
4✔
2383
        CHECK_EQUAL(count, 0);
4✔
2384

2385
        // double
2386
        match = ObjKey(123);
4✔
2387
        CHECK(tv.max(col_rating, &match)->is_null());
4✔
2388
        CHECK_EQUAL(match, realm::null_key);
4✔
2389

2390
        match = ObjKey(123);
4✔
2391
        CHECK(tv.min(col_rating, &match)->is_null());
4✔
2392
        CHECK_EQUAL(match, realm::null_key);
4✔
2393

2394
        CHECK_EQUAL(tv.sum(col_rating), 0.);
4✔
2395
        count = 123;
4✔
2396
        CHECK(tv.avg(col_rating, &count)->is_null());
4✔
2397
        CHECK_EQUAL(count, 0);
4✔
2398

2399
        // date
2400
        match = ObjKey(123);
4✔
2401
        tv.max(col_date, &match);
4✔
2402
        CHECK_EQUAL(match, realm::null_key);
4✔
2403

2404
        match = ObjKey(123);
4✔
2405
        tv.min(col_date, &match);
4✔
2406
        CHECK_EQUAL(match, realm::null_key);
4✔
2407
    };
4✔
2408

2409
    // There are rows in TableView but they all point to null
2410
    tv = table->where().find_all();
2✔
2411
    test_tv();
2✔
2412

2413
    // There are 0 rows in TableView
2414
    tv = table->where().equal(col_price, 123).find_all();
2✔
2415
    test_tv();
2✔
2416

2417
    // Now we test that average does not include nulls in row count:
2418
    /*
2419
    Price<int>      Shipping<float>     Description<String>     Rating<double>      Stock<bool> Delivery<OldDateTime>
2420
    ----------------------------------------------------------------------------------------------------------------
2421
    null            null                null                    null                null            null
2422
    10              10.f                null                    10.                 null            null
2423
    */
2424

2425
    table->create_object().set_all(10, 10.f, null(), 10.);
2✔
2426

2427
    tv = table->where().find_all();
2✔
2428
    count = 123;
2✔
2429
    CHECK_EQUAL(tv.avg(col_price, &count), 10);
2✔
2430
    CHECK_EQUAL(count, 1);
2✔
2431
    count = 123;
2✔
2432
    CHECK_EQUAL(tv.avg(col_shipping, &count), 10.);
2✔
2433
    CHECK_EQUAL(count, 1);
2✔
2434
    count = 123;
2✔
2435
    CHECK_EQUAL(tv.avg(col_rating, &count), 10.);
2✔
2436
    CHECK_EQUAL(count, 1);
2✔
2437
}
2✔
2438

2439

2440
// If number of rows is larger than 8, they can be loaded in chunks by the query system. Test if this works by
2441
// creating a large table with nulls in arbitrary places and query for nulls. Verify the search result manually.
2442
// Do that for all Realm types.
2443
TEST(Query_Null_ManyRows)
2444
{
2✔
2445
    Group g;
2✔
2446
    TableRef table = g.add_table("Inventory");
2✔
2447
    create_columns(table);
2✔
2448

2449
    auto col_price = table->get_column_key("Price");
2✔
2450
    auto col_shipping = table->get_column_key("Shipping");
2✔
2451
    auto col_description = table->get_column_key("Description");
2✔
2452
    auto col_rating = table->get_column_key("Rating");
2✔
2453
    auto col_date = table->get_column_key("Delivery date");
2✔
2454
    Columns<Int> price = table->column<Int>(col_price);
2✔
2455
    Columns<Float> shipping = table->column<Float>(col_shipping);
2✔
2456
    Columns<String> description = table->column<String>(col_description);
2✔
2457
    Columns<Double> rating = table->column<Double>(col_rating);
2✔
2458
    Columns<Bool> stock = table->column<Bool>(table->get_column_key("Stock"));
2✔
2459
    Columns<Timestamp> delivery = table->column<Timestamp>(col_date);
2✔
2460

2461
    // Create lots of non-null rows
2462
    for (size_t t = 0; t < 2000; t++) {
4,002✔
2463
        table->create_object().set_all(123, 30.f, "foo", 12.3, true, Timestamp(1, 2));
4,000✔
2464
    }
4,000✔
2465

2466
    // Reference lists used to verify query results
2467
    std::vector<int64_t> nulls;     // List of rows that have all fields set to null
2✔
2468
    std::vector<int64_t> non_nulls; // List of non-null rows
2✔
2469

2470
    auto all_cols = table->get_column_keys();
2✔
2471

2472
    // Fill in nulls in random rows, at each 10'th row on average
2473
    for (size_t t = 0; t < table->size() / 10; t++) {
402✔
2474
        // Bad but fast random generator
2475
        size_t prime = 883;
400✔
2476
        size_t random = ((t + prime) * prime + t) % table->size();
400✔
2477
        Obj obj = table->get_object(random);
400✔
2478

2479
        // Test if already null (simplest way to avoid dublicates in our nulls vector)
2480
        if (!obj.is_null(col_price)) {
400✔
2481
            for (auto col : all_cols) {
2,800✔
2482
                obj.set_null(col);
2,800✔
2483
            }
2,800✔
2484
            nulls.push_back(obj.get_key().value);
400✔
2485
        }
400✔
2486
    }
400✔
2487

2488
    // Fill out non_nulls vector
2489
    for (auto& o : *table) {
4,000✔
2490
        if (!o.is_null(col_price))
4,000✔
2491
            non_nulls.push_back(o.get_key().value);
3,600✔
2492
    }
4,000✔
2493

2494
    std::sort(nulls.begin(), nulls.end());
2✔
2495
    TableView tv;
2✔
2496

2497
    // Search for nulls and non-nulls and verify matches against our manually created `nulls` and non_nulls vectors.
2498
    // Do that for all Realm data types
2499
    tv = (price == null()).find_all();
2✔
2500
    CHECK(equals(tv, nulls));
2✔
2501

2502
    tv = (price != null()).find_all();
2✔
2503
    CHECK(equals(tv, non_nulls));
2✔
2504

2505
    tv = (shipping == null()).find_all();
2✔
2506
    CHECK(equals(tv, nulls));
2✔
2507

2508
    tv = (shipping != null()).find_all();
2✔
2509
    CHECK(equals(tv, non_nulls));
2✔
2510

2511
    tv = (description == null()).find_all();
2✔
2512
    CHECK(equals(tv, nulls));
2✔
2513

2514
    tv = (description != null()).find_all();
2✔
2515
    CHECK(equals(tv, non_nulls));
2✔
2516

2517
    tv = (rating == null()).find_all();
2✔
2518
    CHECK(equals(tv, nulls));
2✔
2519

2520
    tv = (rating != null()).find_all();
2✔
2521
    CHECK(equals(tv, non_nulls));
2✔
2522

2523
    tv = (stock == null()).find_all();
2✔
2524
    CHECK(equals(tv, nulls));
2✔
2525

2526
    tv = (stock != null()).find_all();
2✔
2527
    CHECK(equals(tv, non_nulls));
2✔
2528

2529
    tv = (delivery == null()).find_all();
2✔
2530
    CHECK(equals(tv, nulls));
2✔
2531

2532
    tv = (delivery != null()).find_all();
2✔
2533
    CHECK(equals(tv, non_nulls));
2✔
2534
}
2✔
2535

2536
TEST(Query_Null_Sort)
2537
{
2✔
2538
    Group g;
2✔
2539
    TableRef table = g.add_table("Inventory");
2✔
2540
    create_columns(table);
2✔
2541

2542
    auto k0 = table->create_object().set_all(0, 0.f, "0", 0.0, false, Timestamp(0, 0)).get_key();
2✔
2543
    auto k1 = table->create_object().get_key();
2✔
2544
    auto k2 = table->create_object().set_all(2, 2.f, "2", 2.0, true, Timestamp(2, 0)).get_key();
2✔
2545

2546
    auto all_cols = table->get_column_keys();
2✔
2547
    for (int i = 0; i <= 5; i++) {
14✔
2548
        TableView tv = table->where().find_all();
12✔
2549
        CHECK(tv.size() == 3);
12✔
2550

2551
        tv.sort(all_cols[i], true);
12✔
2552
        CHECK_EQUAL(tv.get_key(0), k1);
12✔
2553
        CHECK_EQUAL(tv.get_key(1), k0);
12✔
2554
        CHECK_EQUAL(tv.get_key(2), k2);
12✔
2555

2556
        tv = table->where().find_all();
12✔
2557
        tv.sort(all_cols[i], false);
12✔
2558
        CHECK_EQUAL(tv.get_key(0), k2);
12✔
2559
        CHECK_EQUAL(tv.get_key(1), k0);
12✔
2560
        CHECK_EQUAL(tv.get_key(2), k1);
12✔
2561
    }
12✔
2562
}
2✔
2563

2564
TEST(Query_LinkCounts)
2565
{
2✔
2566
    Group group;
2✔
2567
    TableRef table1 = group.add_table("table1");
2✔
2568
    auto col_str = table1->add_column(type_String, "str");
2✔
2569

2570
    auto k0 = table1->create_object().set(col_str, "abc").get_key();
2✔
2571
    auto k1 = table1->create_object().set(col_str, "def").get_key();
2✔
2572
    auto k2 = table1->create_object().set(col_str, "ghi").get_key();
2✔
2573

2574
    TableRef table2 = group.add_table("table2");
2✔
2575
    auto col_int = table2->add_column(type_Int, "int");
2✔
2576
    auto col_link = table2->add_column(*table1, "link");
2✔
2577
    auto col_linklist = table2->add_column_list(*table1, "linklist");
2✔
2578

2579
    table2->create_object().set_all(0);
2✔
2580
    table2->create_object().set_all(1, k1).get_linklist(col_linklist).add(k1);
2✔
2581
    auto ll = table2->create_object().set_all(2, k2).get_linklist(col_linklist);
2✔
2582
    ll.add(k1);
2✔
2583
    ll.add(k2);
2✔
2584

2585
    Query q;
2✔
2586
    ObjKey match;
2✔
2587

2588
    // Verify that queries against the count of a LinkList column work.
2589
    q = table2->column<Link>(col_linklist).count() == 0;
2✔
2590
    match = q.find();
2✔
2591
    CHECK_EQUAL(k0, match);
2✔
2592

2593
    q = table2->column<Link>(col_linklist).count() == 1;
2✔
2594
    match = q.find();
2✔
2595
    CHECK_EQUAL(k1, match);
2✔
2596

2597
    q = table2->column<Link>(col_linklist).count() >= 1;
2✔
2598
    auto tv = q.find_all();
2✔
2599
    CHECK_EQUAL(tv.size(), 2);
2✔
2600
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
2601
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
2602

2603

2604
    // Verify that queries against the count of a Link column work.
2605
    q = table2->column<Link>(col_link).count() == 0;
2✔
2606
    match = q.find();
2✔
2607
    CHECK_EQUAL(k0, match);
2✔
2608

2609
    q = table2->column<Link>(col_link).count() == 1;
2✔
2610
    tv = q.find_all();
2✔
2611
    CHECK_EQUAL(tv.size(), 2);
2✔
2612
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
2613
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
2614

2615
    // Verify that reusing the count expression works.
2616
    auto link_count = table2->column<Link>(col_linklist).count();
2✔
2617
    size_t match_count = (link_count == 0).count();
2✔
2618
    CHECK_EQUAL(1, match_count);
2✔
2619

2620
    match_count = (link_count >= 1).count();
2✔
2621
    CHECK_EQUAL(2, match_count);
2✔
2622

2623
    // Verify that combining the count expression with other queries on the same table works.
2624
    q = table2->column<Link>(col_linklist).count() == 1 && table2->column<Int>(col_int) == 1;
2✔
2625
    match = q.find();
2✔
2626
    CHECK_EQUAL(k1, match);
2✔
2627
}
2✔
2628

2629
struct TestLinkList {
2630
    ColKey add_link_column(TableRef source, TableRef dest)
2631
    {
6✔
2632
        return source->add_column_list(*dest, "linklist");
6✔
2633
    }
6✔
2634
    void create_object_with_links(TableRef table, ColKey col, std::vector<ObjKey> links)
2635
    {
8✔
2636
        LnkLst ll = table->create_object().get_linklist(col);
8✔
2637
        for (auto link : links) {
12✔
2638
            ll.add(link);
12✔
2639
        }
12✔
2640
    }
8✔
2641
    void add_links_to(TableRef table, ColKey col, ObjKey obj, std::vector<ObjKey> links)
2642
    {
12✔
2643
        LnkLst ll = table->get_object(obj).get_linklist(col);
12✔
2644
        for (auto link : links) {
18✔
2645
            ll.add(link);
18✔
2646
        }
18✔
2647
    }
12✔
2648
};
2649

2650

2651
struct TestLinkSet {
2652
    ColKey add_link_column(TableRef source, TableRef dest)
2653
    {
6✔
2654
        return source->add_column_set(*dest, "linkset");
6✔
2655
    }
6✔
2656
    void create_object_with_links(TableRef table, ColKey col, std::vector<ObjKey> links)
2657
    {
8✔
2658
        LnkSet ls = table->create_object().get_linkset(col);
8✔
2659
        for (auto link : links) {
12✔
2660
            ls.insert(link);
12✔
2661
        }
12✔
2662
    }
8✔
2663
    void add_links_to(TableRef table, ColKey col, ObjKey obj, std::vector<ObjKey> links)
2664
    {
12✔
2665
        LnkSet ls = table->get_object(obj).get_linkset(col);
12✔
2666
        for (auto link : links) {
18✔
2667
            ls.insert(link);
18✔
2668
        }
18✔
2669
    }
12✔
2670
};
2671

2672
struct TestDictionaryLinkValues {
2673
    ColKey add_link_column(TableRef source, TableRef dest)
2674
    {
6✔
2675
        return source->add_column_dictionary(*dest, "linkdictionary");
6✔
2676
    }
6✔
2677
    void create_object_with_links(TableRef table, ColKey col, std::vector<ObjKey> links)
2678
    {
8✔
2679
        Dictionary dict = table->create_object().get_dictionary(col);
8✔
2680
        for (auto link : links) {
12✔
2681
            std::string key = util::format("key_%1", keys_added++);
12✔
2682
            dict.insert(Mixed(StringData(key)), Mixed(link));
12✔
2683
        }
12✔
2684
    }
8✔
2685
    void add_links_to(TableRef table, ColKey col, ObjKey obj, std::vector<ObjKey> links)
2686
    {
12✔
2687
        Dictionary dict = table->get_object(obj).get_dictionary(col);
12✔
2688
        for (auto link : links) {
18✔
2689
            std::string key = util::format("key_%1", keys_added++);
18✔
2690
            dict.insert(Mixed(StringData(key)), Mixed(link));
18✔
2691
        }
18✔
2692
    }
12✔
2693

2694
    size_t keys_added = 0;
2695
};
2696

2697
TEST_TYPES(Query_Link_Container_Minimum, TestLinkList, TestLinkSet, TestDictionaryLinkValues)
2698
{
6✔
2699
    Group group;
6✔
2700
    TableRef table1 = group.add_table("table1");
6✔
2701
    auto col_int = table1->add_column(type_Int, "int", /* nullable */ true);
6✔
2702
    auto col_float = table1->add_column(type_Float, "float", /* nullable */ true);
6✔
2703
    auto col_double = table1->add_column(type_Double, "double", /* nullable */ true);
6✔
2704

2705
    // table1
2706
    // 0: 789 789.0f 789.0
2707
    // 1: 456 456.0f 456.0
2708
    // 2: 123 123.0f 123.0
2709
    // 3: null null null
2710

2711
    auto k0 = table1->create_object().set_all(789, 789.f, 789.).get_key();
6✔
2712
    auto k1 = table1->create_object().set_all(456, 456.f, 456.).get_key();
6✔
2713
    auto k2 = table1->create_object().set_all(123, 123.f, 123.).get_key();
6✔
2714
    auto k3 = table1->create_object().get_key();
6✔
2715

2716
    TEST_TYPE test_container;
6✔
2717
    TableRef table2 = group.add_table("table2");
6✔
2718
    ColKey col_linktest = test_container.add_link_column(table2, table1);
6✔
2719

2720
    // table2
2721
    // 0: { }
2722
    // 1: { 1 }
2723
    // 2: { 1, 2 }
2724
    // 3: { 1, 2, 3 }
2725

2726
    test_container.create_object_with_links(table2, col_linktest, {});
6✔
2727
    test_container.create_object_with_links(table2, col_linktest, {k1});
6✔
2728
    test_container.create_object_with_links(table2, col_linktest, {k1, k2});
6✔
2729
    test_container.create_object_with_links(table2, col_linktest, {k1, k2, k3});
6✔
2730

2731
    Query q;
6✔
2732
    TableView tv;
6✔
2733

2734
    q = table2->column<Link>(col_linktest).column<Int>(col_int).min() == 123;
6✔
2735
    tv = q.find_all();
6✔
2736
    CHECK_EQUAL(tv.size(), 2);
6✔
2737
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2738
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2739

2740
    q = table2->column<Link>(col_linktest).column<Int>(col_int).min() == 456;
6✔
2741
    tv = q.find_all();
6✔
2742
    CHECK_EQUAL(tv.size(), 1);
6✔
2743
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2744

2745
    q = table2->column<Link>(col_linktest).column<Int>(col_int).min() == null();
6✔
2746
    tv = q.find_all();
6✔
2747
    CHECK_EQUAL(tv.size(), 1);
6✔
2748
    CHECK_EQUAL(k0, tv.get_key(0));
6✔
2749

2750
    q = table2->column<Link>(col_linktest).column<Float>(col_float).min() == 123.0f;
6✔
2751
    tv = q.find_all();
6✔
2752
    CHECK_EQUAL(tv.size(), 2);
6✔
2753
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2754
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2755

2756
    q = table2->column<Link>(col_linktest).column<Float>(col_float).min() == 456.0f;
6✔
2757
    tv = q.find_all();
6✔
2758
    CHECK_EQUAL(tv.size(), 1);
6✔
2759
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2760

2761
    q = table2->column<Link>(col_linktest).column<Double>(col_double).min() == 123.0;
6✔
2762
    tv = q.find_all();
6✔
2763
    CHECK_EQUAL(tv.size(), 2);
6✔
2764
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2765
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2766

2767
    q = table2->column<Link>(col_linktest).column<Double>(col_double).min() == 456.0;
6✔
2768
    tv = q.find_all();
6✔
2769
    CHECK_EQUAL(tv.size(), 1);
6✔
2770
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2771
}
6✔
2772

2773
TEST_TYPES(Query_Link_MaximumSumAverage, TestLinkList, TestLinkSet, TestDictionaryLinkValues)
2774
{
6✔
2775
    Group group;
6✔
2776
    TableRef table1 = group.add_table("table1");
6✔
2777
    auto col_int = table1->add_column(type_Int, "int", /* nullable */ true);
6✔
2778
    auto col_flt = table1->add_column(type_Float, "float", /* nullable */ true);
6✔
2779
    auto col_dbl = table1->add_column(type_Double, "double", /* nullable */ true);
6✔
2780

2781
    // table1
2782
    // 0: 123 123.0f 123.0
2783
    // 1: 456 456.0f 456.0
2784
    // 2: 789 789.0f 789.0
2785
    // 3: null null null
2786

2787
    ObjKeys keys({3, 5, 7, 9});
6✔
2788
    table1->create_objects(keys);
6✔
2789
    auto it = table1->begin();
6✔
2790
    it->set_all(123, 123.f, 123.);
6✔
2791
    (++it)->set_all(456, 456.f, 456.);
6✔
2792
    (++it)->set_all(789, 789.f, 789.);
6✔
2793

2794
    TEST_TYPE test_container;
6✔
2795
    TableRef table2 = group.add_table("table2");
6✔
2796
    auto col_double = table2->add_column(type_Double, "double");
6✔
2797
    auto col_link = table2->add_column(*table1, "link");
6✔
2798
    ColKey col_linktest = test_container.add_link_column(table2, table1);
6✔
2799

2800
    // table2
2801
    // 0: 456.0 ->0 { }
2802
    // 1: 456.0 ->1 { 1 }
2803
    // 2: 456.0 ->2 { 1, 2 }
2804
    // 3: 456.0 ->3 { 1, 2, 3 }
2805

2806
    auto k0 = table2->create_object().set_all(456.0, keys[0]).get_key();
6✔
2807
    auto k1 = table2->create_object().set_all(456.0, keys[1]).get_key();
6✔
2808
    auto k2 = table2->create_object().set_all(456.0, keys[2]).get_key();
6✔
2809
    auto k3 = table2->create_object().set_all(456.0, keys[3]).get_key();
6✔
2810

2811
    test_container.add_links_to(table2, col_linktest, k0, {});
6✔
2812
    test_container.add_links_to(table2, col_linktest, k1, {keys[1]});
6✔
2813
    test_container.add_links_to(table2, col_linktest, k2, {keys[1], keys[2]});
6✔
2814
    test_container.add_links_to(table2, col_linktest, k3, {keys[1], keys[2], keys[3]});
6✔
2815

2816
    Query q;
6✔
2817
    TableView tv;
6✔
2818

2819
    // Maximum.
2820

2821
    q = table2->column<Link>(col_linktest).column<Int>(col_int).max() == 789;
6✔
2822
    tv = q.find_all();
6✔
2823
    CHECK_EQUAL(tv.size(), 2);
6✔
2824
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2825
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2826

2827
    q = table2->column<Link>(col_linktest).column<Int>(col_int).max() == 456;
6✔
2828
    tv = q.find_all();
6✔
2829
    CHECK_EQUAL(tv.size(), 1);
6✔
2830
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2831

2832
    q = table2->column<Link>(col_linktest).column<Int>(col_int).max() == null();
6✔
2833
    tv = q.find_all();
6✔
2834
    CHECK_EQUAL(tv.size(), 1);
6✔
2835
    CHECK_EQUAL(k0, tv.get_key(0));
6✔
2836

2837
    q = table2->column<Link>(col_linktest).column<Int>(col_int).max() == table2->link(col_link).column<Int>(col_int);
6✔
2838
    tv = q.find_all();
6✔
2839
    CHECK_EQUAL(tv.size(), 2);
6✔
2840
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2841
    CHECK_EQUAL(k2, tv.get_key(1));
6✔
2842

2843
    q = table2->column<Link>(col_linktest).column<Int>(col_int).max() == table2->column<Double>(col_double);
6✔
2844
    tv = q.find_all();
6✔
2845
    CHECK_EQUAL(tv.size(), 1);
6✔
2846
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2847

2848

2849
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).max() == 789.0f;
6✔
2850
    tv = q.find_all();
6✔
2851
    CHECK_EQUAL(tv.size(), 2);
6✔
2852
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2853
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2854

2855
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).max() == 456.0f;
6✔
2856
    tv = q.find_all();
6✔
2857
    CHECK_EQUAL(tv.size(), 1);
6✔
2858
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2859

2860

2861
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).max() == 789.0;
6✔
2862
    tv = q.find_all();
6✔
2863
    CHECK_EQUAL(tv.size(), 2);
6✔
2864
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2865
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2866

2867
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).max() == 456.0;
6✔
2868
    tv = q.find_all();
6✔
2869
    CHECK_EQUAL(tv.size(), 1);
6✔
2870
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2871

2872

2873
    // Sum.
2874
    // Floating point results below may be inexact for some combination of architectures, compilers, and compiler
2875
    // flags.
2876

2877
    q = table2->column<Link>(col_linktest).column<Int>(col_int).sum() == 1245;
6✔
2878
    tv = q.find_all();
6✔
2879
    CHECK_EQUAL(tv.size(), 2);
6✔
2880
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2881
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2882

2883
    q = table2->column<Link>(col_linktest).column<Int>(col_int).sum() == 456;
6✔
2884
    tv = q.find_all();
6✔
2885
    CHECK_EQUAL(tv.size(), 1);
6✔
2886
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2887

2888
    q = table2->column<Link>(col_linktest).column<Int>(col_int).sum() == table2->link(col_link).column<Int>(col_int);
6✔
2889
    tv = q.find_all();
6✔
2890
    CHECK_EQUAL(tv.size(), 1);
6✔
2891
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2892

2893
    q = table2->column<Link>(col_linktest).column<Int>(col_int).sum() == table2->column<Double>(col_double);
6✔
2894
    tv = q.find_all();
6✔
2895
    CHECK_EQUAL(tv.size(), 1);
6✔
2896
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2897

2898

2899
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).sum() == 1245.0f;
6✔
2900
    tv = q.find_all();
6✔
2901
    CHECK_EQUAL(tv.size(), 2);
6✔
2902
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2903
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2904

2905
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).sum() == 456.0f;
6✔
2906
    tv = q.find_all();
6✔
2907
    CHECK_EQUAL(tv.size(), 1);
6✔
2908
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2909

2910

2911
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).sum() == 1245.0;
6✔
2912
    tv = q.find_all();
6✔
2913
    CHECK_EQUAL(tv.size(), 2);
6✔
2914
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2915
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2916

2917
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).sum() == 456.0;
6✔
2918
    tv = q.find_all();
6✔
2919
    CHECK_EQUAL(tv.size(), 1);
6✔
2920
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2921

2922

2923
    // Average.
2924
    // Floating point results below may be inexact for some combination of architectures, compilers, and compiler
2925
    // flags.
2926

2927
    q = table2->column<Link>(col_linktest).column<Int>(col_int).average() == 622.5;
6✔
2928
    tv = q.find_all();
6✔
2929
    CHECK_EQUAL(tv.size(), 2);
6✔
2930
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2931
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2932

2933
    q = table2->column<Link>(col_linktest).column<Int>(col_int).average() == 456;
6✔
2934
    tv = q.find_all();
6✔
2935
    CHECK_EQUAL(tv.size(), 1);
6✔
2936
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2937

2938
    q = table2->column<Link>(col_linktest).column<Int>(col_int).average() == null();
6✔
2939
    tv = q.find_all();
6✔
2940
    CHECK_EQUAL(tv.size(), 1);
6✔
2941
    CHECK_EQUAL(k0, tv.get_key(0));
6✔
2942

2943
    q = table2->column<Link>(col_linktest).column<Int>(col_int).average() <
6✔
2944
        table2->link(col_link).column<Int>(col_int);
6✔
2945
    tv = q.find_all();
6✔
2946
    CHECK_EQUAL(tv.size(), 1);
6✔
2947
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2948

2949
    q = table2->column<Link>(col_linktest).column<Int>(col_int).average() == table2->column<Double>(col_double);
6✔
2950
    tv = q.find_all();
6✔
2951
    CHECK_EQUAL(tv.size(), 1);
6✔
2952
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2953

2954

2955
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).average() == 622.5;
6✔
2956
    tv = q.find_all();
6✔
2957
    CHECK_EQUAL(tv.size(), 2);
6✔
2958
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2959
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2960

2961
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).average() == 456.0f;
6✔
2962
    tv = q.find_all();
6✔
2963
    CHECK_EQUAL(tv.size(), 1);
6✔
2964
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2965

2966

2967
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).average() == 622.5;
6✔
2968
    tv = q.find_all();
6✔
2969
    CHECK_EQUAL(tv.size(), 2);
6✔
2970
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2971
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2972

2973
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).average() == 456.0;
6✔
2974
    tv = q.find_all();
6✔
2975
    CHECK_EQUAL(tv.size(), 1);
6✔
2976
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2977
}
6✔
2978

2979
TEST_TYPES(Query_OperatorsOverLink, TestLinkList, TestLinkSet, TestDictionaryLinkValues)
2980
{
6✔
2981
    Group group;
6✔
2982
    TableRef table1 = group.add_table("table1");
6✔
2983
    table1->add_column(type_Int, "int");
6✔
2984
    table1->add_column(type_Double, "double");
6✔
2985

2986
    // table1
2987
    // 0: 2 2.0
2988
    // 1: 3 3.0
2989

2990
    ObjKeys keys({5, 6});
6✔
2991
    table1->create_objects(keys);
6✔
2992
    table1->get_object(keys[0]).set_all(2, 2.0);
6✔
2993
    table1->get_object(keys[1]).set_all(3, 3.0);
6✔
2994

2995
    TEST_TYPE test_container;
6✔
2996
    TableRef table2 = group.add_table("table2");
6✔
2997
    table2->add_column(type_Int, "int");
6✔
2998
    ColKey col_linktest = test_container.add_link_column(table2, table1);
6✔
2999

3000
    // table2
3001
    // 0:  0 { }
3002
    // 1:  4 { 0 }
3003
    // 2:  4 { 1, 0 }
3004

3005
    table2->create_object();
6✔
3006
    auto k1 = table2->create_object().set_all(4).get_key();
6✔
3007
    auto k2 = table2->create_object().set_all(4).get_key();
6✔
3008

3009
    test_container.add_links_to(table2, col_linktest, k1, {keys[0]});
6✔
3010
    test_container.add_links_to(table2, col_linktest, k2, {keys[1], keys[0]});
6✔
3011

3012
    Query q;
6✔
3013
    TableView tv;
6✔
3014

3015
    // Binary operators.
3016

3017
    // Rows 1 and 2 should match this query as 2 * 2 == 4.
3018
    // Row 0 should not as the multiplication will not produce any results.
3019
    std::string link_prop = table2->get_column_name(col_linktest);
6✔
3020
    q = table2->query(link_prop + ".int * 2 == int");
6✔
3021
    tv = q.find_all();
6✔
3022
    CHECK_EQUAL(tv.size(), 2);
6✔
3023
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
3024
    CHECK_EQUAL(k2, tv.get_key(1));
6✔
3025

3026
    // Rows 1 and 2 should match this query as 2 * 2 == 4.
3027
    // Row 0 should not as the multiplication will not produce any results.
3028
    q = table2->query("int == 2 * " + link_prop + ".int");
6✔
3029
    tv = q.find_all();
6✔
3030
    CHECK_EQUAL(tv.size(), 2);
6✔
3031
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
3032
    CHECK_EQUAL(k2, tv.get_key(1));
6✔
3033

3034
    // Rows 1 and 2 should match this query as 2.0 * 2.0 == 4.0.
3035
    // Row 0 should not as the multiplication will not produce any results.
3036
    q = table2->query(link_prop + ".double * 2 == int");
6✔
3037
    tv = q.find_all();
6✔
3038
    CHECK_EQUAL(tv.size(), 2);
6✔
3039
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
3040
    CHECK_EQUAL(k2, tv.get_key(1));
6✔
3041

3042
    // Rows 1 and 2 should match this query as 2.0 * 2.0 == 4.0.
3043
    // Row 0 should not as the multiplication will not produce any results.
3044
    q = table2->query("int == 2 * " + link_prop + ".double");
6✔
3045
    tv = q.find_all();
6✔
3046
    CHECK_EQUAL(tv.size(), 2);
6✔
3047
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
3048
    CHECK_EQUAL(k2, tv.get_key(1));
6✔
3049
}
6✔
3050

3051
TEST(Query_CompareLinkedColumnVsColumn)
3052
{
2✔
3053
    Group group;
2✔
3054
    TableRef table1 = group.add_table("table1");
2✔
3055
    auto col_int = table1->add_column(type_Int, "int");
2✔
3056
    auto col_dbl = table1->add_column(type_Double, "double");
2✔
3057

3058
    // table1
3059
    // 0: 2 2.0
3060
    // 1: 3 3.0
3061

3062
    ObjKeys keys({5, 6});
2✔
3063
    table1->create_objects(keys);
2✔
3064
    table1->get_object(keys[0]).set_all(2, 2.0);
2✔
3065
    table1->get_object(keys[1]).set_all(3, 3.0);
2✔
3066

3067
    TableRef table2 = group.add_table("table2");
2✔
3068
    auto col_int2 = table2->add_column(type_Int, "int");
2✔
3069
    auto col_link1 = table2->add_column(*table1, "link1");
2✔
3070
    auto col_link2 = table2->add_column(*table1, "link2");
2✔
3071

3072
    // table2
3073
    // 0: 2 {   } { 0 }
3074
    // 1: 4 { 0 } { 1 }
3075
    // 2: 4 { 1 } {   }
3076

3077
    auto k0 = table2->create_object().set_all(2, null(), keys[0]).get_key();
2✔
3078
    auto k1 = table2->create_object().set_all(4, keys[0], keys[1]).get_key();
2✔
3079
    auto k2 = table2->create_object().set_all(4, keys[1], null()).get_key();
2✔
3080

3081
    Query q;
2✔
3082
    TableView tv;
2✔
3083

3084
    q = table2->link(col_link1).column<Int>(col_int) < table2->column<Int>(col_int2);
2✔
3085
    tv = q.find_all();
2✔
3086
    CHECK_EQUAL(tv.size(), 2);
2✔
3087
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
3088
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
3089

3090
    q = table2->link(col_link1).column<Double>(col_dbl) < table2->column<Int>(col_int2);
2✔
3091
    tv = q.find_all();
2✔
3092
    CHECK_EQUAL(tv.size(), 2);
2✔
3093
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
3094
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
3095

3096
    q = table2->link(col_link2).column<Int>(col_int) == table2->column<Int>(col_int2);
2✔
3097
    tv = q.find_all();
2✔
3098
    CHECK_EQUAL(tv.size(), 1);
2✔
3099
    CHECK_EQUAL(k0, tv.get_key(0));
2✔
3100
}
2✔
3101

3102
TEST(Query_CompareThroughUnaryLinks)
3103
{
2✔
3104
    Group group;
2✔
3105
    TableRef table1 = group.add_table("table1");
2✔
3106
    auto col_int = table1->add_column(type_Int, "int");
2✔
3107
    auto col_dbl = table1->add_column(type_Double, "double");
2✔
3108
    auto col_str = table1->add_column(type_String, "string");
2✔
3109

3110
    // table1
3111
    // 0: 2 2.0 "abc"
3112
    // 1: 3 3.0 "def"
3113
    // 2: 8 8.0 "def"
3114

3115
    ObjKeys keys({5, 6, 7});
2✔
3116
    table1->create_objects(keys);
2✔
3117
    table1->get_object(keys[0]).set_all(2, 2.0, "abc");
2✔
3118
    table1->get_object(keys[1]).set_all(3, 3.0, "def");
2✔
3119
    table1->get_object(keys[2]).set_all(8, 8.0, "def");
2✔
3120

3121
    TableRef table2 = group.add_table("table2");
2✔
3122
    auto col_link1 = table2->add_column(*table1, "link1");
2✔
3123
    auto col_link2 = table2->add_column(*table1, "link2");
2✔
3124

3125
    // table2
3126
    // 0: {   } { 0 }
3127
    // 1: { 0 } { 1 }
3128
    // 2: { 1 } { 2 }
3129
    // 3: { 2 } {   }
3130

3131
    table2->create_object().set_all(null(), keys[0]).get_key();
2✔
3132
    auto k1 = table2->create_object().set_all(keys[0], keys[1]).get_key();
2✔
3133
    auto k2 = table2->create_object().set_all(keys[1], keys[2]).get_key();
2✔
3134
    table2->create_object().set_all(keys[2], null()).get_key();
2✔
3135

3136
    Query q;
2✔
3137
    TableView tv;
2✔
3138

3139
    q = table2->link(col_link1).column<Int>(col_int) < table2->link(col_link2).column<Int>(col_int);
2✔
3140
    tv = q.find_all();
2✔
3141
    CHECK_EQUAL(tv.size(), 2);
2✔
3142
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
3143
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
3144

3145
    q = table2->link(col_link1).column<Double>(col_dbl) < table2->link(col_link2).column<Double>(col_dbl);
2✔
3146
    tv = q.find_all();
2✔
3147
    CHECK_EQUAL(tv.size(), 2);
2✔
3148
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
3149
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
3150

3151
    q = table2->link(col_link1).column<String>(col_str) == table2->link(col_link2).column<String>(col_str);
2✔
3152
    tv = q.find_all();
2✔
3153
    CHECK_EQUAL(tv.size(), 1);
2✔
3154
    CHECK_EQUAL(k2, tv.get_key(0));
2✔
3155
}
2✔
3156

3157
TEST(Query_DeepLink)
3158
{
2✔
3159

3160
    //
3161
    // +---------+--------+------------+
3162
    // | int     | bool   | list       |
3163
    // +---------+--------+------------+
3164
    // |       0 | true   | null       |
3165
    // |       1 | false  | 0          |
3166
    // |       2 | true   | 0, 1       |
3167
    // |       N | even(N)| 0, .., N-1 |
3168
    // +---------+--------+-------------+
3169

3170
    const int N = 10;
2✔
3171

3172
    Group group;
2✔
3173
    TableRef table = group.add_table("test");
2✔
3174
    table->add_column(type_Int, "int");
2✔
3175
    auto col_bool = table->add_column(type_Bool, "bool");
2✔
3176
    auto col_linklist = table->add_column_list(*table, "list");
2✔
3177

3178
    for (int j = 0; j < N; ++j) {
22✔
3179
        TableView view = table->where().find_all();
20✔
3180

3181
        Obj obj = table->create_object().set_all(j, (j % 2) == 0);
20✔
3182
        auto ll = obj.get_linklist(col_linklist);
20✔
3183
        for (size_t i = 0; i < view.size(); ++i) {
110✔
3184
            ll.add(view.get_key(i));
90✔
3185
        }
90✔
3186
    }
20✔
3187

3188
    Query query = table->link(col_linklist).column<Bool>(col_bool) == true;
2✔
3189
    TableView view = query.find_all();
2✔
3190
    CHECK_EQUAL(N - 1, view.size());
2✔
3191
}
2✔
3192

3193
TEST(Query_LinksToDeletedOrMovedRow)
3194
{
2✔
3195
    // This test is not that relevant with stable keys
3196
    Group group;
2✔
3197

3198
    TableRef source = group.add_table("source");
2✔
3199
    TableRef target = group.add_table("target");
2✔
3200

3201
    auto col_link = source->add_column(*target, "link");
2✔
3202
    auto col_name = target->add_column(type_String, "name");
2✔
3203

3204
    ObjKeys keys({4, 6, 8});
2✔
3205
    target->create_objects(keys);
2✔
3206
    target->get_object(keys[0]).set(col_name, "A");
2✔
3207
    target->get_object(keys[1]).set(col_name, "B");
2✔
3208
    target->get_object(keys[2]).set(col_name, "C");
2✔
3209

3210
    source->create_object().set(col_link, keys[0]);
2✔
3211
    source->create_object().set(col_link, keys[1]).get_key();
2✔
3212
    source->create_object().set(col_link, keys[2]);
2✔
3213

3214
    Query qA = source->column<Link>(col_link) == target->get_object(keys[0]);
2✔
3215
    Query qB = source->column<Link>(col_link) == target->get_object(keys[1]);
2✔
3216
    Query qC = source->column<Link>(col_link) == target->get_object(keys[2]);
2✔
3217

3218
    // Remove first object
3219
    target->remove_object(keys[0]);
2✔
3220

3221
    // Row A should not be found as it has been removed.
3222
    TableView tvA = qA.find_all();
2✔
3223
    CHECK_EQUAL(0, tvA.size());
2✔
3224

3225
    // Row B should be found as it was not changed.
3226
    TableView tvB = qB.find_all();
2✔
3227
    CHECK_EQUAL(1, tvB.size());
2✔
3228
    CHECK_EQUAL(keys[1], tvB[0].get<ObjKey>(col_link));
2✔
3229
    CHECK_EQUAL("B", tvB.get_object(0).get_linked_object(col_link).get<String>(col_name));
2✔
3230

3231
    // Row C should still be found
3232
    TableView tvC = qC.find_all();
2✔
3233
    CHECK_EQUAL(1, tvC.size());
2✔
3234
    CHECK_EQUAL(keys[2], tvC[0].get<ObjKey>(col_link));
2✔
3235
    CHECK_EQUAL("C", tvC.get_object(0).get_linked_object(col_link).get<String>(col_name));
2✔
3236
}
2✔
3237

3238
// Triggers bug in compare_relation()
3239
TEST(Query_BrokenFindGT)
3240
{
2✔
3241
    Group group;
2✔
3242
    TableRef table = group.add_table("test");
2✔
3243
    auto col = table->add_column(type_Int, "int");
2✔
3244

3245
    const size_t rows = 12;
2✔
3246
    for (size_t i = 0; i < rows; ++i) {
26✔
3247
        table->create_object().set(col, int64_t(i + 2));
24✔
3248
    }
24✔
3249

3250
    table->create_object().set(col, 1);
2✔
3251
    table->create_object().set(col, 1);
2✔
3252
    table->create_object().set(col, 1);
2✔
3253

3254
    for (size_t i = 0; i < 3; ++i) {
8✔
3255
        table->create_object().set(col, int64_t(i + 2));
6✔
3256
    }
6✔
3257

3258
    CHECK_EQUAL(18, table->size());
2✔
3259

3260
    Query q = table->where().greater(col, 1);
2✔
3261
    TableView tv = q.find_all();
2✔
3262
    CHECK_EQUAL(15, tv.size());
2✔
3263

3264
    for (size_t i = 0; i < tv.size(); ++i) {
32✔
3265
        CHECK_NOT_EQUAL(1, tv[i].get<Int>(col));
30✔
3266
    }
30✔
3267
}
2✔
3268

3269
// Small fuzzy test also to trigger bugs such as the compare_relation() bug above
3270
TEST(Query_FuzzyFind)
3271
{
2✔
3272
    // TEST_DURATION is normally 0.
3273
    for (size_t iter = 0; iter < 50 + TEST_DURATION * 2000; iter++) {
102✔
3274
        Group group;
100✔
3275
        TableRef table = group.add_table("test");
100✔
3276
        auto col = table->add_column(type_Int, "int");
100✔
3277

3278
        // The bug happened when values were stored in 4 bits or less. So create a table full of such random values
3279
        const size_t rows = 18;
100✔
3280
        for (size_t i = 0; i < rows; ++i) {
1,900✔
3281
            // Produce numbers -3 ... 17. Just to test edge cases around 4-bit values also
3282
            int64_t t = (fastrand() % 21) - 3;
1,800✔
3283

3284
            table->create_object().set(col, t);
1,800✔
3285
        }
1,800✔
3286

3287
        for (int64_t s = -2; s < 18; s++) {
2,100✔
3288
            Query q_g = table->where().greater(col, s);
2,000✔
3289
            TableView tv_g = q_g.find_all();
2,000✔
3290
            for (size_t i = 0; i < tv_g.size(); ++i) {
18,370✔
3291
                CHECK(tv_g[i].get<Int>(col) > s);
16,370✔
3292
            }
16,370✔
3293

3294
            Query q_l = table->where().less(col, s);
2,000✔
3295
            TableView tv_l = q_l.find_all();
2,000✔
3296
            for (size_t i = 0; i < tv_l.size(); ++i) {
19,901✔
3297
                CHECK(tv_l[i].get<Int>(col) < s);
17,901✔
3298
            }
17,901✔
3299

3300
            Query q_le = table->where().less_equal(col, s);
2,000✔
3301
            TableView tv_le = q_le.find_all();
2,000✔
3302
            for (size_t i = 0; i < tv_le.size(); ++i) {
21,630✔
3303
                CHECK(tv_le[i].get<Int>(col) <= s);
19,630✔
3304
            }
19,630✔
3305

3306
            // Sum of values greater + less-or-equal should be total number of rows. This ensures that both
3307
            // 1) no search results are *omitted* from find_all(), and no 2) results are *false* positives
3308
            CHECK(tv_g.size() + tv_le.size() == rows);
2,000✔
3309
        }
2,000✔
3310
    }
100✔
3311
}
2✔
3312

3313
TEST(Query_AverageNullableColumns)
3314
{
2✔
3315
    Table table;
2✔
3316
    auto col_int = table.add_column(type_Int, "int", true);
2✔
3317
    auto col_float = table.add_column(type_Float, "float", true);
2✔
3318
    auto col_double = table.add_column(type_Double, "double", true);
2✔
3319

3320
    CHECK(table.where().avg(col_int)->is_null());
2✔
3321
    CHECK(table.where().avg(col_float)->is_null());
2✔
3322
    CHECK(table.where().avg(col_double)->is_null());
2✔
3323

3324
    //
3325
    // +-----+-------+--------+
3326
    // | int | float | double |
3327
    // +-----+-------+--------+
3328
    // |   2 |     2 |      2 |
3329
    // |   4 |     4 |      4 |
3330
    // +-----+-------+--------+
3331

3332
    table.create_object().set_all(2, 2.0f, 2.0);
2✔
3333
    table.create_object().set_all(4, 4.0f, 4.0);
2✔
3334

3335
    CHECK_EQUAL(3, table.where().avg(col_int));
2✔
3336
    CHECK_EQUAL(3, table.where().avg(col_float));
2✔
3337
    CHECK_EQUAL(3, table.where().avg(col_double));
2✔
3338

3339
    // Add a row with nulls in each column. These nulls must be treated as not existing, that is,
3340
    // it must be such that the average of 2 + 2 + null == 2.
3341
    table.create_object();
2✔
3342

3343
    CHECK_EQUAL(3, table.where().avg(col_int));
2✔
3344
    CHECK_EQUAL(3, table.where().avg(col_float));
2✔
3345
    CHECK_EQUAL(3, table.where().avg(col_double));
2✔
3346
}
2✔
3347

3348
TEST(Query_NegativeNumbers)
3349
{
2✔
3350
    for (size_t nullable = 0; nullable < 2; ++nullable) {
6✔
3351
        Group group;
4✔
3352
        TableRef table = group.add_table("test");
4✔
3353
        auto c0 = table->add_column(type_Int, "int", nullable == 0);
4✔
3354

3355
        int64_t id = -1;
4✔
3356
        for (size_t i = 0; i < 10; ++i) {
44✔
3357
            table->create_object().set_all(id--);
40✔
3358
        }
40✔
3359

3360
        CHECK_EQUAL(10, table->where().between(c0, -10, -1).find_all().size());
4✔
3361
        CHECK_EQUAL(10, (table->column<Int>(c0) > -11).find_all().size());
4✔
3362
        CHECK_EQUAL(10, table->where().greater(c0, -11).find_all().size());
4✔
3363
        CHECK_EQUAL(10, (table->column<Int>(c0) >= -10).find_all().size());
4✔
3364
        CHECK_EQUAL(10, table->where().greater_equal(c0, -10).find_all().size());
4✔
3365
        CHECK_EQUAL(10, (table->column<Int>(c0) < 128).find_all().size());
4✔
3366
        CHECK_EQUAL(10, table->where().less(c0, 128).find_all().size());
4✔
3367
        CHECK_EQUAL(10, (table->column<Int>(c0) < 127).find_all().size());
4✔
3368
        CHECK_EQUAL(10, table->where().less(c0, 127).find_all().size());
4✔
3369
        CHECK_EQUAL(10, (table->column<Int>(c0) <= -1).find_all().size());
4✔
3370
        CHECK_EQUAL(10, table->where().less_equal(c0, -1).find_all().size());
4✔
3371
        CHECK_EQUAL(10, (table->column<Int>(c0) < 0).find_all().size());
4✔
3372
        TableView view = table->where().less(c0, 0).find_all();
4✔
3373
        CHECK_EQUAL(10, view.size());
4✔
3374

3375
        id = -1;
4✔
3376
        for (size_t i = 0; i < view.size(); ++i) {
44✔
3377
            if (nullable == 0) {
40✔
3378
                CHECK_EQUAL(id, view.get_object(i).get<Optional<Int>>(c0));
20✔
3379
            }
20✔
3380
            else {
20✔
3381
                CHECK_EQUAL(id, view.get_object(i).get<Int>(c0));
20✔
3382
            }
20✔
3383
            id--;
40✔
3384
        }
40✔
3385
    }
4✔
3386
}
2✔
3387

3388
template <class T>
3389
int64_t unbox(const T& val)
3390
{
2,020✔
3391
    return val;
2,020✔
3392
}
2,020✔
3393

3394
template <>
3395
int64_t unbox(const util::Optional<int64_t>& val)
3396
{
2,020✔
3397
    return *val;
2,020✔
3398
}
2,020✔
3399

3400
TEST_TYPES(Query_EqualityInts, int64_t, util::Optional<int64_t>)
3401
{
4✔
3402
    Group group;
4✔
3403
    TableRef table = group.add_table("test");
4✔
3404
    auto col_ndx = table->add_column(type_Int, "int", std::is_same<TEST_TYPE, util::Optional<int64_t>>::value);
4✔
3405

3406
    int64_t id = -1;
4✔
3407
    int64_t sum = 0;
4✔
3408
    constexpr static size_t num_rows = REALM_MAX_BPNODE_SIZE + 10;
4✔
3409
    for (size_t i = 0; i < num_rows; ++i) {
4,044✔
3410
        sum += id;
4,040✔
3411
        table->create_object().set<Int>(col_ndx, id++);
4,040✔
3412
    }
4,040✔
3413

3414
    bool first = true;
4✔
3415
    for (auto& obj : *table) {
4,040✔
3416
        int64_t target = unbox(obj.get<TEST_TYPE>(col_ndx));
4,040✔
3417
        Query q_eq = table->where().equal(col_ndx, target);
4,040✔
3418
        CHECK_EQUAL(q_eq.find(), obj.get_key());
4,040✔
3419
        CHECK_EQUAL(q_eq.count(), 1);
4,040✔
3420
        CHECK_EQUAL(q_eq.sum(col_ndx), target);
4,040✔
3421
        CHECK_EQUAL(q_eq.avg(col_ndx), target);
4,040✔
3422

3423
        Query q_neq = table->where().not_equal(col_ndx, target);
4,040✔
3424
        CHECK_EQUAL(q_neq.find(), first ? ObjKey(1) : ObjKey(0));
4,040✔
3425
        CHECK_EQUAL(q_neq.count(), num_rows - 1);
4,040✔
3426
        CHECK_EQUAL(q_neq.sum(col_ndx), sum - target);
4,040✔
3427
        CHECK_EQUAL(q_neq.avg(col_ndx), (sum - target) / double(num_rows - 1));
4,040✔
3428
        first = false;
4,040✔
3429
    }
4,040✔
3430
}
4✔
3431

3432
// Exposes bug that would lead to nulls being included as 0 value in average when performed
3433
// on Query. When performed on TableView or Table, it worked OK.
3434
TEST(Query_MaximumSumAverage)
3435
{
2✔
3436
    for (int nullable = 0; nullable < 2; nullable++) {
6✔
3437
        bool n = (nullable == 1);
4✔
3438
        Group group;
4✔
3439
        TableRef table1 = group.add_table("table1");
4✔
3440
        ColKey c0 = table1->add_column(type_Int, "int1", /* nullable */ n);
4✔
3441
        ColKey c1 = table1->add_column(type_Int, "int2", /* nullable */ n);
4✔
3442
        ColKey c2 = table1->add_column(type_Double, "d", /* nullable */ n);
4✔
3443

3444
        // Create three identical columns with values: For the nullable case:
3445
        //      3, 4, null
3446
        // For non-nullable iteration:
3447
        //      3, 4
3448

3449
        table1->create_object().set_all(3, 3, 3.0);
4✔
3450
        table1->create_object().set_all(4, 4, 4.0);
4✔
3451
        if (n)
4✔
3452
            table1->create_object();
2✔
3453

3454
        // Average
3455
        {
4✔
3456
            double d;
4✔
3457

3458
            // Those that have criterias include all rows, also those with null
3459
            d = table1->where().avg(c0)->get_double();
4✔
3460
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3461

3462
            d = table1->where().avg(c1)->get_double();
4✔
3463
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3464

3465
            // Criteria on same column as average
3466
            d = table1->where().not_equal(c0, 1234).avg(c0)->get_double();
4✔
3467
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3468

3469
            // Criteria on other column than average (triggers different code paths)
3470
            d = table1->where().not_equal(c0, 1234).avg(c1)->get_double();
4✔
3471
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3472

3473
            // Average of double, criteria on integer
3474
            d = table1->where().not_equal(c0, 1234).avg(c2)->get_double();
4✔
3475
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3476

3477
            d = table1->where().not_equal(c2, 1234.).avg(c2)->get_double();
4✔
3478
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3479

3480
            CHECK((table1->column<Int>(c0) == null()).avg(c0)->is_null());
4✔
3481

3482
            d = (table1->column<Int>(c0) != null()).avg(c0)->get_double();
4✔
3483
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3484

3485
            // Those with criteria now only include some rows, whereof none are null
3486
            d = table1->where().avg(c0)->get_double();
4✔
3487
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3488

3489
            d = table1->where().avg(c1)->get_double();
4✔
3490
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3491

3492
            // Criteria on same column as average
3493
            d = table1->where().equal(c0, 3).avg(c0)->get_double();
4✔
3494
            CHECK_APPROXIMATELY_EQUAL(d, 3., 0.001);
4✔
3495

3496
            // Criteria on other column than average (triggers different code paths)
3497
            d = table1->where().equal(c0, 3).avg(c1)->get_double();
4✔
3498
            CHECK_APPROXIMATELY_EQUAL(d, 3., 0.001);
4✔
3499

3500
            // Average of double, criteria on integer
3501
            d = table1->where().not_equal(c0, 3).avg(c2)->get_double();
4✔
3502
            CHECK_APPROXIMATELY_EQUAL(d, 4., 0.001);
4✔
3503

3504
            d = table1->where().equal(c2, 3.).avg(c2)->get_double();
4✔
3505
            CHECK_APPROXIMATELY_EQUAL(d, 3., 0.001);
4✔
3506

3507
            // Now using null as criteria
3508
            d = (table1->column<Int>(c0) != null()).avg(c2)->get_double();
4✔
3509
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3510

3511
            d = (table1->column<Double>(c2) != null()).avg(c2)->get_double();
4✔
3512
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3513

3514
            d = (table1->column<Int>(c0) != null()).avg(c0)->get_double();
4✔
3515
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3516

3517
            d = (table1->column<Int>(c1) != null()).avg(c0)->get_double();
4✔
3518
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3519
        }
4✔
3520

3521

3522
        // Maximum
3523
        {
4✔
3524
            std::optional<Mixed> m;
4✔
3525
            // Those that have criterias include all rows, also those with null
3526
            m = table1->where().max(c0);
4✔
3527
            CHECK_EQUAL(m, 4);
4✔
3528

3529
            m = table1->where().max(c1);
4✔
3530
            CHECK_EQUAL(m, 4);
4✔
3531

3532
            // Criteria on same column as maximum
3533
            m = table1->where().not_equal(c0, 1234).max(c0);
4✔
3534
            CHECK_EQUAL(m, 4);
4✔
3535

3536
            // Criteria on other column than maximum (triggers different code paths)
3537
            m = table1->where().not_equal(c0, 1234).max(c1);
4✔
3538
            CHECK_EQUAL(m, 4);
4✔
3539

3540
            // Average of double, criteria on integer
3541
            m = table1->where().not_equal(c0, 1234).max(c2);
4✔
3542
            CHECK_EQUAL(m, 4);
4✔
3543

3544
            m = table1->where().not_equal(c2, 1234.).max(c2);
4✔
3545
            CHECK_EQUAL(m, 4.);
4✔
3546

3547
            // Those with criteria now only include some rows, whereof none are null
3548
            m = table1->where().max(c0);
4✔
3549
            CHECK_EQUAL(m, 4);
4✔
3550

3551
            m = table1->where().max(c1);
4✔
3552
            CHECK_EQUAL(m, 4);
4✔
3553

3554
            // Criteria on same column as maximum
3555
            m = table1->where().equal(c0, 4).max(c0);
4✔
3556
            CHECK_EQUAL(m, 4);
4✔
3557

3558
            // Criteria on other column than maximum (triggers different code paths)
3559
            m = table1->where().equal(c0, 4).max(c1);
4✔
3560
            CHECK_EQUAL(m, 4);
4✔
3561

3562
            // Average of double, criteria on integer
3563
            m = table1->where().not_equal(c0, 3).max(c2);
4✔
3564
            CHECK_EQUAL(m, 4.);
4✔
3565

3566
            m = table1->where().equal(c2, 3.).max(c2);
4✔
3567
            CHECK_EQUAL(m, 3.);
4✔
3568

3569
            // Now using null as criteria
3570
            m = (table1->column<Int>(c0) != null()).max(c2);
4✔
3571
            CHECK_EQUAL(m, 4.);
4✔
3572

3573
            m = (table1->column<Double>(c2) != null()).max(c2);
4✔
3574
            CHECK_EQUAL(m, 4.);
4✔
3575

3576
            m = (table1->column<Int>(c0) != null()).max(c0);
4✔
3577
            CHECK_EQUAL(m, 4);
4✔
3578

3579
            m = (table1->column<Int>(c1) != null()).max(c0);
4✔
3580
            CHECK_EQUAL(m, 4);
4✔
3581
        }
4✔
3582

3583

3584
        // Minimum
3585
        {
4✔
3586
            std::optional<Mixed> m;
4✔
3587
            // Those that have criterias include all rows, also those with null
3588
            m = table1->where().min(c0);
4✔
3589
            CHECK_EQUAL(m, 3);
4✔
3590

3591
            m = table1->where().min(c1);
4✔
3592
            CHECK_EQUAL(m, 3);
4✔
3593

3594
            // Criteria on same column as minimum
3595
            m = table1->where().not_equal(c0, 1234).min(c0);
4✔
3596
            CHECK_EQUAL(m, 3);
4✔
3597

3598
            // Criteria on other column than minimum (triggers different code paths)
3599
            m = table1->where().not_equal(c0, 1234).min(c1);
4✔
3600
            CHECK_EQUAL(m, 3);
4✔
3601

3602
            // Average of double, criteria on integer
3603
            m = table1->where().not_equal(c0, 1234).min(c2);
4✔
3604
            CHECK_EQUAL(m, 3);
4✔
3605

3606
            m = table1->where().not_equal(c2, 1234.).min(c2);
4✔
3607
            CHECK_EQUAL(m, 3.);
4✔
3608

3609

3610
            // Those with criteria now only include some rows, whereof none are null
3611
            m = table1->where().min(c0);
4✔
3612
            CHECK_EQUAL(m, 3);
4✔
3613

3614
            m = table1->where().min(c1);
4✔
3615
            CHECK_EQUAL(m, 3);
4✔
3616

3617
            // Criteria on same column as minimum
3618
            m = table1->where().equal(c0, 4).min(c0);
4✔
3619
            CHECK_EQUAL(m, 4);
4✔
3620

3621
            // Criteria on other column than minimum (triggers different code paths)
3622
            m = table1->where().equal(c0, 4).min(c1);
4✔
3623
            CHECK_EQUAL(m, 4);
4✔
3624

3625
            // Average of double, criteria on integer
3626
            m = table1->where().not_equal(c0, 3).min(c2);
4✔
3627
            CHECK_EQUAL(m, 4.);
4✔
3628

3629
            m = table1->where().equal(c2, 3.).min(c2);
4✔
3630
            CHECK_EQUAL(m, 3.);
4✔
3631

3632
            // Now using null as criteria
3633
            m = (table1->column<Int>(c0) != null()).min(c2);
4✔
3634
            CHECK_EQUAL(m, 3.);
4✔
3635

3636
            m = (table1->column<Double>(c2) != null()).min(c2);
4✔
3637
            CHECK_EQUAL(m, 3.);
4✔
3638

3639
            m = (table1->column<Int>(c0) != null()).min(c0);
4✔
3640
            CHECK_EQUAL(m, 3);
4✔
3641

3642
            m = (table1->column<Int>(c1) != null()).min(c0);
4✔
3643
            CHECK_EQUAL(m, 3);
4✔
3644
        }
4✔
3645

3646
        // Sum
3647
        {
4✔
3648
            std::optional<Mixed> m;
4✔
3649
            // Those that have criterias include all rows, also those with null
3650
            m = table1->where().sum(c0);
4✔
3651
            CHECK_EQUAL(m, 7);
4✔
3652

3653
            // Criteria on same column as maximum
3654
            m = table1->where().not_equal(c0, 1234).sum(c0);
4✔
3655
            CHECK_EQUAL(m, 7);
4✔
3656

3657
            // Criteria on other column than maximum (triggers different code paths)
3658
            m = table1->where().not_equal(c0, 1234).sum(c1);
4✔
3659
            CHECK_EQUAL(m, 7);
4✔
3660

3661
            m = (table1->column<Int>(c0) == null()).sum(c0);
4✔
3662
            CHECK_EQUAL(m, 0);
4✔
3663

3664
            m = (table1->column<Int>(c0) != null()).sum(c0);
4✔
3665
            CHECK_EQUAL(m, 7);
4✔
3666

3667
            // Average of double, criteria on integer
3668
            m = table1->where().not_equal(c0, 1234).sum(c2);
4✔
3669
            CHECK_EQUAL(m, 7.);
4✔
3670

3671
            m = table1->where().not_equal(c2, 1234.).sum(c2);
4✔
3672
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 7., 0.001);
4✔
3673

3674

3675
            // Those with criteria now only include some rows, whereof none are null
3676
            m = table1->where().sum(c0);
4✔
3677
            CHECK_EQUAL(m, 7);
4✔
3678

3679
            m = table1->where().sum(c1);
4✔
3680
            CHECK_EQUAL(m, 7);
4✔
3681

3682
            // Criteria on same column as maximum
3683
            m = table1->where().equal(c0, 4).sum(c0);
4✔
3684
            CHECK_EQUAL(m, 4);
4✔
3685

3686
            // Criteria on other column than maximum (triggers different code paths)
3687
            m = table1->where().equal(c0, 4).sum(c1);
4✔
3688
            CHECK_EQUAL(m, 4);
4✔
3689

3690
            // Average of double, criteria on integer
3691
            m = table1->where().not_equal(c0, 3).sum(c2);
4✔
3692
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 4., 0.001);
4✔
3693

3694
            m = table1->where().equal(c2, 3.).sum(c2);
4✔
3695
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 3., 0.001);
4✔
3696

3697
            // Now using null as criteria
3698
            m = (table1->column<Int>(c0) != null()).sum(c2);
4✔
3699
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 7., 0.001);
4✔
3700

3701
            m = (table1->column<Double>(c2) != null()).sum(c2);
4✔
3702
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 7., 0.001);
4✔
3703

3704
            m = (table1->column<Int>(c0) != null()).sum(c0);
4✔
3705
            CHECK_EQUAL(m, 7);
4✔
3706

3707
            m = (table1->column<Int>(c1) != null()).sum(c0);
4✔
3708
            CHECK_EQUAL(m, 7);
4✔
3709
        }
4✔
3710

3711

3712
        // Count
3713
        {
4✔
3714
            int64_t d;
4✔
3715
            d = table1->where().count();
4✔
3716
            CHECK_EQUAL(d, n ? 3 : 2);
4✔
3717

3718
            d = table1->where().not_equal(c0, 1234).count();
4✔
3719
            CHECK_EQUAL(d, n ? 3 : 2);
4✔
3720

3721
            d = table1->where().equal(c0, 4).count();
4✔
3722
            CHECK_EQUAL(d, 1);
4✔
3723

3724
            d = table1->where().not_equal(c0, 3).count();
4✔
3725
            CHECK_EQUAL(d, n ? 2 : 1);
4✔
3726

3727
            d = table1->where().equal(c2, 3.).count();
4✔
3728
            CHECK_EQUAL(d, 1);
4✔
3729

3730
            // Now using null as criteria
3731
            d = (table1->column<Int>(c0) != null()).count();
4✔
3732
            CHECK_EQUAL(d, 2);
4✔
3733

3734
            d = (table1->column<Double>(c2) != null()).count();
4✔
3735
            CHECK_EQUAL(d, 2);
4✔
3736

3737
            d = (table1->column<Int>(c0) == null()).count();
4✔
3738
            CHECK_EQUAL(d, n ? 1 : 0);
4✔
3739

3740
            d = (table1->column<Int>(c0) != null()).count();
4✔
3741
            CHECK_EQUAL(d, 2);
4✔
3742

3743
            d = (table1->column<Int>(c1) != null()).count();
4✔
3744
            CHECK_EQUAL(d, 2);
4✔
3745
        }
4✔
3746
    }
4✔
3747
}
2✔
3748

3749
TEST(Query_ReferDeletedLinkView)
3750
{
2✔
3751
    // Queries and TableViews that depend on a deleted LinkList will now produce valid empty-like results
3752
    // (find() returns npos, find_all() returns empty TableView, sum() returns 0, etc.).
3753
    // They will no longer throw exceptions or crash.
3754
    Group group;
2✔
3755
    TableRef table = group.add_table("table");
2✔
3756
    auto col_link = table->add_column_list(*table, "children");
2✔
3757
    auto col_int = table->add_column(type_Int, "age");
2✔
3758
    auto links = table->create_object().set(col_int, 123).get_linklist(col_link);
2✔
3759
    Query q = table->where(links);
2✔
3760
    TableView tv = q.find_all();
2✔
3761

3762
    // TableView that depends on LinkView soon to be deleted
3763
    TableView tv_sorted = links.get_sorted_view(col_int);
2✔
3764

3765
    // First test depends_on_deleted_object()
3766
    CHECK(!tv_sorted.depends_on_deleted_object());
2✔
3767
    TableView tv2 = table->where(&tv).find_all();
2✔
3768
    CHECK(!tv2.depends_on_deleted_object());
2✔
3769

3770
    // Delete LinkList so LinkView gets detached
3771
    table->remove_object(table->begin());
2✔
3772
    CHECK(!links.is_attached());
2✔
3773
    CHECK(tv_sorted.depends_on_deleted_object());
2✔
3774

3775
    // See if "Query that depends on LinkView" returns sane "empty"-like values
3776
    CHECK_EQUAL(q.find_all().size(), 0);
2✔
3777
    CHECK_EQUAL(q.find(), null_key);
2✔
3778
    CHECK_EQUAL(q.sum(col_int), 0);
2✔
3779
    CHECK_EQUAL(q.count(), 0);
2✔
3780
    size_t rows;
2✔
3781
    q.avg(col_int, &rows);
2✔
3782
    CHECK_EQUAL(rows, 0);
2✔
3783

3784
    tv_sorted.sync_if_needed();
2✔
3785
    // See if "TableView that depends on LinkView" returns sane "empty"-like values
3786
    tv_sorted.avg(col_int, &rows);
2✔
3787
    CHECK_EQUAL(rows, 0);
2✔
3788

3789
    // Now check a "Query that depends on (TableView that depends on LinkView)"
3790
    Query q2 = table->where(&tv_sorted);
2✔
3791
    CHECK_EQUAL(q2.count(), 0);
2✔
3792
    CHECK_EQUAL(q2.find(), null_key);
2✔
3793

3794
    CHECK(!links.is_attached());
2✔
3795
    tv.sync_if_needed();
2✔
3796

3797
    // PLEASE NOTE that 'tv' will still return true in this case! Even though it indirectly depends on
3798
    // the LinkView through multiple levels!
3799
    CHECK(tv.is_attached());
2✔
3800

3801
    // Before executing any methods on a LinkList, you must still always check is_attached(). If you
3802
    // call links->add() on a deleted LinkViewRef (where is_attached() == false), it will assert
3803
    CHECK(!links.is_attached());
2✔
3804
}
2✔
3805

3806
TEST(Query_SubQueries)
3807
{
2✔
3808
    Group group;
2✔
3809

3810
    TableRef origin = group.add_table("origin");
2✔
3811
    TableRef target = group.add_table("target");
2✔
3812

3813
    // add some more columns to origin and target
3814
    auto col_int_t = target->add_column(type_Int, "integers");
2✔
3815
    auto col_string_t = target->add_column(type_String, "strings");
2✔
3816
    // in order to use set_all, columns involved in set_all must be inserted first.
3817
    auto col_link_o = origin->add_column_list(*target, "link");
2✔
3818

3819

3820
    // add some rows
3821
    origin->create_object(ObjKey(0));
2✔
3822
    origin->create_object(ObjKey(1));
2✔
3823
    origin->create_object(ObjKey(2));
2✔
3824

3825
    target->create_object(ObjKey(0)).set_all(400, "hello");
2✔
3826
    target->create_object(ObjKey(1)).set_all(500, "world");
2✔
3827
    target->create_object(ObjKey(2)).set_all(600, "!");
2✔
3828
    target->create_object(ObjKey(3)).set_all(600, "world");
2✔
3829

3830
    // set some links
3831
    auto links0 = origin->get_object(ObjKey(0)).get_linklist(col_link_o);
2✔
3832
    links0.add(ObjKey(1));
2✔
3833

3834
    auto links1 = origin->get_object(ObjKey(1)).get_linklist(col_link_o);
2✔
3835
    links1.add(ObjKey(1));
2✔
3836
    links1.add(ObjKey(2));
2✔
3837

3838
    ObjKey match;
2✔
3839
    TableView tv;
2✔
3840
    Query q;
2✔
3841
    Query sub_query;
2✔
3842

3843
    // The linked rows for rows 0 and 2 all match ("world", 500). Row 2 does by virtue of having no rows.
3844
    sub_query = target->column<String>(col_string_t) == "world" && target->column<Int>(col_int_t) == 500;
2✔
3845
    q = origin->column<Link>(col_link_o, sub_query).count() == origin->column<Link>(col_link_o).count();
2✔
3846
    tv = q.find_all();
2✔
3847
    CHECK_EQUAL(tv.size(), 2);
2✔
3848
    CHECK_EQUAL(ObjKey(0), tv.get_key(0));
2✔
3849
    CHECK_EQUAL(ObjKey(2), tv.get_key(1));
2✔
3850

3851
    // No linked rows match ("world, 600).
3852
    sub_query = target->column<String>(col_string_t) == "world" && target->column<Int>(col_int_t) == 600;
2✔
3853
    q = origin->column<Link>(col_link_o, sub_query).count() >= 1;
2✔
3854
    match = q.find();
2✔
3855
    CHECK_EQUAL(match, null_key);
2✔
3856

3857
    // Rows 0 and 1 both have at least one linked row that matches ("world", 500).
3858
    sub_query = target->column<String>(col_string_t) == "world" && target->column<Int>(col_int_t) == 500;
2✔
3859
    q = origin->column<Link>(col_link_o, sub_query).count() >= 1;
2✔
3860
    tv = q.find_all();
2✔
3861
    CHECK_EQUAL(tv.size(), 2);
2✔
3862
    CHECK_EQUAL(ObjKey(0), tv.get_key(0));
2✔
3863
    CHECK_EQUAL(ObjKey(1), tv.get_key(1));
2✔
3864

3865
    // Row 1 has at least one linked row that matches ("!", 600).
3866
    sub_query = target->column<String>(col_string_t) == "!" && target->column<Int>(col_int_t) == 600;
2✔
3867
    q = origin->column<Link>(col_link_o, sub_query).count() >= 1;
2✔
3868
    tv = q.find_all();
2✔
3869
    CHECK_EQUAL(tv.size(), 1);
2✔
3870
    CHECK_EQUAL(ObjKey(1), tv.get_key(0));
2✔
3871

3872
    // Row 1 has two linked rows that contain either "world" or 600.
3873
    sub_query = target->column<String>(col_string_t) == "world" || target->column<Int>(col_int_t) == 600;
2✔
3874
    q = origin->column<Link>(col_link_o, sub_query).count() == 2;
2✔
3875
    tv = q.find_all();
2✔
3876
    CHECK_EQUAL(tv.size(), 1);
2✔
3877
    CHECK_EQUAL(ObjKey(1), tv.get_key(0));
2✔
3878

3879
    // Rows 0 and 2 have at most one linked row that contains either "world" or 600. Row 2 does by virtue of having no
3880
    // rows.
3881
    sub_query = target->column<String>(col_string_t) == "world" || target->column<Int>(col_int_t) == 600;
2✔
3882
    q = origin->column<Link>(col_link_o, sub_query).count() <= 1;
2✔
3883
    tv = q.find_all();
2✔
3884
    CHECK_EQUAL(tv.size(), 2);
2✔
3885
    CHECK_EQUAL(ObjKey(0), tv.get_key(0));
2✔
3886
    CHECK_EQUAL(ObjKey(2), tv.get_key(1));
2✔
3887
}
2✔
3888

3889
// Ensure that Query's move constructor and move assignment operator don't result in
3890
// a TableView owned by the query being double-deleted when the queries are destroyed.
3891
TEST(Query_MoveDoesntDoubleDelete)
3892
{
2✔
3893
    Table table;
2✔
3894
    ConstTableRef ref = ConstTableRef::unsafe_create(&table);
2✔
3895
    {
2✔
3896
        Query q1(ref, std::unique_ptr<TableView>(new TableView()));
2✔
3897
        Query q2 = std::move(q1);
2✔
3898
    }
2✔
3899

3900
    {
2✔
3901
        Query q1(ref, std::unique_ptr<TableView>(new TableView()));
2✔
3902
        Query q2;
2✔
3903
        q2 = std::move(q1);
2✔
3904
    }
2✔
3905
}
2✔
3906

3907
TEST(Query_Timestamp)
3908
{
2✔
3909
    ObjKey match;
2✔
3910
    size_t cnt;
2✔
3911
    Table table;
2✔
3912
    auto col_first = table.add_column(type_Timestamp, "first", true);
2✔
3913
    auto col_second = table.add_column(type_Timestamp, "second", true);
2✔
3914
    Columns<Timestamp> first = table.column<Timestamp>(col_first);
2✔
3915
    Columns<Timestamp> second = table.column<Timestamp>(col_second);
2✔
3916

3917
    std::vector<ObjKey> keys;
2✔
3918
    table.create_objects(6, keys);
2✔
3919
    table.get_object(keys[0]).set(col_first, Timestamp(111, 222));
2✔
3920
    table.get_object(keys[1]).set(col_first, Timestamp(111, 333));
2✔
3921
    table.get_object(keys[2]).set(col_first, Timestamp(333, 444)).set(col_second, Timestamp(222, 222));
2✔
3922
    table.get_object(keys[3]).set(col_first, Timestamp{});
2✔
3923
    table.get_object(keys[4]).set(col_first, Timestamp(0, 0));
2✔
3924
    table.get_object(keys[5]).set(col_first, Timestamp(-1000, 0));
2✔
3925

3926

3927
    CHECK(table.get_object(keys[0]).get<Timestamp>(col_first) == Timestamp(111, 222));
2✔
3928

3929
    match = (first == Timestamp(111, 222)).find();
2✔
3930
    CHECK_EQUAL(match, keys[0]);
2✔
3931

3932
    match = (first != Timestamp(111, 222)).find();
2✔
3933
    CHECK_EQUAL(match, keys[1]);
2✔
3934

3935
    match = (first > Timestamp(111, 222)).find();
2✔
3936
    CHECK_EQUAL(match, keys[1]);
2✔
3937

3938
    match = (first < Timestamp(111, 333)).find();
2✔
3939
    CHECK_EQUAL(match, keys[0]);
2✔
3940

3941
    match = (first == Timestamp(0, 0)).find();
2✔
3942
    CHECK_EQUAL(match, keys[4]);
2✔
3943

3944
    match = (first < Timestamp(111, 333)).find();
2✔
3945
    CHECK_EQUAL(match, keys[0]);
2✔
3946

3947
    match = (first < Timestamp(0, 0)).find();
2✔
3948
    CHECK_EQUAL(match, keys[5]);
2✔
3949

3950
    // Note: .count(), not find()
3951
    cnt = (first < Timestamp(0, 0)).count();
2✔
3952
    CHECK_EQUAL(cnt, 1);
2✔
3953

3954
    cnt = (first != Timestamp{}).count();
2✔
3955
    CHECK_EQUAL(cnt, 5);
2✔
3956

3957
    cnt = (first != null{}).count();
2✔
3958
    CHECK_EQUAL(cnt, 5);
2✔
3959

3960
    cnt = (first != Timestamp(0, 0)).count();
2✔
3961
    CHECK_EQUAL(cnt, 5);
2✔
3962

3963
    cnt = (first > null{}).count();
2✔
3964
    CHECK_EQUAL(cnt, 0);
2✔
3965

3966
    cnt = (first < null{}).count();
2✔
3967
    CHECK_EQUAL(cnt, 0);
2✔
3968

3969
    cnt = (first >= null{}).count();
2✔
3970
    CHECK_EQUAL(cnt, 1);
2✔
3971

3972
    cnt = (first <= null{}).count();
2✔
3973
    CHECK_EQUAL(cnt, 1);
2✔
3974

3975
    cnt = (first != Timestamp(0, 0)).count();
2✔
3976
    CHECK_EQUAL(cnt, 5);
2✔
3977

3978
    match = (first < Timestamp(-100, 0)).find();
2✔
3979
    CHECK_EQUAL(match, keys[5]);
2✔
3980

3981
    cnt = (first >= Timestamp(std::numeric_limits<int64_t>::min(), -Timestamp::nanoseconds_per_second + 1)).count();
2✔
3982
    CHECK_EQUAL(cnt, 5);
2✔
3983

3984
    cnt = (first > Timestamp(std::numeric_limits<int64_t>::min(), -Timestamp::nanoseconds_per_second + 1)).count();
2✔
3985
    CHECK_EQUAL(cnt, 5);
2✔
3986

3987
    cnt = (first <= Timestamp(std::numeric_limits<int64_t>::max(), Timestamp::nanoseconds_per_second - 1)).count();
2✔
3988
    CHECK_EQUAL(cnt, 5);
2✔
3989

3990
    cnt = (first < Timestamp(std::numeric_limits<int64_t>::max(), Timestamp::nanoseconds_per_second - 1)).count();
2✔
3991
    CHECK_EQUAL(cnt, 5);
2✔
3992

3993
    // Left-hand-side being Timestamp() constant, right being column
3994
    match = (Timestamp(111, 222) == first).find();
2✔
3995
    CHECK_EQUAL(match, keys[0]);
2✔
3996

3997
    match = (Timestamp{} == first).find();
2✔
3998
    CHECK_EQUAL(match, keys[3]);
2✔
3999

4000
    match = (Timestamp(111, 222) > first).find();
2✔
4001
    CHECK_EQUAL(match, keys[4]);
2✔
4002

4003
    match = (Timestamp(111, 333) < first).find();
2✔
4004
    CHECK_EQUAL(match, keys[2]);
2✔
4005

4006
    match = (Timestamp(111, 222) >= first).find();
2✔
4007
    CHECK_EQUAL(match, keys[0]);
2✔
4008

4009
    match = (Timestamp(111, 111) >= first).find();
2✔
4010
    CHECK_EQUAL(match, keys[4]);
2✔
4011

4012
    match = (Timestamp(333, 444) <= first).find();
2✔
4013
    CHECK_EQUAL(match, keys[2]);
2✔
4014

4015
    match = (Timestamp(111, 300) <= first).find();
2✔
4016
    CHECK_EQUAL(match, keys[1]);
2✔
4017

4018
    match = (Timestamp(111, 222) != first).find();
2✔
4019
    CHECK_EQUAL(match, keys[1]);
2✔
4020

4021
    // Compare column with self
4022
    match = (first == first).find();
2✔
4023
    CHECK_EQUAL(match, keys[0]);
2✔
4024

4025
    match = (first != first).find();
2✔
4026
    CHECK_EQUAL(match, null_key);
2✔
4027

4028
    match = (first > first).find();
2✔
4029
    CHECK_EQUAL(match, null_key);
2✔
4030

4031
    match = (first < first).find();
2✔
4032
    CHECK_EQUAL(match, null_key);
2✔
4033

4034
    match = (first >= first).find();
2✔
4035
    CHECK_EQUAL(match, keys[0]);
2✔
4036

4037
    match = (first <= first).find();
2✔
4038
    CHECK_EQUAL(match, keys[0]);
2✔
4039

4040
    // Two different columns
4041
    match = (first == second).find();
2✔
4042
    CHECK_EQUAL(match, keys[3]); // null == null
2✔
4043

4044
    match = (first > second).find();
2✔
4045
    CHECK_EQUAL(match, keys[2]); // Timestamp(333, 444) > Timestamp(111, 222)
2✔
4046

4047
    match = (first < second).find();
2✔
4048
    CHECK_EQUAL(match, null_key); // Note that (null < null) == false
2✔
4049
}
2✔
4050

4051
TEST(Query_TimestampCount)
4052
{
2✔
4053
    Table table;
2✔
4054
    auto col_date = table.add_column(type_Timestamp, "date", true);
2✔
4055
    for (int i = 0; i < 10; i++) {
22✔
4056
        table.create_object().set(col_date, Timestamp(i / 4, i % 4));
20✔
4057
    }
20✔
4058
    table.get_object(5).set_null(col_date);
2✔
4059

4060
    // Timestamps : {0,0}, {0,1}, {0,2}, {0,3}, {1,0}, {}, {1,2}, {1,3}, {2,0}, {2,1}
4061

4062
    auto timestamps = table.column<Timestamp>(col_date);
2✔
4063

4064
    CHECK_EQUAL((timestamps > Timestamp(0, 3)).count(), 5);
2✔
4065
    CHECK_EQUAL((timestamps >= Timestamp(0, 3)).count(), 6);
2✔
4066
    CHECK_EQUAL((timestamps < Timestamp(1, 3)).count(), 6);
2✔
4067
    CHECK_EQUAL((timestamps <= Timestamp(1, 3)).count(), 7);
2✔
4068
    CHECK_EQUAL((timestamps == Timestamp(0, 2)).count(), 1);
2✔
4069
    CHECK_EQUAL((timestamps != Timestamp(0, 2)).count(), 9);
2✔
4070
    CHECK_EQUAL((timestamps == Timestamp()).count(), 1);
2✔
4071
    CHECK_EQUAL((timestamps != Timestamp()).count(), 9);
2✔
4072
}
2✔
4073

4074
TEST(Query_Timestamp_Null)
4075
{
2✔
4076
    // Test that querying for null on non-nullable column (with default value being non-null value) is
4077
    // possible (i.e. does not throw or fail) and also gives no search matches.
4078
    Table table;
2✔
4079
    ObjKey match;
2✔
4080

4081
    auto col0 = table.add_column(type_Timestamp, "first", false);
2✔
4082
    auto col1 = table.add_column(type_Timestamp, "second", true);
2✔
4083
    ObjKey k0 = table.create_object().get_key();
2✔
4084

4085
    Columns<Timestamp> first = table.column<Timestamp>(col0);
2✔
4086
    Columns<Timestamp> second = table.column<Timestamp>(col1);
2✔
4087

4088
    match = (first == Timestamp{}).find();
2✔
4089
    CHECK_EQUAL(match, null_key);
2✔
4090

4091
    match = (second == Timestamp{}).find();
2✔
4092
    CHECK_EQUAL(match, k0);
2✔
4093
}
2✔
4094

4095
// Ensure that coyping a Query copies a restricting TableView if the query owns the view.
4096
TEST(Query_CopyRestrictingTableViewWhenOwned)
4097
{
2✔
4098
    Table table;
2✔
4099
    ConstTableRef ref = ConstTableRef::unsafe_create(&table);
2✔
4100
    {
2✔
4101
        Query q1(ref, std::unique_ptr<TableView>(new TableView()));
2✔
4102
        Query q2(q1);
2✔
4103

4104
        // Reset the source query, destroying the original TableView.
4105
        q1 = {};
2✔
4106

4107
        // Operations on the copied query that touch the restricting view should not crash.
4108
        CHECK_EQUAL(0, q2.count());
2✔
4109
    }
2✔
4110

4111
    {
2✔
4112
        Query q1(ref, std::unique_ptr<TableView>(new TableView()));
2✔
4113
        Query q2;
2✔
4114
        q2 = q1;
2✔
4115

4116
        // Reset the source query, destroying the original TableView.
4117
        q1 = {};
2✔
4118

4119
        // Operations on the copied query that touch the restricting view should not crash.
4120
        CHECK_EQUAL(0, q2.count());
2✔
4121
    }
2✔
4122
}
2✔
4123

4124
TEST(Query_SyncViewIfNeeded)
4125
{
2✔
4126
    Group group;
2✔
4127
    TableRef source = group.add_table("source");
2✔
4128
    TableRef target = group.add_table("target");
2✔
4129

4130
    auto col_links = source->add_column_list(*target, "link");
2✔
4131
    auto col_id = target->add_column(type_Int, "id");
2✔
4132

4133
    auto reset_table_contents = [&] {
8✔
4134
        source->clear();
8✔
4135
        target->clear();
8✔
4136

4137
        for (int64_t i = 0; i < 15; ++i) {
128✔
4138
            target->create_object(ObjKey(i)).set(col_id, i);
120✔
4139
        }
120✔
4140

4141
        LnkLst ll = source->create_object().get_linklist(col_links);
8✔
4142
        for (size_t i = 6; i < 15; ++i) {
80✔
4143
            ll.add(ObjKey(i));
72✔
4144
        }
72✔
4145
    };
8✔
4146

4147
    // Restricting TableView. Query::sync_view_if_needed() syncs the TableView if needed.
4148
    {
2✔
4149
        reset_table_contents();
2✔
4150
        TableView restricting_view = target->where().greater(col_id, 5).find_all();
2✔
4151
        Query q = target->where(&restricting_view).less(col_id, 10);
2✔
4152

4153
        // Bring the view out of sync with the table.
4154
        target->get_object(ObjKey(7)).set(col_id, -7);
2✔
4155
        target->get_object(ObjKey(8)).set(col_id, -8);
2✔
4156

4157
        // Verify that the query uses the view as-is.
4158
        CHECK_EQUAL(4, q.count());
2✔
4159
        CHECK_EQUAL(false, restricting_view.is_in_sync());
2✔
4160

4161
        // And that syncing the query brings the view back into sync.
4162
        auto version = q.sync_view_if_needed();
2✔
4163
        CHECK_EQUAL(true, restricting_view.is_in_sync());
2✔
4164
        CHECK_EQUAL(2, q.count());
2✔
4165
        CHECK_EQUAL(version[0].first, target->get_key());
2✔
4166
        CHECK_EQUAL(version[0].second, target->get_content_version());
2✔
4167
    }
2✔
4168

4169
    // Restricting LinkView.
4170
    {
2✔
4171
        reset_table_contents();
2✔
4172
        LnkLst restricting_view = source->begin()->get_linklist(col_links);
2✔
4173
        Query q = target->where(restricting_view).less(col_id, 10);
2✔
4174
        CHECK_EQUAL(restricting_view.size(), 9);
2✔
4175
        CHECK_EQUAL(q.count(), 4);
2✔
4176

4177
        auto content = source->get_content_version();
2✔
4178
        // Modify the underlying table to remove rows from the LinkView.
4179
        target->remove_object(ObjKey(7));
2✔
4180
        target->remove_object(ObjKey(8));
2✔
4181
        CHECK_NOT_EQUAL(content, source->get_content_version());
2✔
4182

4183
        // A LnkLst is always in sync:
4184
        CHECK_EQUAL(true, restricting_view.is_in_sync());
2✔
4185
        CHECK_EQUAL(restricting_view.size(), 7);
2✔
4186
        // The query correctly takes into account the changes to the restricting view
4187
        CHECK_EQUAL(2, q.count());
2✔
4188

4189
        // And that syncing the query does nothing.
4190
        auto version = q.sync_view_if_needed();
2✔
4191
        CHECK_EQUAL(true, restricting_view.is_in_sync());
2✔
4192
        CHECK_EQUAL(version[0].first, target->get_key());
2✔
4193
        CHECK_EQUAL(version[0].second, target->get_content_version());
2✔
4194
        CHECK_EQUAL(2, q.count());
2✔
4195
    }
2✔
4196

4197
    // No restricting view. Query::sync_view_if_needed() does nothing.
4198
    {
2✔
4199
        reset_table_contents();
2✔
4200
        Query q = target->where().greater(col_id, 5).less(col_id, 10);
2✔
4201

4202
        target->get_object(ObjKey(7)).set(col_id, -7);
2✔
4203
        target->get_object(ObjKey(8)).set(col_id, -8);
2✔
4204

4205
        CHECK_EQUAL(2, q.count());
2✔
4206

4207
        auto version = q.sync_view_if_needed();
2✔
4208
        CHECK_EQUAL(version[0].first, target->get_key());
2✔
4209
        CHECK_EQUAL(version[0].second, target->get_content_version());
2✔
4210
        CHECK_EQUAL(2, q.count());
2✔
4211
    }
2✔
4212

4213
    // Query that is not associated with a Table. Query::sync_view_if_needed() does nothing.
4214
    {
2✔
4215
        reset_table_contents();
2✔
4216
        Query q;
2✔
4217

4218
        auto version = q.sync_view_if_needed();
2✔
4219
        CHECK(version.empty());
2✔
4220
    }
2✔
4221
}
2✔
4222

4223
// Ensure that two queries can be combined via Query::and_query, &&, and || even if one of them has no conditions.
4224
TEST(Query_CombineWithEmptyQueryDoesntCrash)
4225
{
2✔
4226
    Table table;
2✔
4227
    auto col_id = table.add_column(type_Int, "id");
2✔
4228

4229
    table.create_object().set(col_id, 0);
2✔
4230
    table.create_object().set(col_id, 1);
2✔
4231
    table.create_object().set(col_id, 2);
2✔
4232

4233
    {
2✔
4234
        Query q = table.where().equal(col_id, 1);
2✔
4235
        q.and_query(table.where());
2✔
4236
        CHECK_EQUAL(1, q.find_all().size());
2✔
4237
    }
2✔
4238

4239
    {
2✔
4240
        Query q1 = table.where().equal(col_id, 1);
2✔
4241
        Query q2 = table.where();
2✔
4242
        q1.and_query(q2);
2✔
4243
        CHECK_EQUAL(1, q1.count());
2✔
4244
    }
2✔
4245

4246
    {
2✔
4247
        Query q1 = table.where().equal(col_id, 1);
2✔
4248
        Query q2 = table.where();
2✔
4249
        q2.and_query(q1);
2✔
4250
        CHECK_EQUAL(1, q2.count());
2✔
4251
    }
2✔
4252

4253
    {
2✔
4254
        Query q = table.where();
2✔
4255
        q.and_query(table.where().equal(col_id, 1));
2✔
4256
        CHECK_EQUAL(1, q.count());
2✔
4257
    }
2✔
4258

4259
    {
2✔
4260
        Query q1 = table.where().equal(col_id, 1);
2✔
4261
        Query q2 = q1 && table.where();
2✔
4262
        CHECK_EQUAL(1, q2.count());
2✔
4263

4264
        Query q3 = table.where() && q1;
2✔
4265
        CHECK_EQUAL(1, q3.count());
2✔
4266
    }
2✔
4267

4268
    {
2✔
4269
        Query q1 = table.where().equal(col_id, 1);
2✔
4270
        Query q2 = q1 || table.where();
2✔
4271
        CHECK_EQUAL(1, q2.count());
2✔
4272

4273
        Query q3 = table.where() || q1;
2✔
4274
        CHECK_EQUAL(1, q3.count());
2✔
4275
    }
2✔
4276
}
2✔
4277

4278
// Check that queries take into account restricting views, but still
4279
// return row index into the underlying table
4280
TEST(Query_AccountForRestrictingViews)
4281
{
2✔
4282
    Table table;
2✔
4283
    auto col_id = table.add_column(type_Int, "id");
2✔
4284

4285
    table.create_object().set(col_id, 42);
2✔
4286
    table.create_object().set(col_id, 43);
2✔
4287
    table.create_object().set(col_id, 44);
2✔
4288

4289
    {
2✔
4290
        // Create initial table view
4291
        TableView results = table.where().equal(col_id, 44).find_all();
2✔
4292
        CHECK_EQUAL(1, results.size());
2✔
4293
        CHECK_EQUAL(44, results[0].get<Int>(col_id));
2✔
4294

4295
        // Create query based on restricting view
4296
        Query q = Query(results.get_parent()->where(&results));
2✔
4297
        ObjKey obj_key = q.find();
2✔
4298
        CHECK_EQUAL(obj_key, results.get_key(0));
2✔
4299
    }
2✔
4300
}
2✔
4301

4302
/*
4303

4304
// These tests fail on Windows due to lack of tolerance for invalid UTF-8 in the case mapping methods
4305

4306
TEST(Query_UTF8_Contains)
4307
{
4308
    Group group;
4309
    TableRef table1 = group.add_table("table1");
4310
    table1->add_column(type_String, "str1");
4311
    table1->add_empty_row();
4312
    table1->set_string(0, 0, StringData("\x0ff\x000", 2));
4313
    size_t m = table1->column<String>(0).contains(StringData("\x0ff\x000", 2), false).count();
4314
    CHECK_EQUAL(1, m);
4315
}
4316

4317

4318
TEST(Query_UTF8_Contains_Fuzzy)
4319
{
4320
    Table table;
4321
    table.add_column(type_String, "str1");
4322
    table.add_empty_row();
4323

4324
    for (size_t t = 0; t < 10000; t++) {
4325
        char haystack[10];
4326
        char needle[7];
4327

4328
        for (size_t c = 0; c < 10; c++)
4329
            haystack[c] = char(fastrand());
4330

4331
        for (size_t c = 0; c < 7; c++)
4332
            needle[c] = char(fastrand());
4333

4334
        table.set_string(0, 0, StringData(haystack, 10));
4335

4336
        table.column<String>(0).contains(StringData(needle, fastrand(7)), false).count();
4337
        table.column<String>(0).contains(StringData(needle, fastrand(7)), true).count();
4338
    }
4339
}
4340
*/
4341

4342
TEST(Query_ArrayLeafRelocate)
4343
{
2✔
4344
    for (size_t iter = 0; iter < 10; iter++) {
22✔
4345
        // Tests crash where a query node would have a SequentialGetter that pointed to an old array leaf
4346
        // that was relocated. https://github.com/realm/realm-core/issues/2269
4347
        // The above description does not apply to the cluster based implementation.
4348
        Group group;
20✔
4349

4350
        TableRef contact = group.add_table("contact");
20✔
4351
        TableRef contact_type = group.add_table("contact_type");
20✔
4352

4353
        auto col_int = contact_type->add_column(type_Int, "id");
20✔
4354
        auto col_str = contact_type->add_column(type_String, "str");
20✔
4355
        auto col_link = contact->add_column_list(*contact_type, "link");
20✔
4356

4357
        std::vector<ObjKey> contact_type_keys;
20✔
4358
        std::vector<ObjKey> contact_keys;
20✔
4359
        contact_type->create_objects(10, contact_type_keys);
20✔
4360
        contact->create_objects(10, contact_keys);
20✔
4361

4362
        Query q1 = (contact->link(col_link).column<Int>(col_int) == 0);
20✔
4363
        Query q2 = contact_type->where().equal(col_int, 0);
20✔
4364
        Query q3 = contact_type->query("id + id == 0");
20✔
4365
        Query q4 = (contact_type->column<Int>(col_int) == 0);
20✔
4366
        Query q5 = (contact_type->column<String>(col_str) == "hejsa");
20✔
4367

4368
        TableView tv = q1.find_all();
20✔
4369
        TableView tv2 = q2.find_all();
20✔
4370
        TableView tv3 = q3.find_all();
20✔
4371
        TableView tv4 = q4.find_all();
20✔
4372
        TableView tv5 = q5.find_all();
20✔
4373

4374
        contact->add_column(type_Float, "extra");
20✔
4375
        contact_type->add_column(type_Float, "extra");
20✔
4376

4377
        for (size_t t = 0; t < REALM_MAX_BPNODE_SIZE + 1; t++) {
20,040✔
4378
            Obj contact_obj = contact->create_object();
20,020✔
4379
            Obj contact_type_obj = contact_type->create_object();
20,020✔
4380
            //  contact_type.get()->set_string(1, t, "hejsa");
4381

4382
            auto ll = contact_obj.get_linklist(col_link);
20,020✔
4383
            ll.add(contact_type_obj.get_key());
20,020✔
4384

4385
            if (t == 0 || t == REALM_MAX_BPNODE_SIZE) {
20,020✔
4386
                tv.sync_if_needed();
40✔
4387
                tv2.sync_if_needed();
40✔
4388
                tv3.sync_if_needed();
40✔
4389
                tv4.sync_if_needed();
40✔
4390
                tv5.sync_if_needed();
40✔
4391
            }
40✔
4392
        }
20,020✔
4393
    }
20✔
4394
}
2✔
4395

4396

4397
TEST(Query_ColumnDeletionSimple)
4398
{
2✔
4399
    Table foo;
2✔
4400
    auto col_int0 = foo.add_column(type_Int, "a");
2✔
4401
    auto col_int1 = foo.add_column(type_Int, "b");
2✔
4402

4403
    std::vector<ObjKey> keys;
2✔
4404
    foo.create_objects(10, keys);
2✔
4405

4406
    foo.get_object(keys[3]).set(col_int0, 123);
2✔
4407
    foo.get_object(keys[4]).set(col_int0, 123);
2✔
4408
    foo.get_object(keys[7]).set(col_int0, 123);
2✔
4409
    foo.get_object(keys[2]).set(col_int1, 456);
2✔
4410
    foo.get_object(keys[4]).set(col_int1, 456);
2✔
4411

4412
    auto q1 = foo.column<Int>(col_int0) == 123;
2✔
4413
    auto q2 = foo.column<Int>(col_int1) == 456;
2✔
4414
    auto q3 = q1 || q2;
2✔
4415
    TableView tv1 = q1.find_all();
2✔
4416
    TableView tv2 = q2.find_all();
2✔
4417
    TableView tv3 = q3.find_all();
2✔
4418
    CHECK_EQUAL(tv1.size(), 3);
2✔
4419
    CHECK_EQUAL(tv2.size(), 2);
2✔
4420
    CHECK_EQUAL(tv3.size(), 4);
2✔
4421

4422
    foo.remove_column(col_int0);
2✔
4423

4424
    size_t x = 0;
2✔
4425
    CHECK_LOGIC_ERROR(x = q1.count(), ErrorCodes::InvalidProperty);
2✔
4426
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4427
    CHECK_EQUAL(x, 0);
2✔
4428
    CHECK_EQUAL(tv1.size(), 0);
2✔
4429

4430
    // This one should succeed in spite the column index is 1 and we
4431
    x = q2.count();
2✔
4432
    tv2.sync_if_needed();
2✔
4433
    CHECK_EQUAL(x, 2);
2✔
4434
    CHECK_EQUAL(tv2.size(), 2);
2✔
4435

4436
    x = 0;
2✔
4437
    CHECK_LOGIC_ERROR(x = q3.count(), ErrorCodes::InvalidProperty);
2✔
4438
    CHECK_LOGIC_ERROR(tv3.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4439
    CHECK_EQUAL(x, 0);
2✔
4440
    CHECK_EQUAL(tv3.size(), 0);
2✔
4441
}
2✔
4442

4443

4444
TEST(Query_ColumnDeletionExpression)
4445
{
2✔
4446
    Table foo;
2✔
4447
    auto col_int0 = foo.add_column(type_Int, "a");
2✔
4448
    auto col_int1 = foo.add_column(type_Int, "b");
2✔
4449
    auto col_date2 = foo.add_column(type_Timestamp, "c");
2✔
4450
    auto col_date3 = foo.add_column(type_Timestamp, "d");
2✔
4451
    auto col_str4 = foo.add_column(type_String, "e");
2✔
4452
    auto col_float5 = foo.add_column(type_Float, "f");
2✔
4453
    auto col_bin6 = foo.add_column(type_Binary, "g");
2✔
4454

4455
    Obj obj0 = foo.create_object();
2✔
4456
    Obj obj1 = foo.create_object();
2✔
4457
    Obj obj2 = foo.create_object();
2✔
4458
    Obj obj3 = foo.create_object();
2✔
4459
    Obj obj4 = foo.create_object();
2✔
4460
    obj0.set(col_int0, 0);
2✔
4461
    obj1.set(col_int0, 1);
2✔
4462
    obj2.set(col_int0, 2);
2✔
4463
    obj3.set(col_int0, 3);
2✔
4464
    obj4.set(col_int0, 4);
2✔
4465
    obj0.set(col_int1, 0);
2✔
4466
    obj1.set(col_int1, 0);
2✔
4467
    obj2.set(col_int1, 3);
2✔
4468
    obj3.set(col_int1, 5);
2✔
4469
    obj4.set(col_int1, 3);
2✔
4470
    obj0.set(col_date2, Timestamp(100, 100));
2✔
4471
    obj0.set(col_date3, Timestamp(200, 100));
2✔
4472
    obj0.set(col_str4, StringData("Hello, world"));
2✔
4473
    obj0.set(col_float5, 3.141592f);
2✔
4474
    obj1.set(col_float5, 1.0f);
2✔
4475
    obj0.set(col_bin6, BinaryData("Binary", 6));
2✔
4476

4477
    // Expression
4478
    auto q = foo.query("a == b + 1");
2✔
4479
    // TwoColumnsNode
4480
    auto q1 = foo.column<Int>(col_int0) == foo.column<Int>(col_int1);
2✔
4481
    TableView tv = q.find_all();
2✔
4482
    TableView tv1 = q1.find_all();
2✔
4483
    CHECK_EQUAL(tv.size(), 2);
2✔
4484
    CHECK_EQUAL(tv1.size(), 1);
2✔
4485

4486
    foo.remove_column(col_int0);
2✔
4487
    size_t x = 0;
2✔
4488
    CHECK_LOGIC_ERROR(x = q.count(), ErrorCodes::InvalidProperty);
2✔
4489
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4490
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4491
    CHECK_EQUAL(x, 0);
2✔
4492
    CHECK_EQUAL(tv.size(), 0);
2✔
4493

4494
    q = foo.column<Timestamp>(col_date2) < foo.column<Timestamp>(col_date3);
2✔
4495
    // TimestampNode
4496
    q1 = foo.column<Timestamp>(col_date3) == Timestamp(200, 100);
2✔
4497
    tv = q.find_all();
2✔
4498
    tv1 = q1.find_all();
2✔
4499
    CHECK_EQUAL(tv.size(), 1);
2✔
4500
    CHECK_EQUAL(tv1.size(), 1);
2✔
4501
    foo.remove_column(col_date3);
2✔
4502
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4503
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4504

4505
    // StringNodeBase
4506
    q = foo.column<String>(col_str4) == StringData("Hello, world");
2✔
4507
    q1 = !(foo.column<String>(col_str4) == StringData("Hello, world"));
2✔
4508
    tv = q.find_all();
2✔
4509
    tv1 = q1.find_all();
2✔
4510
    CHECK_EQUAL(tv.size(), 1);
2✔
4511
    CHECK_EQUAL(tv1.size(), 4);
2✔
4512
    foo.remove_column(col_str4);
2✔
4513
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4514
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4515

4516
    // FloatDoubleNode
4517
    q = foo.column<Float>(col_float5) > 0.0f;
2✔
4518
    tv = q.find_all();
2✔
4519
    CHECK_EQUAL(tv.size(), 2);
2✔
4520
    foo.remove_column(col_float5);
2✔
4521
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4522

4523
    // BinaryNode
4524
    q = foo.column<Binary>(col_bin6) != BinaryData("Binary", 6);
2✔
4525
    tv = q.find_all();
2✔
4526
    CHECK_EQUAL(tv.size(), 4);
2✔
4527
    foo.remove_column(col_bin6);
2✔
4528
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4529
}
2✔
4530

4531

4532
TEST(Query_ColumnDeletionLinks)
4533
{
2✔
4534
    Group g;
2✔
4535
    TableRef foo = g.add_table("foo");
2✔
4536
    TableRef bar = g.add_table("bar");
2✔
4537
    TableRef foobar = g.add_table("foobar");
2✔
4538

4539
    auto col_int0 = foobar->add_column(type_Int, "int");
2✔
4540

4541
    auto col_int1 = bar->add_column(type_Int, "int");
2✔
4542
    auto col_link0 = bar->add_column(*foobar, "link");
2✔
4543

4544
    auto col_link1 = foo->add_column(*bar, "link");
2✔
4545

4546
    std::vector<ObjKey> foobar_keys;
2✔
4547
    std::vector<ObjKey> bar_keys;
2✔
4548
    std::vector<ObjKey> foo_keys;
2✔
4549
    foobar->create_objects(5, foobar_keys);
2✔
4550
    bar->create_objects(5, bar_keys);
2✔
4551
    foo->create_objects(10, foo_keys);
2✔
4552

4553
    for (int i = 0; i < 5; i++) {
12✔
4554
        foobar->get_object(foobar_keys[i]).set(col_int0, i);
10✔
4555
        bar->get_object(bar_keys[i]).set(col_int1, i);
10✔
4556
        bar->get_object(bar_keys[i]).set(col_link0, foobar_keys[i]);
10✔
4557
        foo->get_object(foo_keys[i]).set(col_link1, bar_keys[i]);
10✔
4558
    }
10✔
4559
    auto q = foo->link(col_link1).link(col_link0).column<Int>(col_int0) == 2;
2✔
4560
    auto q1 = foo->column<Link>(col_link1).is_null();
2✔
4561
    auto q2 = foo->column<Link>(col_link1) == bar->get_object(bar_keys[2]);
2✔
4562

4563
    auto tv = q.find_all();
2✔
4564
    auto cnt = q1.count();
2✔
4565
    CHECK_EQUAL(tv.size(), 1);
2✔
4566
    CHECK_EQUAL(cnt, 5);
2✔
4567
    cnt = q2.count();
2✔
4568
    CHECK_EQUAL(cnt, 1);
2✔
4569

4570
    // remove integer column, should not affect query
4571
    bar->remove_column(col_int1);
2✔
4572
    tv.sync_if_needed();
2✔
4573
    CHECK_EQUAL(tv.size(), 1);
2✔
4574
    // remove link column, disaster
4575
    bar->remove_column(col_link0);
2✔
4576
    CHECK_LOGIC_ERROR(bar->check_column(col_link0), ErrorCodes::InvalidProperty);
2✔
4577
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4578
    foo->remove_column(col_link1);
2✔
4579
    CHECK_LOGIC_ERROR(foo->check_column(col_link1), ErrorCodes::InvalidProperty);
2✔
4580
    CHECK_LOGIC_ERROR(q1.count(), ErrorCodes::InvalidProperty);
2✔
4581
    CHECK_LOGIC_ERROR(q2.count(), ErrorCodes::InvalidProperty);
2✔
4582
}
2✔
4583

4584

4585
TEST(Query_CaseInsensitiveIndexEquality_CommonNumericPrefix)
4586
{
2✔
4587
    Table table;
2✔
4588
    auto col_ndx = table.add_column(type_String, "id");
2✔
4589
    table.add_search_index(col_ndx);
2✔
4590

4591
    ObjKey key0 = table.create_object().set(col_ndx, "111111111111111111111111").get_key();
2✔
4592
    table.create_object().set(col_ndx, "111111111111111111111112");
2✔
4593

4594
    Query q = table.where().equal(col_ndx, "111111111111111111111111", false);
2✔
4595
    CHECK_EQUAL(q.count(), 1);
2✔
4596
    TableView tv = q.find_all();
2✔
4597
    CHECK_EQUAL(tv.size(), 1);
2✔
4598
    CHECK_EQUAL(tv[0].get_key(), key0);
2✔
4599
}
2✔
4600

4601

4602
TEST_TYPES(Query_CaseInsensitiveNullable, std::true_type, std::false_type)
4603
{
4✔
4604
    Table table;
4✔
4605
    bool nullable = true;
4✔
4606
    constexpr bool with_index = TEST_TYPE::value;
4✔
4607
    auto col_ndx = table.add_column(type_String, "id", nullable);
4✔
4608
    if (with_index) {
4✔
4609
        table.add_search_index(col_ndx);
2✔
4610
    }
2✔
4611

4612
    table.create_object().set(col_ndx, "test");
4✔
4613
    table.create_object().set(col_ndx, "words");
4✔
4614
    ObjKey key2 = table.create_object().get_key();
4✔
4615
    ObjKey key3 = table.create_object().get_key();
4✔
4616
    table.create_object().set(col_ndx, "");
4✔
4617
    table.create_object().set(col_ndx, "");
4✔
4618

4619
    bool case_sensitive = true;
4✔
4620
    StringData null_string;
4✔
4621
    Query q = table.where().equal(col_ndx, null_string, case_sensitive);
4✔
4622
    CHECK_EQUAL(q.count(), 2);
4✔
4623
    TableView tv = q.find_all();
4✔
4624
    CHECK_EQUAL(tv.size(), 2);
4✔
4625
    CHECK_EQUAL(tv.get_key(0), key2);
4✔
4626
    CHECK_EQUAL(tv.get_key(1), key3);
4✔
4627
    Query q2 = table.where().contains(col_ndx, null_string, case_sensitive);
4✔
4628
    CHECK_EQUAL(q2.count(), 6);
4✔
4629
    tv = q2.find_all();
4✔
4630
    CHECK_EQUAL(tv.size(), 6);
4✔
4631

4632
    case_sensitive = false;
4✔
4633
    q = table.where().equal(col_ndx, null_string, case_sensitive);
4✔
4634
    CHECK_EQUAL(q.count(), 2);
4✔
4635
    tv = q.find_all();
4✔
4636
    CHECK_EQUAL(tv.size(), 2);
4✔
4637
    CHECK_EQUAL(tv.get_key(0), key2);
4✔
4638
    CHECK_EQUAL(tv.get_key(1), key3);
4✔
4639
    q2 = table.where().contains(col_ndx, null_string, case_sensitive);
4✔
4640
    CHECK_EQUAL(q2.count(), 6);
4✔
4641
    tv = q2.find_all();
4✔
4642
    CHECK_EQUAL(tv.size(), 6);
4✔
4643
}
4✔
4644

4645

4646
TEST_TYPES(Query_Rover, std::true_type, std::false_type)
4647
{
4✔
4648
    constexpr bool nullable = TEST_TYPE::value;
4✔
4649

4650
    Table table;
4✔
4651
    auto col = table.add_column(type_String, "name", nullable);
4✔
4652
    table.add_search_index(col);
4✔
4653

4654
    table.create_object().set(col, "ROVER");
4✔
4655
    table.create_object().set(col, "Rover");
4✔
4656

4657
    Query q = table.where().equal(col, "rover", false);
4✔
4658
    CHECK_EQUAL(q.count(), 2);
4✔
4659
    TableView tv = q.find_all();
4✔
4660
    CHECK_EQUAL(tv.size(), 2);
4✔
4661
}
4✔
4662

4663
TEST(Query_StringPrimaryKey)
4664
{
2✔
4665
    Table table;
2✔
4666
    auto col = table.add_column(type_String, "name");
2✔
4667
    table.set_primary_key_column(col);
2✔
4668

4669
    table.create_object_with_primary_key("RASMUS");
2✔
4670
    table.create_object_with_primary_key("Rasmus");
2✔
4671

4672
    Query q = table.where().equal(col, "rasmus", false);
2✔
4673
    CHECK_EQUAL(q.count(), 2);
2✔
4674
    TableView tv = q.find_all();
2✔
4675
    CHECK_EQUAL(tv.size(), 2);
2✔
4676
}
2✔
4677

4678
TEST(Query_IntOnly)
4679
{
2✔
4680
    Table table;
2✔
4681
    auto c0 = table.add_column(type_Int, "i1");
2✔
4682
    auto c1 = table.add_column(type_Int, "i2");
2✔
4683

4684
    table.create_object(ObjKey(7)).set_all(7, 6);
2✔
4685
    table.create_object(ObjKey(19)).set_all(19, 9);
2✔
4686
    table.create_object(ObjKey(5)).set_all(19, 22);
2✔
4687
    table.create_object(ObjKey(21)).set_all(2, 6);
2✔
4688

4689
    auto q = table.column<Int>(c1) == 6;
2✔
4690
    ObjKey key = q.find();
2✔
4691
    CHECK_EQUAL(key, ObjKey(7));
2✔
4692

4693
    TableView tv = q.find_all();
2✔
4694
    CHECK_EQUAL(tv.size(), 2);
2✔
4695
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(7));
2✔
4696
    CHECK_EQUAL(tv.get_object(1).get_key(), ObjKey(21));
2✔
4697

4698
    auto q1 = table.where(&tv).equal(c0, 2);
2✔
4699
    TableView tv1 = q1.find_all();
2✔
4700
    CHECK_EQUAL(tv1.size(), 1);
2✔
4701
    CHECK_EQUAL(tv1.get_object(0).get_key(), ObjKey(21));
2✔
4702

4703
    q1 = table.where(&tv).greater(c0, 5);
2✔
4704
    tv1 = q1.find_all();
2✔
4705
    CHECK_EQUAL(tv1.size(), 1);
2✔
4706
    CHECK_EQUAL(tv1.get_object(0).get_key(), ObjKey(7));
2✔
4707

4708
    q = table.column<Int>(c0) == 19 && table.column<Int>(c1) == 9;
2✔
4709
    key = q.find();
2✔
4710
    CHECK_EQUAL(key.value, 19);
2✔
4711

4712
    tv = q.find_all();
2✔
4713
    CHECK_EQUAL(tv.size(), 1);
2✔
4714
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(19));
2✔
4715

4716
    // Two column expression
4717
    q = table.column<Int>(c0) < table.column<Int>(c1);
2✔
4718
    tv = q.find_all();
2✔
4719
    CHECK_EQUAL(tv.size(), 2);
2✔
4720
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(5));
2✔
4721
    CHECK_EQUAL(tv.get_object(1).get_key(), ObjKey(21));
2✔
4722
}
2✔
4723

4724
TEST(Query_LinksTo)
4725
{
2✔
4726
    Query q;
2✔
4727
    ObjKey found_key;
2✔
4728
    Group group;
2✔
4729

4730
    TableRef source = group.add_table("source");
2✔
4731
    TableRef target = group.add_table("target");
2✔
4732

4733
    auto col_link = source->add_column(*target, "link");
2✔
4734
    auto col_linklist = source->add_column_list(*target, "linklist");
2✔
4735

4736
    std::vector<ObjKey> target_keys;
2✔
4737
    target->create_objects(10, target_keys);
2✔
4738

4739
    std::vector<ObjKey> source_keys;
2✔
4740
    source->create_objects(10, source_keys);
2✔
4741

4742
    source->get_object(source_keys[2]).set(col_link, target_keys[2]);
2✔
4743
    source->get_object(source_keys[5]).set(col_link, target_keys[5]);
2✔
4744
    source->get_object(source_keys[8]).set(col_link, target_keys[5]);
2✔
4745
    source->get_object(source_keys[9]).set(col_link, target_keys[9]);
2✔
4746

4747
    q = source->column<Link>(col_link) == target->get_object(target_keys[2]);
2✔
4748
    found_key = q.find();
2✔
4749
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4750

4751
    q = source->where().equal(col_link, Mixed(target_keys[2]));
2✔
4752
    found_key = q.find();
2✔
4753
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4754

4755
    q = source->column<Link>(col_link) == target->get_object(target_keys[5]);
2✔
4756
    found_key = q.find();
2✔
4757
    CHECK_EQUAL(found_key, source_keys[5]);
2✔
4758
    q = source->where().equal(col_link, Mixed(target_keys[5]));
2✔
4759
    auto tv = q.find_all();
2✔
4760
    CHECK_EQUAL(tv.size(), 2);
2✔
4761
    q = source->where().not_equal(col_link, Mixed(target_keys[5]));
2✔
4762
    tv = q.find_all();
2✔
4763
    CHECK_EQUAL(tv.size(), 8);
2✔
4764
    q = source->where().equal(col_link, Mixed(ObjLink(source->get_key(), target_keys[5]))); // Wrong table
2✔
4765
    tv = q.find_all();
2✔
4766
    CHECK_EQUAL(tv.size(), 0);
2✔
4767

4768
    q = source->column<Link>(col_link) == target->get_object(target_keys[9]);
2✔
4769
    found_key = q.find();
2✔
4770
    CHECK_EQUAL(found_key, source_keys[9]);
2✔
4771

4772
    q = source->column<Link>(col_link) == target->get_object(target_keys[0]);
2✔
4773
    found_key = q.find();
2✔
4774
    CHECK_EQUAL(found_key, null_key);
2✔
4775

4776
    q = source->column<Link>(col_link).is_null();
2✔
4777
    tv = q.find_all();
2✔
4778
    CHECK_EQUAL(tv.size(), 6);
2✔
4779
    q = source->where().equal(col_link, Mixed()); // Null
2✔
4780
    tv = q.find_all();
2✔
4781
    CHECK_EQUAL(tv.size(), 6);
2✔
4782

4783
    q = source->column<Link>(col_link) != null();
2✔
4784
    found_key = q.find();
2✔
4785
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4786
    q = source->where().not_equal(col_link, Mixed()); // Null
2✔
4787
    tv = q.find_all();
2✔
4788
    CHECK_EQUAL(tv.size(), 4);
2✔
4789

4790
    auto linklist = source->get_object(source_keys[1]).get_linklist_ptr(col_linklist);
2✔
4791
    linklist->add(target_keys[6]);
2✔
4792
    linklist = source->get_object(source_keys[2]).get_linklist_ptr(col_linklist);
2✔
4793
    linklist->add(target_keys[0]);
2✔
4794
    linklist->add(target_keys[1]);
2✔
4795
    linklist->add(target_keys[2]);
2✔
4796
    linklist = source->get_object(source_keys[8]).get_linklist_ptr(col_linklist);
2✔
4797
    linklist->add(target_keys[0]);
2✔
4798
    linklist->add(target_keys[5]);
2✔
4799
    linklist->add(target_keys[7]);
2✔
4800

4801
    q = source->column<Link>(col_linklist) == target->get_object(target_keys[5]);
2✔
4802
    found_key = q.find();
2✔
4803
    CHECK_EQUAL(found_key, source_keys[8]);
2✔
4804

4805
    q = source->column<Link>(col_linklist) != target->get_object(target_keys[6]);
2✔
4806
    found_key = q.find();
2✔
4807
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4808

4809
    q = source->where().equal(col_linklist, Mixed(target_keys[0]));
2✔
4810
    tv = q.find_all();
2✔
4811
    CHECK_EQUAL(tv.size(), 2);
2✔
4812
    q = source->where().not_equal(col_linklist, Mixed(target_keys[6]));
2✔
4813
    tv = q.find_all();
2✔
4814
    CHECK_EQUAL(tv.size(), 2);
2✔
4815

4816
    q = source->where().equal(col_linklist, Mixed());
2✔
4817
    tv = q.find_all();
2✔
4818
    CHECK_EQUAL(tv.size(), 0); // LinkList never matches null
2✔
4819
    q = source->where().not_equal(col_linklist, Mixed());
2✔
4820
    tv = q.find_all();
2✔
4821
    CHECK_EQUAL(tv.size(), 3);
2✔
4822
}
2✔
4823

4824
TEST(Query_Group_bug)
4825
{
2✔
4826
    // Tests for a bug in queries with OR nodes at different nesting levels
4827

4828
    Group g;
2✔
4829
    TableRef service_table = g.add_table("service");
2✔
4830
    TableRef profile_table = g.add_table("profile");
2✔
4831
    TableRef person_table = g.add_table("person");
2✔
4832

4833
    auto col_service_id = service_table->add_column(type_String, "id");
2✔
4834
    auto col_service_link = service_table->add_column_list(*profile_table, "profiles");
2✔
4835

4836
    auto col_profile_id = profile_table->add_column(type_String, "role");
2✔
4837
    auto col_profile_link = profile_table->add_column(*service_table, "services");
2✔
4838

4839
    auto col_person_id = person_table->add_column(type_String, "id");
2✔
4840
    auto col_person_link = person_table->add_column_list(*service_table, "services");
2✔
4841

4842
    auto sk0 = service_table->create_object().set(col_service_id, "service_1").get_key();
2✔
4843
    auto sk1 = service_table->create_object().set(col_service_id, "service_2").get_key();
2✔
4844

4845
    auto pk0 = profile_table->create_object().set(col_profile_id, "profile_1").get_key();
2✔
4846
    auto pk1 = profile_table->create_object().set(col_profile_id, "profile_2").get_key();
2✔
4847
    auto pk2 = profile_table->create_object().set(col_profile_id, "profile_3").get_key();
2✔
4848
    auto pk3 = profile_table->create_object().set(col_profile_id, "profile_4").get_key();
2✔
4849
    auto pk4 = profile_table->create_object().set(col_profile_id, "profile_5").get_key();
2✔
4850

4851
    {
2✔
4852
        auto ll0 = service_table->get_object(sk0).get_linklist(col_service_link);
2✔
4853
        auto ll1 = service_table->get_object(sk1).get_linklist(col_service_link);
2✔
4854
        ll0.add(pk0);
2✔
4855
        ll0.add(pk1);
2✔
4856
        ll1.add(pk2);
2✔
4857
        ll0.add(pk3);
2✔
4858
        ll0.add(pk4);
2✔
4859
    }
2✔
4860

4861
    profile_table->get_object(pk0).set(col_profile_link, sk0);
2✔
4862
    profile_table->get_object(pk1).set(col_profile_link, sk0);
2✔
4863
    profile_table->get_object(pk2).set(col_profile_link, sk1);
2✔
4864
    profile_table->get_object(pk3).set(col_profile_link, sk0);
2✔
4865
    profile_table->get_object(pk4).set(col_profile_link, sk0);
2✔
4866

4867
    person_table->create_object().set(col_person_id, "person_1").get_linklist(col_person_link).add(sk0);
2✔
4868
    person_table->create_object().set(col_person_id, "person_2").get_linklist(col_person_link).add(sk0);
2✔
4869
    person_table->create_object().set(col_person_id, "person_3").get_linklist(col_person_link).add(sk1);
2✔
4870
    person_table->create_object().set(col_person_id, "person_4").get_linklist(col_person_link).add(sk0);
2✔
4871
    person_table->create_object().set(col_person_id, "person_5").get_linklist(col_person_link).add(sk0);
2✔
4872

4873
    realm::Query q0 =
2✔
4874
        person_table->where()
2✔
4875
            .group()
2✔
4876

4877
            .group()
2✔
4878
            .and_query(person_table->link(col_person_link)
2✔
4879
                           .link(col_service_link)
2✔
4880
                           .column<String>(col_profile_id)
2✔
4881
                           .equal("profile_1"))
2✔
4882
            .Or()
2✔
4883
            .and_query(person_table->link(col_person_link)
2✔
4884
                           .link(col_service_link)
2✔
4885
                           .column<String>(col_profile_id)
2✔
4886
                           .equal("profile_2"))
2✔
4887
            .end_group()
2✔
4888

4889
            .group()
2✔
4890
            .and_query(person_table->link(col_person_link).column<String>(col_service_id).equal("service_1"))
2✔
4891
            .end_group()
2✔
4892

4893
            .end_group()
2✔
4894

4895
            .Or()
2✔
4896

4897
            .group()
2✔
4898
            .equal(col_person_id, "person_3")
2✔
4899
            .end_group();
2✔
4900

4901
    CHECK_EQUAL(5, q0.count());
2✔
4902
}
2✔
4903

4904
TEST(Query_TwoColumnUnaligned)
4905
{
2✔
4906
    Group g;
2✔
4907
    TableRef table = g.add_table("table");
2✔
4908
    ColKey a_col_ndx = table->add_column(type_Int, "a");
2✔
4909
    ColKey b_col_ndx = table->add_column(type_Int, "b");
2✔
4910

4911
    // Adding 1001 rows causes arrays in the 2 columns to be aligned differently
4912
    // (on a 0 and on an 8 address resp)
4913
    auto matches = 0;
2✔
4914
    for (int i = 0; i < 1001; ++i) {
2,004✔
4915
        Obj obj = table->create_object();
2,002✔
4916
        obj.set(a_col_ndx, i);
2,002✔
4917
        if (i % 88) {
2,002✔
4918
            obj.set(b_col_ndx, i + 5);
1,978✔
4919
        }
1,978✔
4920
        else {
24✔
4921
            obj.set(b_col_ndx, i);
24✔
4922
            matches++;
24✔
4923
        }
24✔
4924
    }
2,002✔
4925

4926
    Query q = table->column<Int>(a_col_ndx) == table->column<Int>(b_col_ndx);
2✔
4927
    size_t cnt = q.count();
2✔
4928
    CHECK_EQUAL(cnt, matches);
2✔
4929
}
2✔
4930

4931

4932
TEST(Query_IntOrQueryOptimisation)
4933
{
2✔
4934
    Group g;
2✔
4935
    TableRef table = g.add_table("table");
2✔
4936
    auto col_optype = table->add_column(type_String, "optype");
2✔
4937
    auto col_active = table->add_column(type_Bool, "active");
2✔
4938
    auto col_id = table->add_column(type_Int, "id");
2✔
4939

4940
    for (int i = 0; i < 100; i++) {
202✔
4941
        auto obj = table->create_object();
200✔
4942
        obj.set<bool>(col_active, (i % 10) != 0);
200✔
4943
        obj.set<int>(col_id, i);
200✔
4944
        if (i == 0)
200✔
4945
            obj.set(col_optype, "CREATE");
2✔
4946
        if (i == 1)
200✔
4947
            obj.set(col_optype, "DELETE");
2✔
4948
        if (i == 2)
200✔
4949
            obj.set(col_optype, "CREATE");
2✔
4950
    }
200✔
4951
    auto optype = table->column<String>(col_optype);
2✔
4952
    auto active = table->column<Bool>(col_active);
2✔
4953
    auto id = table->column<Int>(col_id);
2✔
4954

4955
    Query q;
2✔
4956
    q = (id == 0 && optype == "CREATE") || id == 1;
2✔
4957
    CHECK_EQUAL(q.count(), 2);
2✔
4958

4959
    q = id == 1 || (id == 0 && optype == "DELETE");
2✔
4960
    CHECK_EQUAL(q.count(), 1);
2✔
4961

4962
    q = table->where().equal(col_id, 1).Or().equal(col_id, 0).Or().equal(col_id, 2);
2✔
4963
    CHECK_EQUAL(q.count(), 3);
2✔
4964
}
2✔
4965

4966
TEST_IF(Query_IntOrQueryPerformance, TEST_DURATION > 0)
4967
{
×
4968
    using std::chrono::duration_cast;
×
4969
    using std::chrono::microseconds;
×
4970

4971
    Group g;
×
4972
    TableRef table = g.add_table("table");
×
4973
    auto ints_col_key = table->add_column(type_Int, "ints");
×
4974
    auto nullable_ints_col_key = table->add_column(type_Int, "nullable_ints", true);
×
4975

4976
    const int null_frequency = 1000;
×
4977
    int num_nulls_added = 0;
×
4978
    int limit = 100000;
×
4979
    for (int i = 0; i < limit; ++i) {
×
4980
        if (i % null_frequency == 0) {
×
4981
            auto o = table->create_object().set_all(i);
×
4982
            o.set_null(nullable_ints_col_key);
×
4983
            ++num_nulls_added;
×
4984
        }
×
4985
        else {
×
4986
            table->create_object().set_all(i, i);
×
4987
        }
×
4988
    }
×
4989

4990
    auto run_queries = [&](int num_matches) {
×
4991
        // std::cout << "num_matches: " << num_matches << std::endl;
4992
        Query q_ints = table->column<Int>(ints_col_key) == -1;
×
4993
        Query q_nullables =
×
4994
            (table->column<Int>(nullable_ints_col_key) == -1).Or().equal(nullable_ints_col_key, realm::null());
×
4995
        for (int i = 0; i < num_matches; ++i) {
×
4996
            q_ints = q_ints.Or().equal(ints_col_key, i);
×
4997
            q_nullables = q_nullables.Or().equal(nullable_ints_col_key, i);
×
4998
        }
×
4999

5000
        auto before = std::chrono::steady_clock().now();
×
5001
        size_t ints_count = q_ints.count();
×
5002
        auto after = std::chrono::steady_clock().now();
×
5003
        // std::cout << "ints count: " << duration_cast<microseconds>(after - before).count() << " us" << std::endl;
5004

5005
        before = std::chrono::steady_clock().now();
×
5006
        size_t nullable_ints_count = q_nullables.count();
×
5007
        after = std::chrono::steady_clock().now();
×
5008
        // std::cout << "nullable ints count: " << duration_cast<microseconds>(after - before).count() << " us"
5009
        //           << std::endl;
5010

5011
        size_t expected_nullable_query_count =
×
5012
            num_matches + num_nulls_added - (((num_matches - 1) / null_frequency) + 1);
×
5013
        CHECK_EQUAL(ints_count, num_matches);
×
5014
        CHECK_EQUAL(nullable_ints_count, expected_nullable_query_count);
×
5015
    };
×
5016

5017
    run_queries(2);
×
5018
    run_queries(2048);
×
5019

5020
    table->add_search_index(ints_col_key);
×
5021
    table->add_search_index(nullable_ints_col_key);
×
5022

5023
    run_queries(2);
×
5024
    run_queries(2048);
×
5025
}
×
5026

5027
TEST(Query_IntIndexed)
5028
{
2✔
5029
    Group g;
2✔
5030
    TableRef table = g.add_table("table");
2✔
5031
    auto col_id = table->add_column(type_Int, "id");
2✔
5032

5033
    for (int i = 0; i < 100; i++) {
202✔
5034
        table->create_object().set_all(i % 10);
200✔
5035
    }
200✔
5036

5037
    table->add_search_index(col_id);
2✔
5038
    Query q = table->where().equal(col_id, 1);
2✔
5039
    CHECK_EQUAL(q.count(), 10);
2✔
5040
    auto tv = q.find_all();
2✔
5041
    CHECK_EQUAL(tv.size(), 10);
2✔
5042
}
2✔
5043

5044
TEST(Query_IntIndexedRandom)
5045
{
2✔
5046
    Random random(random_int<int>());
2✔
5047

5048
    Group g;
2✔
5049
    TableRef table = g.add_table("table");
2✔
5050
    auto col_id = table->add_column(type_Int, "id");
2✔
5051
    auto col_val = table->add_column(type_Int, "val");
2✔
5052

5053
    for (int i = 0; i < 100000; i++) {
200,002✔
5054
        table->create_object().set(col_id, random.draw_int_max(20)).set(col_val, random.draw_int_max(100));
200,000✔
5055
    }
200,000✔
5056

5057
    for (const char* str : {"id == 1", "id == 1 and val > 50"}) {
4✔
5058
        table->remove_search_index(col_id);
4✔
5059
        Query q = table->query(str);
4✔
5060
        auto before = std::chrono::steady_clock().now();
4✔
5061
        size_t c1 = q.count();
4✔
5062
        auto after = std::chrono::steady_clock().now();
4✔
5063
        auto count_without_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5064
        before = std::chrono::steady_clock().now();
4✔
5065
        auto tv1 = q.find_all();
4✔
5066
        after = std::chrono::steady_clock().now();
4✔
5067
        auto find_all_without_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5068

5069
        table->add_search_index(col_id);
4✔
5070
        before = std::chrono::steady_clock().now();
4✔
5071
        size_t c2 = q.count();
4✔
5072
        after = std::chrono::steady_clock().now();
4✔
5073
        auto count_with_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5074
        CHECK_EQUAL(c1, c2);
4✔
5075
        before = std::chrono::steady_clock().now();
4✔
5076
        auto tv2 = q.find_all();
4✔
5077
        after = std::chrono::steady_clock().now();
4✔
5078
        auto find_all_with_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5079
        CHECK_EQUAL(tv1.size(), tv2.size());
4✔
5080
        CHECK_EQUAL(tv1.size(), c1);
4✔
5081

5082
        /*
5083
        std::cout << "Query: " << str << std::endl;
5084
        std::cout << "count without index: " << count_without_index << " us" << std::endl;
5085
        std::cout << "find all without index: " << find_all_without_index << " us" << std::endl;
5086
        std::cout << "count with index: " << count_with_index << " us" << std::endl;
5087
        std::cout << "find all with index: " << find_all_with_index << " us" << std::endl;
5088
         */
5089
        static_cast<void>(count_without_index);
4✔
5090
        static_cast<void>(find_all_without_index);
4✔
5091
        static_cast<void>(count_with_index);
4✔
5092
        static_cast<void>(find_all_with_index);
4✔
5093
    }
4✔
5094
}
2✔
5095

5096
TEST(Query_IntFindInNextLeaf)
5097
{
2✔
5098
    Group g;
2✔
5099
    TableRef table = g.add_table("table");
2✔
5100
    auto col_id = table->add_column(type_Int, "id");
2✔
5101

5102
    // num_misses > MAX_BPNODE_SIZE to check results on other leafs
5103
    constexpr int num_misses = 1000 * 2 + 10;
2✔
5104
    for (int i = 0; i < num_misses; i++) {
4,022✔
5105
        table->create_object().set(col_id, i % 10);
4,020✔
5106
    }
4,020✔
5107
    table->create_object().set(col_id, 20);
2✔
5108

5109
    auto check_results = [&]() {
4✔
5110
        for (int i = 0; i < 10; ++i) {
44✔
5111
            Query qi = table->where().equal(col_id, i);
40✔
5112
            CHECK_EQUAL(qi.count(), num_misses / 10);
40✔
5113
        }
40✔
5114
        Query q20 = table->where().equal(col_id, 20);
4✔
5115
        CHECK_EQUAL(q20.count(), 1);
4✔
5116
    };
4✔
5117
    check_results();
2✔
5118
    table->add_search_index(col_id);
2✔
5119
    check_results();
2✔
5120
}
2✔
5121

5122
TEST(Query_IntIndexOverLinkViewNotInTableOrder)
5123
{
2✔
5124
    Group g;
2✔
5125

5126
    TableRef child_table = g.add_table("child");
2✔
5127
    auto col_child_id = child_table->add_column(type_Int, "id");
2✔
5128
    child_table->add_search_index(col_child_id);
2✔
5129

5130
    auto k0 = child_table->create_object().set(col_child_id, 3).get_key();
2✔
5131
    auto k1 = child_table->create_object().set(col_child_id, 2).get_key();
2✔
5132

5133
    TableRef parent_table = g.add_table("parent");
2✔
5134
    auto col_parent_children = parent_table->add_column_list(*child_table, "children");
2✔
5135

5136
    auto parent_obj = parent_table->create_object();
2✔
5137
    auto children = parent_obj.get_linklist(col_parent_children);
2✔
5138
    // Add in reverse order so that the query node sees declining start indices
5139
    children.add(k1);
2✔
5140
    children.add(k0);
2✔
5141

5142
    // Query via linkview
5143
    Query q = child_table->where(children).equal(col_child_id, 3);
2✔
5144
    // Call find() twice. This caused a memory lead at some point. Must pass a memory leak test.
5145
    CHECK_EQUAL(k0, q.find());
2✔
5146
    CHECK_EQUAL(k0, q.find());
2✔
5147
    CHECK_EQUAL(k1, child_table->where(children).equal(col_child_id, 2).find());
2✔
5148

5149
    // Query directly
5150
    CHECK_EQUAL(k0, child_table->where().equal(col_child_id, 3).find());
2✔
5151
    CHECK_EQUAL(k1, child_table->where().equal(col_child_id, 2).find());
2✔
5152
}
2✔
5153

5154
TEST(Query_MixedTypeQuery)
5155
{
2✔
5156
    Group g;
2✔
5157
    auto table = g.add_table("Foo");
2✔
5158
    auto col_int = table->add_column(type_Int, "int");
2✔
5159
    auto col_double = table->add_column(type_Double, "double");
2✔
5160
    for (int64_t i = 0; i < 100; i++) {
202✔
5161
        table->create_object().set(col_int, i).set(col_double, 100. - i);
200✔
5162
    }
200✔
5163

5164
    auto tv = (table->column<Int>(col_int) > 9.5).find_all();
2✔
5165
    CHECK_EQUAL(tv.size(), 90);
2✔
5166
    auto tv1 = (table->column<Int>(col_int) > table->column<Double>(col_double)).find_all();
2✔
5167
    CHECK_EQUAL(tv1.size(), 49);
2✔
5168
}
2✔
5169

5170
TEST(Query_LinkListIntPastOneIsNull)
5171
{
2✔
5172
    Group g;
2✔
5173
    auto table_foo = g.add_table("Foo");
2✔
5174
    auto table_bar = g.add_table("Bar");
2✔
5175
    auto col_int = table_foo->add_column(type_Int, "int", true);
2✔
5176
    auto col_list = table_bar->add_column_list(*table_foo, "foo_link");
2✔
5177
    std::vector<util::Optional<int64_t>> values = {{0}, {1}, {2}, {util::none}};
2✔
5178
    auto bar_obj = table_bar->create_object();
2✔
5179
    auto list = bar_obj.get_linklist(col_list);
2✔
5180

5181
    for (size_t i = 0; i < values.size(); i++) {
10✔
5182
        auto obj = table_foo->create_object();
8✔
5183
        obj.set(col_int, values[i]);
8✔
5184
        list.add(obj.get_key());
8✔
5185
    }
8✔
5186

5187
    Query q = table_bar->link(col_list).column<Int>(col_int) == realm::null();
2✔
5188

5189
    CHECK_EQUAL(q.count(), 1);
2✔
5190
}
2✔
5191

5192
TEST(Query_LinkView_StrIndex)
5193
{
2✔
5194
    Group g;
2✔
5195
    auto table_foo = g.add_table_with_primary_key("class_Foo", type_String, "id");
2✔
5196
    auto col_id = table_foo->get_column_key("id");
2✔
5197

5198
    auto table_bar = g.add_table("class_Bar");
2✔
5199
    auto col_list = table_bar->add_column_list(*table_foo, "link");
2✔
5200

5201
    auto foo = table_foo->create_object_with_primary_key("97625fdc-d3f8-4c45-9a4d-dc8c83c657a1");
2✔
5202
    auto bar = table_bar->create_object();
2✔
5203
    auto ll = bar.get_linklist(col_list);
2✔
5204
    ll.add(foo.get_key());
2✔
5205

5206
    auto q = table_foo->where(ll).equal(col_id, "97625fdc-d3f8-4c45-9a4d-dc8c83c657a1");
2✔
5207
    CHECK_EQUAL(q.count(), 1);
2✔
5208
}
2✔
5209

5210
TEST(Query_StringOrShortStrings)
5211
{
2✔
5212
    Group g;
2✔
5213
    TableRef table = g.add_table("table");
2✔
5214
    auto col_value = table->add_column(type_String, "value");
2✔
5215

5216
    std::string strings[] = {"0", "1", "2"};
2✔
5217
    for (auto& str : strings) {
6✔
5218
        table->create_object().set(col_value, str);
6✔
5219
    }
6✔
5220

5221
    for (auto& str : strings) {
6✔
5222
        Query q = table->where()
6✔
5223
                      .group()
6✔
5224
                      .equal(col_value, StringData(str))
6✔
5225
                      .Or()
6✔
5226
                      .equal(col_value, StringData("not present"))
6✔
5227
                      .end_group();
6✔
5228
        CHECK_EQUAL(q.count(), 1);
6✔
5229
    }
6✔
5230
}
2✔
5231

5232
TEST(Query_StringOrMediumStrings)
5233
{
2✔
5234
    Group g;
2✔
5235
    TableRef table = g.add_table("table");
2✔
5236
    auto col_value = table->add_column(type_String, "value");
2✔
5237

5238
    std::string strings[] = {"0", "1", "2"};
2✔
5239
    for (auto& str : strings) {
6✔
5240
        str.resize(16, str[0]); // Make the strings long enough to require ArrayStringLong
6✔
5241
        table->create_object().set(col_value, str);
6✔
5242
    }
6✔
5243

5244
    for (auto& str : strings) {
6✔
5245
        Query q = table->where()
6✔
5246
                      .group()
6✔
5247
                      .equal(col_value, StringData(str))
6✔
5248
                      .Or()
6✔
5249
                      .equal(col_value, StringData("not present"))
6✔
5250
                      .end_group();
6✔
5251
        CHECK_EQUAL(q.count(), 1);
6✔
5252
    }
6✔
5253
}
2✔
5254

5255
TEST(Query_StringOrLongStrings)
5256
{
2✔
5257
    Group g;
2✔
5258
    TableRef table = g.add_table("table");
2✔
5259
    auto col_value = table->add_column(type_String, "value");
2✔
5260

5261
    std::string strings[] = {"0", "1", "2"};
2✔
5262
    for (auto& str : strings) {
6✔
5263
        str.resize(64, str[0]); // Make the strings long enough to require ArrayBigBlobs
6✔
5264
        table->create_object().set(col_value, str);
6✔
5265
    }
6✔
5266

5267
    for (auto& str : strings) {
6✔
5268
        Query q = table->where()
6✔
5269
                      .group()
6✔
5270
                      .equal(col_value, StringData(str))
6✔
5271
                      .Or()
6✔
5272
                      .equal(col_value, StringData("not present"))
6✔
5273
                      .end_group();
6✔
5274
        CHECK_EQUAL(q.count(), 1);
6✔
5275
    }
6✔
5276
}
2✔
5277

5278
TEST(Query_LinkViewAnd)
5279
{
2✔
5280
    Group g;
2✔
5281

5282
    TableRef child_table = g.add_table("child");
2✔
5283
    auto col_child_id = child_table->add_column(type_Int, "id");
2✔
5284
    auto col_child_name = child_table->add_column(type_String, "name");
2✔
5285

5286
    auto k0 = child_table->create_object().set(col_child_id, 3).set(col_child_name, "Adam").get_key();
2✔
5287
    auto k1 = child_table->create_object().set(col_child_id, 2).set(col_child_name, "Jeff").get_key();
2✔
5288

5289
    TableRef parent_table = g.add_table("parent");
2✔
5290
    auto col_parent_children = parent_table->add_column_list(*child_table, "children");
2✔
5291

5292
    auto parent_obj = parent_table->create_object();
2✔
5293
    auto children = parent_obj.get_linklist(col_parent_children);
2✔
5294
    children.add(k0);
2✔
5295
    children.add(k1);
2✔
5296

5297
    Query q1 = child_table->where(children).equal(col_child_id, 3);
2✔
5298
    Query q2 = child_table->where(children).equal(col_child_name, "Jeff");
2✔
5299
    CHECK_EQUAL(k0, q1.find());
2✔
5300
    CHECK_EQUAL(k1, q2.find());
2✔
5301
    q1.and_query(q2);
2✔
5302
    CHECK_NOT(q1.find());
2✔
5303
}
2✔
5304

5305
TEST(Query_LinksWithIndex)
5306
{
2✔
5307
    Group g;
2✔
5308

5309
    TableRef target = g.add_table("target");
2✔
5310
    auto col_value = target->add_column(type_String, "value");
2✔
5311
    auto col_date = target->add_column(type_Timestamp, "date");
2✔
5312
    target->add_search_index(col_value);
2✔
5313
    target->add_search_index(col_date);
2✔
5314

5315
    TableRef foo = g.add_table("foo");
2✔
5316
    auto col_foo = foo->add_column_list(*target, "linklist");
2✔
5317
    auto col_location = foo->add_column(type_String, "location");
2✔
5318
    auto col_score = foo->add_column(type_Int, "score");
2✔
5319
    foo->add_search_index(col_location);
2✔
5320
    foo->add_search_index(col_score);
2✔
5321

5322
    TableRef middle = g.add_table("middle");
2✔
5323
    auto col_link = middle->add_column(*target, "link");
2✔
5324

5325
    TableRef origin = g.add_table("origin");
2✔
5326
    auto col_linklist = origin->add_column_list(*middle, "linklist");
2✔
5327

5328
    std::vector<StringData> strings{"Copenhagen", "Aarhus", "Odense", "Aalborg", "Faaborg"};
2✔
5329
    auto now = std::chrono::system_clock::now();
2✔
5330
    std::chrono::seconds d{0};
2✔
5331
    for (auto& str : strings) {
10✔
5332
        target->create_object().set(col_value, str).set(col_date, Timestamp(now + d));
10✔
5333
        d = d + std::chrono::seconds{1};
10✔
5334
    }
10✔
5335

5336
    auto m0 = middle->create_object().set(col_link, target->find_first(col_value, strings[0])).get_key();
2✔
5337
    auto m1 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5338
    auto m2 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5339
    auto m3 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5340
    auto m4 = middle->create_object().set(col_link, target->find_first(col_value, strings[3])).get_key();
2✔
5341

5342
    auto obj0 = origin->create_object();
2✔
5343
    obj0.get_linklist(col_linklist).add(m3);
2✔
5344

5345
    auto obj1 = origin->create_object();
2✔
5346
    auto ll1 = obj1.get_linklist(col_linklist);
2✔
5347
    ll1.add(m1);
2✔
5348
    ll1.add(m2);
2✔
5349

5350
    origin->create_object().get_linklist(col_linklist).add(m4);
2✔
5351
    origin->create_object().get_linklist(col_linklist).add(m3);
2✔
5352
    auto obj4 = origin->create_object();
2✔
5353
    obj4.get_linklist(col_linklist).add(m0);
2✔
5354

5355
    Query q = origin->link(col_linklist).link(col_link).column<String>(col_value) == "Odense";
2✔
5356
    CHECK_EQUAL(q.find(), obj0.get_key());
2✔
5357
    auto tv = q.find_all();
2✔
5358
    CHECK_EQUAL(tv.size(), 3);
2✔
5359

5360
    auto ll = foo->create_object().set(col_location, "Fyn").set(col_score, 5).get_linklist(col_foo);
2✔
5361
    ll.add(target->find_first(col_value, strings[2]));
2✔
5362
    ll.add(target->find_first(col_value, strings[4]));
2✔
5363

5364
    Query q1 =
2✔
5365
        origin->link(col_linklist).link(col_link).backlink(*foo, col_foo).column<String>(col_location) == "Fyn";
2✔
5366
    CHECK_EQUAL(q1.find(), obj0.get_key());
2✔
5367
    Query q2 = origin->link(col_linklist).link(col_link).backlink(*foo, col_foo).column<Int>(col_score) == 5;
2✔
5368
    CHECK_EQUAL(q2.find(), obj0.get_key());
2✔
5369

5370
    // Make sure that changes in the table are reflected in the query result
5371
    middle->get_object(m3).set(col_link, target->find_first(col_value, strings[1]));
2✔
5372
    CHECK_EQUAL(q.find(), obj1.get_key());
2✔
5373

5374
    q = origin->link(col_linklist).link(col_link).column<Timestamp>(col_date) == Timestamp(now);
2✔
5375
    CHECK_EQUAL(q.find(), obj4.get_key());
2✔
5376
}
2✔
5377

5378
TEST(Query_NotImmediatelyBeforeKnownRange)
5379
{
2✔
5380
    Group g;
2✔
5381
    TableRef parent = g.add_table("parent");
2✔
5382
    TableRef child = g.add_table("child");
2✔
5383
    auto col_link = parent->add_column_list(*child, "list");
2✔
5384
    auto col_str = child->add_column(type_String, "value");
2✔
5385
    child->add_search_index(col_str);
2✔
5386

5387
    Obj obj = parent->create_object();
2✔
5388
    auto k0 = child->create_object().set(col_str, "a").get_key();
2✔
5389
    auto k1 = child->create_object().set(col_str, "b").get_key();
2✔
5390
    auto list = obj.get_linklist(col_link);
2✔
5391
    list.insert(0, k0);
2✔
5392
    list.insert(0, k1);
2✔
5393

5394
    Query q = child->where(list).Not().equal(col_str, "a");
2✔
5395
    CHECK_EQUAL(q.count(), 1);
2✔
5396
}
2✔
5397

5398
TEST_TYPES(Query_PrimaryKeySearchForNull, Prop<String>, Prop<Int>, Prop<ObjectId>, Nullable<String>, Nullable<Int>,
5399
           Nullable<ObjectId>)
5400
{
12✔
5401
    using type = typename TEST_TYPE::type;
12✔
5402
    using underlying_type = typename TEST_TYPE::underlying_type;
12✔
5403
    Table table;
12✔
5404
    TestValueGenerator gen;
12✔
5405
    auto col = table.add_column(TEST_TYPE::data_type, "property", TEST_TYPE::is_nullable);
12✔
5406
    table.set_primary_key_column(col);
12✔
5407
    underlying_type v0 = gen.convert_for_test<underlying_type>(42);
12✔
5408
    underlying_type v1 = gen.convert_for_test<underlying_type>(43);
12✔
5409
    Mixed mixed_null;
12✔
5410
    auto obj0 = table.create_object_with_primary_key(v0);
12✔
5411
    auto obj1 = table.create_object_with_primary_key(v1);
12✔
5412

5413
    auto verify_result_count = [&](Query& q, size_t expected_count) {
24✔
5414
        CHECK_EQUAL(q.count(), expected_count);
24✔
5415
        TableView tv = q.find_all();
24✔
5416
        CHECK_EQUAL(tv.size(), expected_count);
24✔
5417
    };
24✔
5418
    Query q = table.where().equal(col, v0);
12✔
5419
    verify_result_count(q, 1);
12✔
5420
    q = table.where().equal(col, v1);
12✔
5421
    verify_result_count(q, 1);
12✔
5422

5423
    CHECK_EQUAL(table.find_first(col, v0), obj0.get_key());
12✔
5424
    CHECK_EQUAL(table.find_first(col, v1), obj1.get_key());
12✔
5425
    CHECK_NOT(table.find_first(col, type{}));
12✔
5426
}
12✔
5427

5428
TEST_TYPES(Query_Mixed, std::true_type, std::false_type)
5429
{
4✔
5430
    bool has_index = TEST_TYPE::value;
4✔
5431
    constexpr bool exact_match = true;
4✔
5432
    constexpr bool insensitive_match = false;
4✔
5433
    Group g;
4✔
5434
    auto table = g.add_table("Foo");
4✔
5435
    auto origin = g.add_table("Origin");
4✔
5436
    auto col_any = table->add_column(type_Mixed, "any");
4✔
5437
    auto col_int = table->add_column(type_Int, "int");
4✔
5438
    auto col_link = origin->add_column(*table, "link");
4✔
5439
    auto col_mixed = origin->add_column(type_Mixed, "mixed");
4✔
5440
    auto col_links = origin->add_column_list(*table, "links");
4✔
5441

5442
    if (has_index)
4✔
5443
        table->add_search_index(col_any);
2✔
5444

5445
    size_t int_over_50 = 0;
4✔
5446
    size_t nb_strings = 0;
4✔
5447
    for (int64_t i = 0; i < 100; i++) {
404✔
5448
        if (i % 4) {
400✔
5449
            if (i > 50)
300✔
5450
                int_over_50++;
148✔
5451
            table->create_object().set(col_any, Mixed(i)).set(col_int, i);
300✔
5452
        }
300✔
5453
        else {
100✔
5454
            std::string str = "String" + util::to_string(i);
100✔
5455
            table->create_object().set(col_any, Mixed(str)).set(col_int, i);
100✔
5456
            nb_strings++;
100✔
5457
        }
100✔
5458
    }
400✔
5459
    std::string str2bin("String2Binary");
4✔
5460
    std::string str2bin_lowered = "string2binary";
4✔
5461

5462
    table->get_object(15).set(col_any, Mixed());
4✔
5463
    table->get_object(75).set(col_any, Mixed(75.));
4✔
5464
    table->get_object(28).set(col_any, Mixed(BinaryData(str2bin)));
4✔
5465
    table->get_object(25).set(col_any, Mixed(3.));
4✔
5466
    table->get_object(35).set(col_any, Mixed(Decimal128("3")));
4✔
5467
    table->get_object(80).set(col_any, Mixed("abcdefgh"));
4✔
5468
    table->get_object(81).set(col_any, Mixed(int64_t(0x6867666564636261)));
4✔
5469

5470
    auto it = table->begin();
4✔
5471
    for (int64_t i = 0; i < 10; i++) {
44✔
5472
        auto obj = origin->create_object();
40✔
5473
        auto ll = obj.get_linklist(col_links);
40✔
5474

5475
        obj.set(col_link, it->get_key());
40✔
5476
        if (i % 3) {
40✔
5477
            obj.set(col_mixed, Mixed(i));
24✔
5478
        }
24✔
5479
        else {
16✔
5480
            obj.set(col_mixed, Mixed(table->begin()->get_link()));
16✔
5481
        }
16✔
5482
        for (int64_t j = 0; j < 10; j++) {
440✔
5483
            ll.add(it->get_key());
400✔
5484
            ++it;
400✔
5485
        }
400✔
5486
    }
40✔
5487

5488
    // g.to_json(std::cout);
5489
    auto tv = (table->column<Mixed>(col_any) > Mixed(50)).find_all();
4✔
5490
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5491
    tv = (table->column<Mixed>(col_any) > 50).find_all();
4✔
5492
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5493
    tv = (table->column<Mixed>(col_any) == 37).find_all();
4✔
5494
    CHECK_EQUAL(tv.size(), 1);
4✔
5495
    tv = table->where().equal(col_any, Mixed(37)).find_all();
4✔
5496
    CHECK_EQUAL(tv.size(), 1);
4✔
5497
    tv = (table->column<Mixed>(col_any) >= 50).find_all();
4✔
5498
    CHECK_EQUAL(tv.size(), int_over_50 + 1);
4✔
5499
    tv = (table->column<Mixed>(col_any) <= 50).find_all();
4✔
5500
    CHECK_EQUAL(tv.size(), 100 - int_over_50 - nb_strings - 1);
4✔
5501
    tv = (table->column<Mixed>(col_any) < 50).find_all();
4✔
5502
    CHECK_EQUAL(tv.size(), 100 - int_over_50 - nb_strings - 2);
4✔
5503
    tv = (table->column<Mixed>(col_any) < 50 || table->column<Mixed>(col_any) > 50).find_all();
4✔
5504
    CHECK_EQUAL(tv.size(), 100 - nb_strings - 2);
4✔
5505
    tv = (table->column<Mixed>(col_any) != 50).find_all();
4✔
5506
    CHECK_EQUAL(tv.size(), 99);
4✔
5507

5508
    tv = table->where().greater(col_any, Mixed(50)).find_all();
4✔
5509
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5510
    tv = table->where().greater(col_any, 50).find_all();
4✔
5511
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5512

5513
    tv = table->where().equal(col_any, null()).find_all();
4✔
5514
    CHECK_EQUAL(tv.size(), 1);
4✔
5515
    tv = table->where().not_equal(col_any, null()).find_all();
4✔
5516
    CHECK_EQUAL(tv.size(), 99);
4✔
5517

5518
    tv = table->where().begins_with(col_any, StringData("String2")).find_all(); // 20, 24
4✔
5519
    CHECK_EQUAL(tv.size(), 2);
4✔
5520
    tv = table->where().begins_with(col_any, BinaryData("String2", 7)).find_all(); // 28
4✔
5521
    CHECK_EQUAL(tv.size(), 1);
4✔
5522
    tv = table->where().begins_with(col_any, Mixed(75.), exact_match).find_all();
4✔
5523
    CHECK_EQUAL(tv.size(), 0);
4✔
5524
    tv = table->where().begins_with(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5525
    CHECK_EQUAL(tv.size(), 0);
4✔
5526

5527
    tv = table->where().contains(col_any, StringData("TRIN"), insensitive_match).find_all();
4✔
5528
    CHECK_EQUAL(tv.size(), 23);
4✔
5529
    tv = table->where().contains(col_any, Mixed("TRIN"), insensitive_match).find_all();
4✔
5530
    CHECK_EQUAL(tv.size(), 23);
4✔
5531
    tv = table->where().contains(col_any, Mixed("TRIN"), exact_match).find_all();
4✔
5532
    CHECK_EQUAL(tv.size(), 0);
4✔
5533
    tv = table->where().contains(col_any, Mixed(75.), exact_match).find_all();
4✔
5534
    CHECK_EQUAL(tv.size(), 0);
4✔
5535
    tv = table->where().contains(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5536
    CHECK_EQUAL(tv.size(), 0);
4✔
5537

5538
    tv = table->where().like(col_any, StringData("Strin*")).find_all();
4✔
5539
    CHECK_EQUAL(tv.size(), 23);
4✔
5540
    tv = table->where().like(col_any, Mixed(75.), exact_match).find_all();
4✔
5541
    CHECK_EQUAL(tv.size(), 0);
4✔
5542
    tv = table->where().like(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5543
    CHECK_EQUAL(tv.size(), 0);
4✔
5544

5545
    tv = table->where().ends_with(col_any, StringData("4")).find_all(); // 4, 24, 44, 64, 84
4✔
5546
    CHECK_EQUAL(tv.size(), 5);
4✔
5547
    char bin[1] = {0x34};
4✔
5548
    tv = table->where().ends_with(col_any, BinaryData(bin)).find_all();
4✔
5549
    CHECK_EQUAL(tv.size(), 0);
4✔
5550
    bin[0] = 'y';
4✔
5551
    tv = table->where().ends_with(col_any, BinaryData(bin)).find_all(); // 28
4✔
5552
    CHECK_EQUAL(tv.size(), 1);
4✔
5553
    tv = table->where().ends_with(col_any, Mixed(75.), exact_match).find_all();
4✔
5554
    CHECK_EQUAL(tv.size(), 0);
4✔
5555
    tv = table->where().ends_with(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5556
    CHECK_EQUAL(tv.size(), 0);
4✔
5557

5558
    tv = table->where().equal(col_any, StringData(str2bin), exact_match).find_all();
4✔
5559
    CHECK_EQUAL(tv.size(), 0);
4✔
5560
    tv = table->where().equal(col_any, BinaryData(str2bin), exact_match).find_all();
4✔
5561
    CHECK_EQUAL(tv.size(), 1);
4✔
5562
    tv = table->where().equal(col_any, Mixed{BinaryData(str2bin)}, exact_match).find_all();
4✔
5563
    CHECK_EQUAL(tv.size(), 1);
4✔
5564

5565
    tv = table->where().equal(col_any, Mixed{str2bin_lowered}, insensitive_match).find_all();
4✔
5566
    CHECK_EQUAL(tv.size(), 0);
4✔
5567
    tv = table->where().equal(col_any, Mixed{BinaryData(str2bin_lowered)}, insensitive_match).find_all();
4✔
5568
    CHECK_EQUAL(tv.size(), 1);
4✔
5569

5570
    tv = table->where().not_equal(col_any, Mixed{str2bin_lowered}, insensitive_match).find_all();
4✔
5571
    CHECK_EQUAL(tv.size(), 100);
4✔
5572
    tv = table->where().not_equal(col_any, Mixed{BinaryData(str2bin_lowered)}, insensitive_match).find_all();
4✔
5573
    CHECK_EQUAL(tv.size(), 99);
4✔
5574

5575
    tv = table->where().equal(col_any, StringData(), insensitive_match).find_all();
4✔
5576
    CHECK_EQUAL(tv.size(), 1);
4✔
5577
    tv = table->where().equal(col_any, StringData(), exact_match).find_all();
4✔
5578
    CHECK_EQUAL(tv.size(), 1);
4✔
5579

5580
    tv = table->where().equal(col_any, Mixed(), insensitive_match).find_all();
4✔
5581
    CHECK_EQUAL(tv.size(), 1);
4✔
5582
    tv = table->where().equal(col_any, Mixed(), exact_match).find_all();
4✔
5583
    CHECK_EQUAL(tv.size(), 1);
4✔
5584

5585
    tv = table->where().equal(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5586
    CHECK_EQUAL(tv.size(), 1);
4✔
5587
    tv = table->where().equal(col_any, Mixed(75.), exact_match).find_all();
4✔
5588
    CHECK_EQUAL(tv.size(), 1);
4✔
5589

5590
    tv = (table->column<Mixed>(col_any) == StringData("String48")).find_all();
4✔
5591
    CHECK_EQUAL(tv.size(), 1);
4✔
5592
    tv = (table->column<Mixed>(col_any) == 3.).find_all();
4✔
5593
    CHECK_EQUAL(tv.size(), 3);
4✔
5594
    tv = (table->column<Mixed>(col_any) == table->column<Int>(col_int)).find_all();
4✔
5595
    CHECK_EQUAL(tv.size(), 71);
4✔
5596

5597
    tv = (table->column<Mixed>(col_any) == StringData("abcdefgh")).find_all();
4✔
5598
    CHECK_EQUAL(tv.size(), 1);
4✔
5599
    tv = (table->column<Mixed>(col_any) == StringData("ABCDEFGH")).find_all();
4✔
5600
    CHECK_EQUAL(tv.size(), 0);
4✔
5601

5602
    ObjLink link_to_first = table->begin()->get_link();
4✔
5603
    tv = (origin->column<Mixed>(col_mixed) == Mixed(link_to_first)).find_all();
4✔
5604
    CHECK_EQUAL(tv.size(), 4);
4✔
5605
    tv = (origin->where().links_to(col_mixed, link_to_first)).find_all();
4✔
5606
    CHECK_EQUAL(tv.size(), 4);
4✔
5607
    tv = (origin->where().equal(col_link, Mixed(link_to_first))).find_all();
4✔
5608
    CHECK_EQUAL(tv.size(), 1);
4✔
5609
    tv = (origin->where().equal(col_links, Mixed(link_to_first))).find_all();
4✔
5610
    CHECK_EQUAL(tv.size(), 1);
4✔
5611
    auto q = origin->where().not_equal(col_links, Mixed(link_to_first));
4✔
5612
    auto d = q.get_description();
4✔
5613
    tv = q.find_all();
4✔
5614
    CHECK_EQUAL(tv.size(), 10);
4✔
5615
    q = origin->query(d);
4✔
5616
    tv = q.find_all();
4✔
5617
    CHECK_EQUAL(tv.size(), 10);
4✔
5618
    tv = (origin->link(col_links).column<Mixed>(col_any) > 50).find_all();
4✔
5619
    CHECK_EQUAL(tv.size(), 5);
4✔
5620
    tv = (origin->link(col_link).column<Mixed>(col_any) > 50).find_all();
4✔
5621
    CHECK_EQUAL(tv.size(), 2);
4✔
5622
    std::string bin2str_truncated = str2bin_lowered.substr(0, 10);
4✔
5623
    tv = (origin->link(col_links).column<Mixed>(col_any).contains(bin2str_truncated, insensitive_match)).find_all();
4✔
5624
    CHECK_EQUAL(tv.size(), 0);
4✔
5625
    tv = (origin->link(col_links).column<Mixed>(col_any).contains(BinaryData(bin2str_truncated), insensitive_match))
4✔
5626
             .find_all();
4✔
5627
    CHECK_EQUAL(tv.size(), 1);
4✔
5628
    tv = (origin->link(col_links).column<Mixed>(col_any).like("*ring*", insensitive_match)).find_all();
4✔
5629
    CHECK_EQUAL(tv.size(), 10);
4✔
5630
    tv = (origin->link(col_links).column<Mixed>(col_any).begins_with("String", exact_match)).find_all();
4✔
5631
    CHECK_EQUAL(tv.size(), 10);
4✔
5632
    tv = (origin->link(col_links).column<Mixed>(col_any).ends_with("g40", exact_match)).find_all();
4✔
5633
    CHECK_EQUAL(tv.size(), 1);
4✔
5634
}
4✔
5635

5636
TEST(Query_NestedListNull)
5637
{
2✔
5638
    SHARED_GROUP_TEST_PATH(path);
2✔
5639
    auto hist = make_in_realm_history();
2✔
5640
    DBRef db = DB::create(*hist, path);
2✔
5641
    auto tr = db->start_write();
2✔
5642
    auto foo = tr->add_table("foo");
2✔
5643
    auto col_any = foo->add_column(type_Mixed, "mixed");
2✔
5644

5645
    const char* listOfListOfNull = R"([[null]])";
2✔
5646

5647
    foo->create_object().set_json(col_any, R"("not a list")");
2✔
5648
    foo->create_object().set_json(col_any, listOfListOfNull);
2✔
5649
    foo->create_object().set_json(col_any, listOfListOfNull);
2✔
5650
    foo->create_object().set_json(col_any, listOfListOfNull);
2✔
5651

5652
    CHECK_EQUAL(foo->query("mixed[0][0] == null").count(), 3);
2✔
5653
    CHECK_EQUAL(foo->query("mixed[0][5] == null").count(), 0);
2✔
5654
    CHECK_EQUAL(foo->query("mixed[0][*] == null").count(), 3);
2✔
5655
}
2✔
5656

5657
TEST(Query_NestedDictionaryNull)
5658
{
2✔
5659
    SHARED_GROUP_TEST_PATH(path);
2✔
5660
    auto hist = make_in_realm_history();
2✔
5661
    DBRef db = DB::create(*hist, path);
2✔
5662
    auto tr = db->start_write();
2✔
5663
    auto foo = tr->add_table("foo");
2✔
5664
    auto col_any = foo->add_column(type_Mixed, "mixed");
2✔
5665

5666
    const char* dictOfDictOfNull = R"({ "nestedDict": { "nullValue": null }})";
2✔
5667

5668
    foo->create_object().set_json(col_any, R"("not a dictionary")");
2✔
5669
    foo->create_object().set_json(col_any, dictOfDictOfNull);
2✔
5670
    foo->create_object().set_json(col_any, dictOfDictOfNull);
2✔
5671
    foo->create_object().set_json(col_any, dictOfDictOfNull);
2✔
5672

5673
    CHECK_EQUAL(foo->query("mixed['nestedDict']['nullValue'] == null").count(), 3);
2✔
5674
    CHECK_EQUAL(foo->query("mixed.nestedDict.nullValue == null").count(), 3);
2✔
5675
    CHECK_EQUAL(foo->query("mixed['nestedDict']['foo'] == null").count(), 3);
2✔
5676
    CHECK_EQUAL(foo->query("mixed.nestedDict.foo == null").count(), 3);
2✔
5677
    CHECK_EQUAL(foo->query("mixed.nestedDict[*] == null").count(), 3);
2✔
5678
    CHECK_EQUAL(foo->query("mixed.nestedDict[*].@type == 'null'").count(), 3);
2✔
5679
}
2✔
5680

5681
TEST(Query_ListOfMixed)
5682
{
2✔
5683
    Group g;
2✔
5684
    auto table = g.add_table("Foo");
2✔
5685
    auto origin = g.add_table("Origin");
2✔
5686
    auto col_any = table->add_column_list(type_Mixed, "any");
2✔
5687
    auto col_int = origin->add_column(type_Int, "int");
2✔
5688
    auto col_link = origin->add_column(*table, "link");
2✔
5689
    auto col_links = origin->add_column_list(*table, "links");
2✔
5690
    size_t expected = 0;
2✔
5691

5692
    for (int64_t i = 0; i < 100; i++) {
202✔
5693
        auto obj = table->create_object();
200✔
5694
        auto list = obj.get_list<Mixed>(col_any);
200✔
5695
        if (i % 4) {
200✔
5696
            list.add(i);
150✔
5697
            if (i > 50)
150✔
5698
                expected++;
74✔
5699
        }
150✔
5700
        else if ((i % 10) == 0) {
50✔
5701
            list.add(100.);
10✔
5702
            expected++;
10✔
5703
        }
10✔
5704
        if (i % 3) {
200✔
5705
            std::string str = "String" + util::to_string(i);
132✔
5706
            list.add(str);
132✔
5707
        }
132✔
5708
    }
200✔
5709
    auto it = table->begin();
2✔
5710
    for (int64_t i = 0; i < 10; i++) {
22✔
5711
        auto obj = origin->create_object();
20✔
5712
        obj.set(col_int, 100);
20✔
5713
        auto ll = obj.get_linklist(col_links);
20✔
5714

5715
        obj.set(col_link, it->get_key());
20✔
5716
        for (int64_t j = 0; j < 10; j++) {
220✔
5717
            ll.add(it->get_key());
200✔
5718
            ++it;
200✔
5719
        }
200✔
5720
    }
20✔
5721

5722
    // g.to_json(std::cout, 2);
5723
    auto tv = (table->column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5724
    CHECK_EQUAL(tv.size(), expected);
2✔
5725
    tv = (origin->link(col_links).column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5726
    CHECK_EQUAL(tv.size(), 8);
2✔
5727
    tv = (origin->link(col_link).column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5728
    CHECK_EQUAL(tv.size(), 7);
2✔
5729
    tv = (origin->link(col_links).column<Lst<Mixed>>(col_any) == origin->column<Int>(col_int)).find_all();
2✔
5730
    CHECK_EQUAL(tv.size(), 5);
2✔
5731
}
2✔
5732

5733
TEST(Query_Dictionary)
5734
{
2✔
5735
    Group g;
2✔
5736
    auto foo = g.add_table("foo");
2✔
5737
    auto origin = g.add_table("origin");
2✔
5738
    auto col_dict = foo->add_column_dictionary(type_Mixed, "dict");
2✔
5739
    auto col_link = origin->add_column(*foo, "link");
2✔
5740
    auto col_links = origin->add_column_list(*foo, "links");
2✔
5741
    size_t expected = 0;
2✔
5742

5743
    for (int64_t i = 0; i < 100; i++) {
202✔
5744
        auto obj = foo->create_object();
200✔
5745
        Dictionary dict = obj.get_dictionary(col_dict);
200✔
5746
        bool incr = false;
200✔
5747
        if (i % 4) {
200✔
5748
            dict.insert("Value", i);
150✔
5749
            if (i > 50)
150✔
5750
                incr = true;
74✔
5751
        }
150✔
5752
        else if ((i % 10) == 0) {
50✔
5753
            dict.insert("Foo", "Bar");
10✔
5754
            dict.insert("Value", 100.);
10✔
5755
            incr = true;
10✔
5756
        }
10✔
5757
        if (i % 3) {
200✔
5758
            std::string str = "String" + util::to_string(i);
132✔
5759
            dict.insert("Value", str);
132✔
5760
            incr = false;
132✔
5761
        }
132✔
5762
        if (i == 76) {
200✔
5763
            dict.insert("Value", Mixed());
2✔
5764
        }
2✔
5765
        dict.insert("Dummy", i);
200✔
5766
        if (incr) {
200✔
5767
            expected++;
30✔
5768
        }
30✔
5769
    }
200✔
5770

5771
    auto it = foo->begin();
2✔
5772
    for (int64_t i = 0; i < 10; i++) {
22✔
5773
        auto obj = origin->create_object();
20✔
5774

5775
        obj.set(col_link, it->get_key());
20✔
5776

5777
        auto ll = obj.get_linklist(col_links);
20✔
5778
        for (int64_t j = 0; j < 10; j++) {
220✔
5779
            ll.add(it->get_key());
200✔
5780
            ++it;
200✔
5781
        }
200✔
5782
    }
20✔
5783

5784
    // g.to_json(std::cout);
5785
    auto tv = (foo->column<Dictionary>(col_dict).key("Value") > Mixed(50)).find_all();
2✔
5786
    CHECK_EQUAL(tv.size(), expected);
2✔
5787
    tv = (foo->column<Dictionary>(col_dict) > 50).find_all(); // Any key will do
2✔
5788
    CHECK_EQUAL(tv.size(), 50);                               // 0 and 51..99
2✔
5789

5790
    tv = (origin->link(col_link).column<Dictionary>(col_dict).key("Value") > 50).find_all();
2✔
5791
    CHECK_EQUAL(tv.size(), 3);
2✔
5792
    tv = (origin->link(col_links).column<Dictionary>(col_dict).key("Value") > 50).find_all();
2✔
5793
    CHECK_EQUAL(tv.size(), 6);
2✔
5794
    tv = (origin->link(col_links).column<Dictionary>(col_dict) > 50).find_all();
2✔
5795
    CHECK_EQUAL(tv.size(), 6);
2✔
5796
    tv = (origin->link(col_links).column<Dictionary>(col_dict).key("Value") == null()).find_all();
2✔
5797
    CHECK_EQUAL(tv.size(), 7);
2✔
5798

5799
    tv = (foo->column<Dictionary>(col_dict).keys().begins_with("F")).find_all();
2✔
5800
    CHECK_EQUAL(tv.size(), 5);
2✔
5801
    tv = (origin->link(col_link).column<Dictionary>(col_dict).keys() == "Foo").find_all();
2✔
5802
    CHECK_EQUAL(tv.size(), 5);
2✔
5803
}
2✔
5804

5805
TEST(Query_DictionaryTypedLinks)
5806
{
2✔
5807
    Group g;
2✔
5808
    auto dog = g.add_table("dog");
2✔
5809
    auto cat = g.add_table("cat");
2✔
5810
    auto person = g.add_table("person");
2✔
5811
    auto col_data = person->add_column_dictionary(type_Mixed, "data");
2✔
5812
    auto col_dog_name = dog->add_column(type_String, "Name");
2✔
5813
    auto col_dog_parent = dog->add_column(*dog, "Parent");
2✔
5814
    auto col_cat_name = cat->add_column(type_String, "Name");
2✔
5815

5816
    auto fido = dog->create_object().set(col_dog_name, "Fido");
2✔
5817
    auto pluto = dog->create_object().set(col_dog_name, "Pluto");
2✔
5818
    pluto.set(col_dog_parent, fido.get_key());
2✔
5819
    dog->create_object().set(col_dog_name, "Vaks");
2✔
5820
    auto marie = cat->create_object().set(col_cat_name, "Marie");
2✔
5821
    cat->create_object().set(col_cat_name, "Berlioz");
2✔
5822
    cat->create_object().set(col_cat_name, "Toulouse");
2✔
5823

5824
    auto john = person->create_object().get_dictionary(col_data);
2✔
5825
    auto paul = person->create_object().get_dictionary(col_data);
2✔
5826

5827
    john.insert("Name", "John");
2✔
5828
    john.insert("Pet", pluto);
2✔
5829

5830
    paul.insert("Name", "Paul");
2✔
5831
    paul.insert("Pet", marie);
2✔
5832

5833
    // g.to_json(std::cout, 5);
5834

5835
    auto cnt = person->query("data.Pet.Name == 'Pluto'").count();
2✔
5836
    CHECK_EQUAL(cnt, 1);
2✔
5837
    cnt = person->query("data.Pet.Name == 'Marie'").count();
2✔
5838
    CHECK_EQUAL(cnt, 1);
2✔
5839
    cnt = person->query("data.Pet.Parent.Name == 'Fido'").count();
2✔
5840
    CHECK_EQUAL(cnt, 1);
2✔
5841
}
2✔
5842

5843
TEST(Query_TypeOfValue)
5844
{
2✔
5845
    Group g;
2✔
5846
    auto table = g.add_table("Foo");
2✔
5847
    auto origin = g.add_table("Origin");
2✔
5848
    auto col_any = table->add_column(type_Mixed, "mixed");
2✔
5849
    auto col_int = table->add_column(type_Int, "int");
2✔
5850
    auto col_primitive_list = table->add_column_list(type_Mixed, "list");
2✔
5851
    auto col_link = origin->add_column(*table, "link");
2✔
5852
    auto col_links = origin->add_column_list(*table, "links");
2✔
5853
    size_t nb_ints = 0;
2✔
5854
    size_t nb_strings = 0;
2✔
5855
    for (int64_t i = 0; i < 100; i++) {
202✔
5856
        if (i % 4) {
200✔
5857
            nb_ints++;
150✔
5858
            table->create_object().set(col_any, Mixed(i)).set(col_int, i);
150✔
5859
        }
150✔
5860
        else {
50✔
5861
            std::string str = "String" + util::to_string(i);
50✔
5862
            table->create_object().set(col_any, Mixed(str)).set(col_int, i);
50✔
5863
            nb_strings++;
50✔
5864
        }
50✔
5865
    }
200✔
5866
    std::string bin_data("String2Binary");
2✔
5867
    table->get_object(15).set(col_any, Mixed());
2✔
5868
    nb_ints--;
2✔
5869
    table->get_object(75).set(col_any, Mixed(75.));
2✔
5870
    nb_ints--;
2✔
5871
    table->get_object(28).set(col_any, Mixed(BinaryData(bin_data)));
2✔
5872
    nb_strings--;
2✔
5873
    table->get_object(25).set(col_any, Mixed(3.));
2✔
5874
    nb_ints--;
2✔
5875
    table->get_object(35).set(col_any, Mixed(Decimal128("3")));
2✔
5876
    nb_ints--;
2✔
5877

5878
    auto list_0 = table->get_object(0).get_list<Mixed>(col_primitive_list);
2✔
5879
    list_0.add(Mixed{1});
2✔
5880
    list_0.add(Mixed{Decimal128(10)});
2✔
5881
    list_0.add(Mixed{Double{100}});
2✔
5882
    auto list_1 = table->get_object(1).get_list<Mixed>(col_primitive_list);
2✔
5883
    list_1.add(Mixed{std::string("hello")});
2✔
5884
    list_1.add(Mixed{1000});
2✔
5885

5886
    auto it = table->begin();
2✔
5887
    for (int64_t i = 0; i < 10; i++) {
22✔
5888
        auto obj = origin->create_object();
20✔
5889
        auto ll = obj.get_linklist(col_links);
20✔
5890

5891
        obj.set(col_link, it->get_key());
20✔
5892
        for (int64_t j = 0; j < 10; j++) {
220✔
5893
            ll.add(it->get_key());
200✔
5894
            ++it;
200✔
5895
        }
200✔
5896
    }
20✔
5897

5898
    auto tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string_view("string"))).find_all();
2✔
5899
    CHECK_EQUAL(tv.size(), nb_strings);
2✔
5900
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string_view("double"))).find_all();
2✔
5901
    CHECK_EQUAL(tv.size(), 2);
2✔
5902
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string_view("Decimal128"))).find_all();
2✔
5903
    CHECK_EQUAL(tv.size(), 1);
2✔
5904
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(BinaryData(bin_data))).find_all();
2✔
5905
    CHECK_EQUAL(tv.size(), 1);
2✔
5906
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(util::none)).find_all();
2✔
5907
    CHECK_EQUAL(tv.size(), 1);
2✔
5908
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(type_String)).find_all();
2✔
5909
    CHECK_EQUAL(tv.size(), nb_strings);
2✔
5910
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(col_int)).find_all();
2✔
5911
    CHECK_EQUAL(tv.size(), nb_ints);
2✔
5912
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(col_int)).find_all();
2✔
5913
    CHECK_EQUAL(tv.size(), 2);
2✔
5914
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(type_Decimal)).find_all();
2✔
5915
    CHECK_EQUAL(tv.size(), 1);
2✔
5916
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(type_Int)).find_all();
2✔
5917
    CHECK_EQUAL(tv.size(), 2);
2✔
5918
    tv = (table->column<Lst<Mixed>>(col_primitive_list, ExpressionComparisonType::All).type_of_value() ==
2✔
5919
              TypeOfValue(TypeOfValue::Attribute::Numeric) &&
2✔
5920
          table->column<Lst<Mixed>>(col_primitive_list).size() > 0)
2✔
5921
             .find_all();
2✔
5922
    CHECK_EQUAL(tv.size(), 1);
2✔
5923
}
2✔
5924

5925
TEST(Query_links_to_with_bpnode_split)
5926
{
2✔
5927
    // The bug here is that LinksToNode would read a LinkList as a simple Array
5928
    // instead of a BPTree. So this only worked when the number of items < REALM_MAX_BPNODE_SIZE
5929
    Group g;
2✔
5930
    auto table = g.add_table("Foo");
2✔
5931
    auto origin = g.add_table("Origin");
2✔
5932
    auto col_int = table->add_column(type_Int, "int");
2✔
5933
    auto col_link = origin->add_column(*table, "link");
2✔
5934
    auto col_links = origin->add_column_list(*table, "links");
2✔
5935
    constexpr size_t num_items = REALM_MAX_BPNODE_SIZE + 1;
2✔
5936
    for (size_t i = 0; i < num_items; ++i) {
2,004✔
5937
        table->create_object().set(col_int, int64_t(i));
2,002✔
5938
    }
2,002✔
5939
    for (size_t i = 0; i < num_items; ++i) {
2,004✔
5940
        auto obj = origin->create_object();
2,002✔
5941
        auto it_i = table->begin();
2,002✔
5942
        it_i.go(i);
2,002✔
5943
        obj.set(col_link, it_i->get_key());
2,002✔
5944
        auto list = obj.get_linklist(col_links);
2,002✔
5945
        for (auto it = table->begin(); it != table->end(); ++it) {
2,006,004✔
5946
            list.add(it->get_key());
2,004,002✔
5947
        }
2,004,002✔
5948
    }
2,002✔
5949

5950
    for (auto it = table->begin(); it != table->end(); ++it) {
2,004✔
5951
        Query q = origin->where().links_to(col_links, it->get_key());
2,002✔
5952
        CHECK_EQUAL(q.count(), num_items);
2,002✔
5953
        Query q2 = origin->where().links_to(col_link, it->get_key());
2,002✔
5954
        CHECK_EQUAL(q2.count(), 1);
2,002✔
5955
    }
2,002✔
5956
}
2✔
5957

5958
TEST_TYPES(Query_ManyIn, Prop<Int>, Prop<String>, Prop<Float>, Prop<Double>, Prop<Timestamp>, Prop<UUID>,
5959
           Prop<ObjectId>, Prop<Decimal128>, Prop<BinaryData>, Prop<Mixed>, Nullable<Int>, Nullable<String>,
5960
           Nullable<Float>, Nullable<Double>, Nullable<Timestamp>, Nullable<UUID>, Nullable<ObjectId>,
5961
           Nullable<Decimal128>, Nullable<BinaryData>, Indexed<Int>, Indexed<String>, Indexed<Timestamp>,
5962
           Indexed<UUID>, Indexed<ObjectId>, Indexed<Mixed>)
5963
{
50✔
5964
    using type = typename TEST_TYPE::type;
50✔
5965
    TestValueGenerator gen;
50✔
5966
    Group g;
50✔
5967

5968
    auto t = g.add_table("foo");
50✔
5969
    auto col = t->add_column(TEST_TYPE::data_type, "value", TEST_TYPE::is_nullable);
50✔
5970
    if (TEST_TYPE::is_indexed) {
50✔
5971
        t->add_search_index(col);
12✔
5972
    }
12✔
5973
    constexpr size_t num_values = 200;
50✔
5974
    std::vector<int64_t> seed_values;
50✔
5975
    seed_values.resize(num_values);
50✔
5976
    std::iota(seed_values.begin(), seed_values.end(), 1000);
50✔
5977
    auto values = gen.values_from_int<type>(seed_values);
50✔
5978
    for (auto& v : values) {
10,000✔
5979
        t->create_object().set_any(col, v);
10,000✔
5980
    }
10,000✔
5981
    if (TEST_TYPE::is_nullable) {
50✔
5982
        t->create_object(); // null
18✔
5983
    }
18✔
5984
    std::vector<Mixed> mixed_vals;
50✔
5985
    mixed_vals.insert(mixed_vals.begin(), values.begin(), values.end());
50✔
5986

5987
    CHECK_EQUAL(t->where().in(col, mixed_vals.data(), mixed_vals.data() + mixed_vals.size()).count(), num_values);
50✔
5988

5989
    mixed_vals.push_back(Mixed{});                  // exists for nullable types
50✔
5990
    mixed_vals.push_back(Mixed{ObjectId()});        // value does not exist in data
50✔
5991
    mixed_vals.push_back(Mixed{Timestamp{-1, -1}}); // value does not exist in data
50✔
5992

5993
    CHECK_EQUAL(t->where().in(col, mixed_vals.data(), mixed_vals.data() + mixed_vals.size()).count(),
50✔
5994
                TEST_TYPE::is_nullable ? num_values + 1 : num_values);
50✔
5995
    // empty values for begin == end
5996
    CHECK_EQUAL(t->where().in(col, mixed_vals.data(), mixed_vals.data()).count(), 0);
50✔
5997
    CHECK_EQUAL(t->where().in(col, nullptr, nullptr).count(), 0);
50✔
5998
    // subset of existing values
5999
    CHECK_EQUAL(t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 2).count(),
50✔
6000
                1); // assumes test data at mixed_vals[1] is unique
50✔
6001
    CHECK_EQUAL(t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 3).count(),
50✔
6002
                2); // same for mixed_vals[2]
50✔
6003

6004
    CHECK(mixed_vals[1] != mixed_vals[2]); // the following relies on test data uniqueness
50✔
6005
    TableView tv = t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 3).find_all();
50✔
6006
    CHECK_EQUAL(tv.size(), 2);
50✔
6007
    auto first = tv.get_object(0).get<type>(col);
50✔
6008
    auto second = tv.get_object(1).get<type>(col);
50✔
6009
    bool order = first == mixed_vals[1];
50✔
6010
    CHECK_EQUAL(first, order ? mixed_vals[1] : mixed_vals[2]);
50✔
6011
    CHECK_EQUAL(second, order ? mixed_vals[2] : mixed_vals[1]);
50✔
6012
    size_t count_of_two_ins = t->where()
50✔
6013
                                  .in(col, mixed_vals.data() + 1, mixed_vals.data() + 2)
50✔
6014
                                  .Or()
50✔
6015
                                  .in(col, mixed_vals.data() + 2, mixed_vals.data() + 3)
50✔
6016
                                  .count();
50✔
6017
    size_t count_of_one_in = t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 3).count();
50✔
6018
    CHECK_EQUAL(count_of_one_in, 2);
50✔
6019
    CHECK_EQUAL(count_of_two_ins, 2);
50✔
6020
}
50✔
6021

6022
TEST(Query_ManyIntConditionsAgg)
6023
{
2✔
6024
    SHARED_GROUP_TEST_PATH(path);
2✔
6025
    DBRef db = DB::create(path);
2✔
6026

6027
    constexpr size_t num_values = 1000;
2✔
6028
    {
2✔
6029
        auto wt = db->start_write();
2✔
6030
        auto table = wt->add_table("Foo");
2✔
6031
        auto col_int = table->add_column(type_Int, "int", true);
2✔
6032
        for (size_t i = 0; i < num_values; i++) {
2,002✔
6033
            table->create_object().set(col_int, int64_t(i));
2,000✔
6034
        }
2,000✔
6035
        table->create_object(); // Contains null
2✔
6036
        wt->commit();
2✔
6037
    }
2✔
6038
    {
2✔
6039
        auto rt = db->start_read();
2✔
6040
        auto table = rt->get_table("Foo");
2✔
6041
        auto col = table->get_column_key("int");
2✔
6042

6043
        auto check_query = [&](Query& q) {
4✔
6044
            CHECK_EQUAL(q.count(), num_values);
4✔
6045
            TableView tv = q.find_all();
4✔
6046
            CHECK_EQUAL(tv.size(), num_values);
4✔
6047
            CHECK_EQUAL(q.min(col)->get<Int>(), 0);
4✔
6048
            CHECK_EQUAL(q.max(col)->get<Int>(), num_values - 1);
4✔
6049
            CHECK_EQUAL(q.avg(col)->get<double>(), double(num_values - 1) / 2.0);
4✔
6050
            CHECK_EQUAL(q.sum(col)->get<Int>(), (num_values / 2) * (num_values - 1));
4✔
6051
        };
4✔
6052

6053
        std::vector<Mixed> args;
2✔
6054
        args.reserve(num_values);
2✔
6055
        Query q = table->where();
2✔
6056
        for (size_t i = 0; i < num_values; ++i) {
2,002✔
6057
            q.equal(col, int64_t(i)).Or();
2,000✔
6058
            args.push_back(Mixed(int64_t(i)));
2,000✔
6059
        }
2,000✔
6060
        check_query(q);
2✔
6061
        Query q_in = table->where().in(col, args.data(), args.data() + args.size());
2✔
6062
        check_query(q_in);
2✔
6063
    }
2✔
6064
}
2✔
6065

6066
TEST(Query_FullText)
6067
{
2✔
6068
    Group g;
2✔
6069
    auto table = g.add_table("table");
2✔
6070
    auto col = table->add_column(type_String, "text");
2✔
6071

6072
    // Add before index creation
6073
    table->create_object().set(col, " This is a test, with  spaces!");
2✔
6074
    Obj obj2 = table->create_object().set(col, "Ål, ø og 你好世界Æbler"); // "Hello world" should be filtered out
2✔
6075
    Obj obj3 = table->create_object().set(
2✔
6076
        col,
2✔
6077
        "An object database (also object-oriented database management system) is a database management system in "
2✔
6078
        "which information is represented in the form of objects as used in object-oriented programming. Object "
2✔
6079
        "databases are different from relational databases which are table-oriented. Object-relational databases "
2✔
6080
        "are a hybrid of both approaches.");
2✔
6081
    table->create_object().set(
2✔
6082
        col,
2✔
6083
        "Object database management systems grew out of research during the early to mid-1970s into having "
2✔
6084
        "intrinsic database management support for graph-structured objects. The term 'object-oriented database "
2✔
6085
        "system' first appeared around 1985.[4] Notable research projects included Encore-Ob/Server (Brown "
2✔
6086
        "University), EXODUS (University of Wisconsin–Madison), IRIS (Hewlett-Packard), ODE (Bell Labs), ORION "
2✔
6087
        "(Microelectronics and Computer Technology Corporation or MCC), Vodak (GMD-IPSI), and Zeitgeist (Texas "
2✔
6088
        "Instruments). The ORION project had more published papers than any of the other efforts. Won Kim of MCC "
2✔
6089
        "compiled the best of those papers in a book published by The MIT Press.");
2✔
6090
    table->create_object().set(
2✔
6091
        col, "Lilleø er i mange år blevet anvendt til græsning af Askø-bøndernes kreaturer. I 1788 blev en del af "
2✔
6092
             "Askøs gårde flyttet til Lilleø, og tre gårde eksisterer fortsat på øen. Hovederhvervet på Lilleø er i "
2✔
6093
             "dag frugtavl, og der dyrkes især æbler, pærer og blommer.");
2✔
6094

6095
    // Create the fulltext index
6096
    table->add_fulltext_index(col);
2✔
6097
    CHECK_EQUAL(table->search_index_type(col), IndexType::Fulltext);
2✔
6098

6099
    table->create_object().set(col, "Alle elsker John");
2✔
6100
    table->create_object().set(col, "Johns ven kender John godt");
2✔
6101
    table->create_object().set(col, "Ich wohne in Großarl");
2✔
6102
    table->create_object().set(col, "A short story about a dog running after two cats");
2✔
6103

6104
    auto tv = table->where().fulltext(col, "object").find_all();
2✔
6105
    CHECK_EQUAL(2, tv.size());
2✔
6106

6107
    // Add after index creation
6108
    auto k5 =
2✔
6109
        table->create_object()
2✔
6110
            .set(
2✔
6111
                col,
2✔
6112
                "Early commercial products included Gemstone (Servio Logic, name changed to GemStone Systems), Gbase "
2✔
6113
                "(Graphael), and Vbase (Ontologic). The early to mid-1990s saw additional commercial products enter "
2✔
6114
                "the market. These included ITASCA (Itasca Systems), Jasmine (Fujitsu, marketed by Computer "
2✔
6115
                "Associates), Matisse (Matisse Software), Objectivity/DB (Objectivity, Inc.), ObjectStore (Progress "
2✔
6116
                "Software, acquired from eXcelon which was originally Object Design), ONTOS (Ontos, Inc., name "
2✔
6117
                "changed from Ontologic), O2[6] (O2 Technology, merged with several companies, acquired by Informix, "
2✔
6118
                "which was in turn acquired by IBM), POET (now FastObjects from Versant which acquired Poet Software)"
2✔
6119
                ", Versant Object Database (Versant Corporation), VOSS (Logic Arts) and JADE (Jade Software "
2✔
6120
                "Corporation). Some of these products remain on the market and have been joined by new open source "
2✔
6121
                "and commercial products such as InterSystems Caché.")
2✔
6122
            .get_key();
2✔
6123

6124
    tv.sync_if_needed();
2✔
6125
    CHECK_EQUAL(3, tv.size());
2✔
6126

6127
    // Add another
6128
    table->create_object().set(
2✔
6129
        col, "As the usage of web-based technology increases with the implementation of Intranets and extranets, "
2✔
6130
             "companies have a vested interest in OODBMSs to display their complex data. Using a DBMS that has been "
2✔
6131
             "specifically designed to store data as objects gives an advantage to those companies that are geared "
2✔
6132
             "towards multimedia presentation or organizations that utilize computer-aided design (CAD).[3]");
2✔
6133

6134
    tv.sync_if_needed();
2✔
6135
    CHECK_EQUAL(3, tv.size());
2✔
6136

6137
    // Delete one
6138
    table->remove_object(k5);
2✔
6139
    tv.sync_if_needed();
2✔
6140
    CHECK_EQUAL(2, tv.size());
2✔
6141

6142
    tv = table->where().fulltext(col, "hybrid").find_all();
2✔
6143
    CHECK_EQUAL(1, tv.size());
2✔
6144

6145
    // Change value in place
6146
    obj3.set(
2✔
6147
        col,
2✔
6148
        "Object database management systems added the concept of persistence to object programming languages. The "
2✔
6149
        "early commercial products were integrated with various languages: GemStone (Smalltalk), Gbase (LISP), Vbase "
2✔
6150
        "(COP) and VOSS (Virtual Object Storage System for Smalltalk). For much of the 1990s, C++ dominated the "
2✔
6151
        "commercial object database management market. Vendors added Java in the late 1990s and more recently, C#.");
2✔
6152

6153
    tv = table->where().fulltext(col, "hybrid").find_all();
2✔
6154
    CHECK_EQUAL(0, tv.size());
2✔
6155

6156
    tv = table->where().fulltext(col, "Gemstone").find_all();
2✔
6157
    CHECK_EQUAL(1, tv.size());
2✔
6158

6159
    tv = table->where().fulltext(col, "æbler").find_all();
2✔
6160
    CHECK_EQUAL(2, tv.size());
2✔
6161

6162
    table->create_object().set(
2✔
6163
        col, "The song \"Supercalifragilisticexpialidocious\" is from the 1964 Disney musical film \"Mary Poppins\"");
2✔
6164

6165
    tv = table->where().fulltext(col, "supercalifragilisticexpialidocious mary").find_all();
2✔
6166
    CHECK_EQUAL(1, tv.size());
2✔
6167

6168
    obj2.remove();
2✔
6169
    tv.sync_if_needed();
2✔
6170
    CHECK_EQUAL(1, tv.size());
2✔
6171

6172
    tv = table->where().fulltext(col, "Johns").find_all();
2✔
6173
    CHECK_EQUAL(1, tv.size());
2✔
6174
    tv = table->where().fulltext(col, "John").find_all();
2✔
6175
    CHECK_EQUAL(2, tv.size());
2✔
6176
    tv = table->where().fulltext(col, "Großarl").find_all();
2✔
6177
    CHECK_EQUAL(1, tv.size());
2✔
6178
    tv = table->where().fulltext(col, "catssadasdsa").find_all();
2✔
6179
    CHECK_EQUAL(0, tv.size());
2✔
6180

6181
    table->clear();
2✔
6182
    CHECK(table->get_search_index(col)->is_empty());
2✔
6183
}
2✔
6184

6185
TEST(Query_FullTextMulti)
6186
{
2✔
6187
    Group g;
2✔
6188
    auto table = g.add_table("table");
2✔
6189
    auto origin = g.add_table_with_primary_key("origin", type_Int, "id");
2✔
6190
    auto col_link = origin->add_column_list(*table, "link");
2✔
6191
    auto col = table->add_column(type_String, "text");
2✔
6192
    table->add_fulltext_index(col);
2✔
6193

6194
    table->create_object().set(
2✔
6195
        col,
2✔
6196
        "An object database (also object-oriented database management system) is a database management system in "
2✔
6197
        "which information is represented in the form of objects as used in object-oriented programming. Object "
2✔
6198
        "databases are different from relational databases which are table-oriented. Object-relational databases "
2✔
6199
        "are a hybrid of both approaches.");
2✔
6200
    table->create_object().set(
2✔
6201
        col,
2✔
6202
        "Object database management systems grew out of research during the early to mid-1970s into having "
2✔
6203
        "intrinsic database management support for graph-structured objects. The term 'object-oriented database "
2✔
6204
        "system' first appeared around 1985.[4] Notable research projects included Encore-Ob/Server (Brown "
2✔
6205
        "University), EXODUS (University of Wisconsin–Madison), IRIS (Hewlett-Packard), ODE (Bell Labs), ORION "
2✔
6206
        "(Microelectronics and Computer Technology Corporation or MCC), Vodak (GMD-IPSI), and Zeitgeist (Texas "
2✔
6207
        "Instruments). The ORION project had more published papers than any of the other efforts. Won Kim of MCC "
2✔
6208
        "compiled the best of those papers in a book published by The MIT Press.");
2✔
6209
    table->create_object().set(
2✔
6210
        col,
2✔
6211
        "Early commercial products included Gemstone (Servio Logic, name changed to GemStone Systems), Gbase "
2✔
6212
        "(Graphael), and Vbase (Ontologic). The early to mid-1990s saw additional commercial products enter the "
2✔
6213
        "market. These included ITASCA (Itasca Systems), Jasmine (Fujitsu, marketed by Computer Associates), Matisse "
2✔
6214
        "(Matisse Software), Objectivity/DB (Objectivity, Inc.), ObjectStore (Progress Software, acquired from "
2✔
6215
        "eXcelon which was originally Object Design), ONTOS (Ontos, Inc., name changed from Ontologic), O2[6] (O2 "
2✔
6216
        "Technology, merged with several companies, acquired by Informix, which was in turn acquired by IBM), POET "
2✔
6217
        "(now FastObjects from Versant which acquired Poet Software), Versant Object Database (Versant Corporation), "
2✔
6218
        "VOSS (Logic Arts) and JADE (Jade Software Corporation). Some of these products remain on the market and "
2✔
6219
        "have been joined by new open source and commercial products such as InterSystems Caché.");
2✔
6220
    table->create_object().set(
2✔
6221
        col, "As the usage of web-based technology increases with the implementation of Intranets and extranets, "
2✔
6222
             "companies have a vested interest in OODBMSs to display their complex data. Using a DBMS that has been "
2✔
6223
             "specifically designed to store data as objects gives an advantage to those companies that are geared "
2✔
6224
             "towards multimedia presentation or organizations that utilize computer-aided design (CAD).[3]");
2✔
6225
    table->create_object().set(
2✔
6226
        col,
2✔
6227
        "Object database management systems added the concept of persistence to object programming languages. The "
2✔
6228
        "early commercial products were integrated with various languages: GemStone (Smalltalk), Gbase (LISP), Vbase "
2✔
6229
        "(COP) and VOSS (Virtual Object Storage System for Smalltalk). For much of the 1990s, C++ dominated the "
2✔
6230
        "commercial object database management market. Vendors added Java in the late 1990s and more recently, C#.");
2✔
6231

6232
    table->create_object().set(
2✔
6233
        col, "L’archive ouverte pluridisciplinaire HAL, est destinée au dépôt et à la diffusion de documents "
2✔
6234
             "scientifiques de niveau recherche, publiés ou non, émanant des établissements d’enseignement et de "
2✔
6235
             "recherche français ou étrangers, des laboratoires publics ou privés.");
2✔
6236
    table->create_object().set(col, "object object object object object duplicates");
2✔
6237
    table->create_object().set(col, "one two three");
2✔
6238
    table->create_object().set(col, "three two one");
2✔
6239
    table->create_object().set(col, "two one");
2✔
6240

6241
    // object:              0, 1, 2, 4, 6
6242
    // objects:             0, 1, 3
6243
    // 'object-oriented':   0, 1
6244
    // 'table-oriented':    0
6245
    // oriented:            0, 1
6246
    // gemstone:            2, 4
6247
    // data:                3
6248
    // depot:               5
6249
    // emanant:             5
6250
    // database:            0, 1, 2, 4
6251
    // databases:           0
6252
    // duplicates:          6
6253

6254
    int64_t id = 1000;
2✔
6255
    for (auto& o : *table) {
20✔
6256
        auto ll = origin->create_object_with_primary_key(id++).get_linklist(col_link);
20✔
6257
        ll.add(o.get_key());
20✔
6258
    }
20✔
6259

6260
    typedef std::vector<int64_t> Keys;
2✔
6261
    auto get_keys = [&](const TableView& tv) -> Keys {
54✔
6262
        std::vector<int64_t> keys(tv.size());
54✔
6263
        for (size_t i = 0; i < tv.size(); ++i)
164✔
6264
            keys[i] = tv.get_key(i).value;
110✔
6265
        return keys;
54✔
6266
    };
54✔
6267
    auto do_fulltext_find = [&](StringData term) -> Keys {
64✔
6268
        return get_keys(table->where().fulltext(col, term).find_all());
64✔
6269
    };
64✔
6270
    auto do_query_find = [&](const TableRef& table, StringData query) -> Keys {
4✔
6271
        return get_keys(table->query(query).find_all());
4✔
6272
    };
4✔
6273

6274
    CHECK_THROW_ANY(do_fulltext_find(""));
2✔
6275

6276
    // search with multiple terms
6277
    CHECK_EQUAL(do_fulltext_find("ONE THREE"), Keys({7, 8}));
2✔
6278
    CHECK_EQUAL(do_fulltext_find("three one"), Keys({7, 8}));
2✔
6279
    CHECK_EQUAL(do_fulltext_find("1990s"), Keys({2, 4}));
2✔
6280
    CHECK_EQUAL(do_fulltext_find("1990s c++"), Keys({4}));
2✔
6281
    CHECK_EQUAL(do_fulltext_find("object gemstone"), Keys({2, 4}));
2✔
6282

6283
    // over links
6284
    CHECK_EQUAL(do_query_find(origin, "link.text TEXT 'object gemstone'"), Keys({2, 4}));
2✔
6285
    auto tv = origin->link(col_link).column<String>(col).fulltext("object gemstone").find_all();
2✔
6286
    CHECK_EQUAL(get_keys(tv), Keys({2, 4}));
2✔
6287

6288
    // through LnkLst
6289
    auto obj = tv.get_object(0);
2✔
6290
    auto ll = obj.get_linklist(col_link);
2✔
6291
    tv = table->where(ll).fulltext(col, "object gemstone").find_all();
2✔
6292
    CHECK_EQUAL(get_keys(tv), Keys({2}));
2✔
6293

6294
    // Diacritics ignorant
6295
    CHECK_EQUAL(do_fulltext_find("depot emanant archive"), Keys({5}));
2✔
6296

6297
    // search for combination that is not present
6298
    CHECK_EQUAL(do_fulltext_find("object data"), Keys());
2✔
6299

6300
    // Prefix
6301
    CHECK_EQUAL(do_fulltext_find("manage*"), Keys({0, 1, 4}));
2✔
6302
    CHECK_EQUAL(do_fulltext_find("manage* virtu*"), Keys({4}));
2✔
6303

6304
    // exclude words
6305
    CHECK_EQUAL(do_fulltext_find("-three one"), Keys({9}));
2✔
6306
    CHECK_EQUAL(do_fulltext_find("one -three"), Keys({9}));
2✔
6307
    CHECK_EQUAL(do_fulltext_find("object -databases"), Keys({1, 2, 4, 6}));
2✔
6308
    CHECK_EQUAL(do_fulltext_find("-databases object -duplicates"), Keys({1, 2, 4}));
2✔
6309
    CHECK_EQUAL(do_fulltext_find("object -objects"), Keys({2, 4, 6}));
2✔
6310
    CHECK_EQUAL(do_fulltext_find("-object objects"), Keys({3}));
2✔
6311
    CHECK_EQUAL(do_fulltext_find("databases -database"), Keys({}));
2✔
6312
    CHECK_EQUAL(do_fulltext_find("-database databases"), Keys({}));
2✔
6313
    CHECK_EQUAL(do_fulltext_find("database -databases"), Keys({1, 2, 4}));
2✔
6314
    CHECK_EQUAL(do_fulltext_find("-databases database"), Keys({1, 2, 4}));
2✔
6315
    CHECK_EQUAL(do_fulltext_find("-database"), Keys({3, 5, 6, 7, 8, 9}));
2✔
6316
    CHECK_EQUAL(do_fulltext_find("-object"), Keys({3, 5, 7, 8, 9}));
2✔
6317
    CHECK_EQUAL(do_fulltext_find("-object -objects"), Keys({5, 7, 8, 9}));
2✔
6318

6319
    // Don't include and exclude same token
6320
    CHECK_THROW_ANY(do_fulltext_find("C# -c++")); // Will both end up as 'c'
2✔
6321
    CHECK_THROW_ANY(do_fulltext_find("-object object"));
2✔
6322
    CHECK_THROW_ANY(do_fulltext_find("object -object"));
2✔
6323
    CHECK_THROW_ANY(do_fulltext_find("objects -object object"));
2✔
6324
    CHECK_THROW_ANY(do_fulltext_find("object -object object"));
2✔
6325
    CHECK_THROW_ANY(do_fulltext_find("database -database"));
2✔
6326

6327
    // many terms
6328
    CHECK_EQUAL(do_fulltext_find("object database management brown"), Keys({1}));
2✔
6329
    CHECK_EQUAL(do_query_find(table, "text TEXT 'object database management brown'"), Keys({1}));
2✔
6330

6331
    // non alphanum characters not allowed inside seach token
6332
    CHECK_THROW_ANY(do_fulltext_find("object-oriented -database"));
2✔
6333
    CHECK_THROW_ANY(do_fulltext_find("object-oriented -table-oriented"));
2✔
6334

6335
    while (table->size() > 0) {
22✔
6336
        table->begin()->remove();
20✔
6337
    }
20✔
6338

6339
    CHECK(table->get_search_index(col)->is_empty());
2✔
6340
}
2✔
6341

6342
TEST(Query_FullTextPrefix)
6343
{
2✔
6344
    Group g;
2✔
6345
    auto table = g.add_table("table");
2✔
6346
    auto col = table->add_column(type_String, "text");
2✔
6347
    table->add_fulltext_index(col);
2✔
6348

6349
    table->create_object().set(col, "Abby Abba Ada Adalee Baylee Bellamy Blaire Adalyn");
2✔
6350
    table->create_object().set(col, "Abigail Abba Barbara Beatrice Bella Blair Blake");
2✔
6351
    table->create_object().set(col, "Adaline Bellamy Blakely");
2✔
6352

6353
    // table->get_search_index(col)->do_dump_node_structure(std::cout, 0);
6354

6355
    auto q = table->query("text TEXT 'ab*'");
2✔
6356
    CHECK_EQUAL(q.count(), 2);
2✔
6357
    q = table->query("text TEXT 'ac*'"); // No match shorter than 4
2✔
6358
    CHECK_EQUAL(q.count(), 0);
2✔
6359
    q = table->query("text TEXT 'abbe*'"); // No match excatly four
2✔
6360
    CHECK_EQUAL(q.count(), 0);
2✔
6361
    q = table->query("text TEXT 'abbex*'"); // No match bigger than 4
2✔
6362
    CHECK_EQUAL(q.count(), 0);
2✔
6363
    q = table->query("text TEXT 'Bel*'");
2✔
6364
    CHECK_EQUAL(q.count(), 3);
2✔
6365
    q = table->query("text TEXT 'Blak*'");
2✔
6366
    CHECK_EQUAL(q.count(), 2);
2✔
6367
    q = table->query("text TEXT 'Bellam*'");
2✔
6368
    CHECK_EQUAL(q.count(), 2);
2✔
6369
    q = table->query("text TEXT 'Bel* Abba -Ada'");
2✔
6370
    CHECK_EQUAL(q.count(), 1);
2✔
6371
}
2✔
6372

6373
#endif // TEST_QUERY
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