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

realm / realm-core / 2292

02 May 2024 08:09PM UTC coverage: 90.76% (+0.01%) from 90.747%
2292

push

Evergreen

web-flow
Fix a deadlock when accessing current user from inside an App listener (#7671)

App::switch_user() emitted changes without first releasing the lock on
m_user_mutex, leading to a deadlock if anyone inside the listener tried to
acquire the mutex. The rest of the places where we emitted changes were
correct.

The newly added wrapper catches this error when building with clang.

101984 of 180246 branches covered (56.58%)

14 of 17 new or added lines in 2 files covered. (82.35%)

45 existing lines in 16 files now uncovered.

212565 of 234206 relevant lines covered (90.76%)

5840998.47 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,947✔
1457
                        "This string is around 90 bytes long, which falls in the long-string type of Realm strings";
3,947✔
1458
                    char* buf1 = static_cast<char*>(malloc(sizeof(buf)));
3,947✔
1459
                    memcpy(buf1, buf, sizeof(buf));
3,947✔
1460
                    char buf2[] =
3,947✔
1461
                        "                                                                                         ";
3,947✔
1462

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

1466
                    if (fastrand(1) == 0) {
3,947✔
1467
                        // null string
1468
                        sd = realm::null();
2,006✔
1469
                        st = "null";
2,006✔
1470
                    }
2,006✔
1471
                    else {
1,941✔
1472
                        // non-null string
1473
                        size_t len = static_cast<size_t>(fastrand(3));
1,941✔
1474
                        if (len == 0)
1,941✔
1475
                            len = 0;
475✔
1476
                        else if (len == 1)
1,466✔
1477
                            len = 7;
484✔
1478
                        else if (len == 2)
982✔
1479
                            len = 27;
520✔
1480
                        else
462✔
1481
                            len = 73;
462✔
1482

1483
                        if (fastrand(1) == 0) {
1,941✔
1484
                            // duplicate string
1485
                            sd = StringData(buf1, len);
969✔
1486
                            st = std::string(buf1, len);
969✔
1487
                        }
969✔
1488
                        else {
972✔
1489
                            // random string
1490
                            for (size_t s = 0; s < len; s++) {
26,350✔
1491
                                if (fastrand(100) > 20)
25,378✔
1492
                                    buf2[s] = 0; // zero byte
20,072✔
1493
                                else
5,306✔
1494
                                    buf2[s] = static_cast<char>(fastrand(255)); // random byte
5,306✔
1495
                            }
25,378✔
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);
972✔
1499
                            st = std::string(buf2, len);
972✔
1500
                        }
972✔
1501
                    }
1,941✔
1502

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

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

1521

1522
                CHECK_EQUAL(table.size(), v.size());
8,000✔
1523
                for (auto& o : table) {
33,363✔
1524
                    auto k = o.get_key();
33,363✔
1525
                    if (v[k] == "null") {
33,363✔
1526
                        CHECK(o.get<String>(col).is_null());
17,260✔
1527
                    }
17,260✔
1528
                    else {
16,103✔
1529
                        CHECK(o.get<String>(col) == v[k]);
16,103✔
1530
                    }
16,103✔
1531
                }
33,363✔
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,050✔
3291
                CHECK(tv_g[i].get<Int>(col) > s);
16,050✔
3292
            }
16,050✔
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) {
20,229✔
3297
                CHECK(tv_l[i].get<Int>(col) < s);
18,229✔
3298
            }
18,229✔
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,950✔
3303
                CHECK(tv_le[i].get<Int>(col) <= s);
19,950✔
3304
            }
19,950✔
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