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

realm / realm-core / jonathan.reams_3390

31 Jul 2024 07:00PM UTC coverage: 91.105% (-0.01%) from 91.116%
jonathan.reams_3390

Pull #7938

Evergreen

jbreams
RCORE-2212 Make apply-to-state tool handle all batch states properly
Pull Request #7938: RCORE-2212 Make apply-to-state tool handle all batch states properly

102768 of 181570 branches covered (56.6%)

216827 of 237996 relevant lines covered (91.11%)

5898131.93 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_TYPES(Query_FindAllBetween, std::true_type, std::false_type)
196
{
4✔
197
    Table ttt;
4✔
198
    auto col_id = ttt.add_column(type_Int, "id");
4✔
199
    auto col_int = ttt.add_column(type_Int, "1", TEST_TYPE::value);
4✔
200
    ttt.add_column(type_String, "2");
4✔
201

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

211
    Query q1 = ttt.where().between(col_int, 100, 200);
4✔
212
    TableView tv1 = q1.find_all();
4✔
213
    CHECK_EQUAL(tv1.size(), 0);
4✔
214

215
    Query q2 = ttt.where().between(col_int, 3, 5);
4✔
216
    TableView tv2 = q2.find_all();
4✔
217
    CHECK_EQUAL(tv2.size(), 4);
4✔
218
    CHECK_EQUAL(2, tv2[0].get<Int>(col_id));
4✔
219
    CHECK_EQUAL(3, tv2[1].get<Int>(col_id));
4✔
220
    CHECK_EQUAL(4, tv2[2].get<Int>(col_id));
4✔
221
    CHECK_EQUAL(6, tv2[3].get<Int>(col_id));
4✔
222
}
4✔
223

224

225
TEST(Query_FindAllOr)
226
{
2✔
227
    Table ttt;
2✔
228
    auto col_id = ttt.add_column(type_Int, "id");
2✔
229
    auto col_int = ttt.add_column(type_Int, "1");
2✔
230
    auto col_str = ttt.add_column(type_String, "2");
2✔
231

232
    ttt.create_object().set_all(0, 1, "a");
2✔
233
    ttt.create_object().set_all(1, 2, "a");
2✔
234
    ttt.create_object().set_all(2, 3, "X");
2✔
235
    ttt.create_object().set_all(3, 4, "a");
2✔
236
    ttt.create_object().set_all(4, 5, "a");
2✔
237
    ttt.create_object().set_all(5, 6, "a");
2✔
238
    ttt.create_object().set_all(6, 7, "X");
2✔
239
    ttt.create_object().set_all(7, 8, "z");
2✔
240

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

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

259

260
TEST(Query_FindAllParens1)
261
{
2✔
262
    Table ttt;
2✔
263
    auto col_id = ttt.add_column(type_Int, "id");
2✔
264
    auto col_int = ttt.add_column(type_Int, "1");
2✔
265
    auto col_str = ttt.add_column(type_String, "2");
2✔
266

267
    ttt.create_object().set_all(0, 1, "a");
2✔
268
    ttt.create_object().set_all(1, 2, "a");
2✔
269
    ttt.create_object().set_all(2, 3, "X");
2✔
270
    ttt.create_object().set_all(3, 3, "X");
2✔
271
    ttt.create_object().set_all(4, 4, "a");
2✔
272
    ttt.create_object().set_all(5, 5, "a");
2✔
273
    ttt.create_object().set_all(6, 11, "X");
2✔
274

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

282

283
TEST(Query_FindAllOrParan)
284
{
2✔
285
    Table ttt;
2✔
286
    auto col_id = ttt.add_column(type_Int, "id");
2✔
287
    auto col_int = ttt.add_column(type_Int, "1");
2✔
288
    auto col_str = ttt.add_column(type_String, "2");
2✔
289

290
    ttt.create_object().set_all(0, 1, "a");
2✔
291
    ttt.create_object().set_all(1, 2, "a");
2✔
292
    ttt.create_object().set_all(2, 3, "X"); //
2✔
293
    ttt.create_object().set_all(3, 4, "a");
2✔
294
    ttt.create_object().set_all(4, 5, "a"); //
2✔
295
    ttt.create_object().set_all(5, 6, "a");
2✔
296
    ttt.create_object().set_all(6, 7, "X"); //
2✔
297
    ttt.create_object().set_all(7, 2, "X");
2✔
298

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

308

309
TEST(Query_FindAllOrNested0)
310
{
2✔
311
    Table ttt;
2✔
312
    auto col_id = ttt.add_column(type_Int, "id");
2✔
313
    auto col_int = ttt.add_column(type_Int, "1");
2✔
314
    auto col_str = ttt.add_column(type_String, "2");
2✔
315

316
    ttt.create_object().set_all(0, 1, "a");
2✔
317
    ttt.create_object().set_all(1, 2, "a");
2✔
318
    ttt.create_object().set_all(2, 3, "X");
2✔
319
    ttt.create_object().set_all(3, 3, "X");
2✔
320
    ttt.create_object().set_all(4, 4, "a");
2✔
321
    ttt.create_object().set_all(5, 5, "a");
2✔
322
    ttt.create_object().set_all(6, 11, "X");
2✔
323
    ttt.create_object().set_all(7, 8, "Y");
2✔
324

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

333
TEST(Query_FindAllOrNested)
334
{
2✔
335
    Table ttt;
2✔
336
    auto col_id = ttt.add_column(type_Int, "id");
2✔
337
    auto col_int = ttt.add_column(type_Int, "1");
2✔
338
    auto col_str = ttt.add_column(type_String, "2");
2✔
339

340
    ttt.create_object().set_all(0, 1, "a");
2✔
341
    ttt.create_object().set_all(1, 2, "a");
2✔
342
    ttt.create_object().set_all(2, 3, "X");
2✔
343
    ttt.create_object().set_all(3, 3, "X");
2✔
344
    ttt.create_object().set_all(4, 4, "a");
2✔
345
    ttt.create_object().set_all(5, 5, "a");
2✔
346
    ttt.create_object().set_all(6, 11, "X");
2✔
347
    ttt.create_object().set_all(7, 8, "Y");
2✔
348

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

366
TEST(Query_FindAllOrNestedInnerGroup)
367
{
2✔
368
    Table ttt;
2✔
369
    auto col_id = ttt.add_column(type_Int, "id");
2✔
370
    auto col_int = ttt.add_column(type_Int, "1");
2✔
371
    auto col_str = ttt.add_column(type_String, "2");
2✔
372

373
    ttt.create_object().set_all(0, 1, "a");
2✔
374
    ttt.create_object().set_all(1, 2, "a");
2✔
375
    ttt.create_object().set_all(2, 3, "X");
2✔
376
    ttt.create_object().set_all(3, 3, "X");
2✔
377
    ttt.create_object().set_all(4, 4, "a");
2✔
378
    ttt.create_object().set_all(5, 5, "a");
2✔
379
    ttt.create_object().set_all(6, 11, "X");
2✔
380
    ttt.create_object().set_all(7, 8, "Y");
2✔
381

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

401
TEST(Query_FindAllOrPHP)
402
{
2✔
403
    Table ttt;
2✔
404
    auto col_id = ttt.add_column(type_Int, "id");
2✔
405
    auto col_int = ttt.add_column(type_Int, "1");
2✔
406
    auto col_str = ttt.add_column(type_String, "2");
2✔
407

408
    ttt.create_object().set_all(0, 1, "Joe");
2✔
409
    ttt.create_object().set_all(1, 2, "Sara");
2✔
410
    ttt.create_object().set_all(2, 3, "Jim");
2✔
411

412
    // (second == Jim || second == Joe) && first = 1
413
    Query q1 = ttt.where().group().equal(col_str, "Jim").Or().equal(col_str, "Joe").end_group().equal(col_int, 1);
2✔
414
    TableView tv1 = q1.find_all();
2✔
415
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
416

417
    q1 = ttt.where().group().equal(col_str, "Jim").Or().equal(col_str, "Joe").end_group().equal(col_int, 3);
2✔
418
    tv1 = q1.find_all();
2✔
419
    CHECK_EQUAL(2, tv1[0].get<Int>(col_id));
2✔
420
}
2✔
421

422
TEST(Query_FindAllParens2)
423
{
2✔
424
    Table ttt;
2✔
425
    auto col_id = ttt.add_column(type_Int, "id");
2✔
426
    auto col_int = ttt.add_column(type_Int, "1");
2✔
427

428
    ttt.create_object().set_all(0, 1);
2✔
429
    ttt.create_object().set_all(1, 2);
2✔
430
    ttt.create_object().set_all(2, 3);
2✔
431
    ttt.create_object().set_all(3, 3);
2✔
432
    ttt.create_object().set_all(4, 4);
2✔
433
    ttt.create_object().set_all(5, 5);
2✔
434
    ttt.create_object().set_all(6, 11);
2✔
435

436
    // ()
437
    Query q1 = ttt.where().group().end_group();
2✔
438
    TableView tv1 = q1.find_all();
2✔
439
    CHECK_EQUAL(7, tv1.size());
2✔
440

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

463

464
TEST(Query_FindAllBool)
465
{
2✔
466
    Table table;
2✔
467
    auto col_id = table.add_column(type_Int, "id");
2✔
468
    auto col_bool = table.add_column(type_Bool, "2");
2✔
469

470
    table.create_object().set_all(0, true);
2✔
471
    table.create_object().set_all(1, false);
2✔
472
    table.create_object().set_all(2, true);
2✔
473
    table.create_object().set_all(3, false);
2✔
474

475
    Query q1 = table.where().equal(col_bool, true);
2✔
476
    TableView tv1 = q1.find_all();
2✔
477
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
478
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
2✔
479

480
    Query q2 = table.where().equal(col_bool, false);
2✔
481
    TableView tv2 = q2.find_all();
2✔
482
    CHECK_EQUAL(1, tv2[0].get<Int>(col_id));
2✔
483
    CHECK_EQUAL(3, tv2[1].get<Int>(col_id));
2✔
484
}
2✔
485

486
TEST(Query_FindAllBegins)
487
{
2✔
488
    Table table;
2✔
489
    auto col_id = table.add_column(type_Int, "id");
2✔
490
    auto col_str = table.add_column(type_String, "2");
2✔
491

492
    table.create_object().set_all(0, "fo");
2✔
493
    table.create_object().set_all(1, "foo");
2✔
494
    table.create_object().set_all(2, "foobar");
2✔
495

496
    Query q1 = table.where().begins_with(col_str, StringData("foo"));
2✔
497
    TableView tv1 = q1.find_all();
2✔
498
    CHECK_EQUAL(2, tv1.size());
2✔
499
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
500
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
2✔
501
}
2✔
502

503
TEST(Query_FindAllEnds)
504
{
2✔
505
    Table table;
2✔
506
    auto col_id = table.add_column(type_Int, "id");
2✔
507
    auto col_str = table.add_column(type_String, "2");
2✔
508

509
    table.create_object().set_all(0, "barfo");
2✔
510
    table.create_object().set_all(1, "barfoo");
2✔
511
    table.create_object().set_all(2, "barfoobar");
2✔
512

513
    Query q1 = table.where().ends_with(col_str, StringData("foo"));
2✔
514
    TableView tv1 = q1.find_all();
2✔
515
    CHECK_EQUAL(1, tv1.size());
2✔
516
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
517
}
2✔
518

519

520
TEST(Query_FindAllContains)
521
{
2✔
522
    Table table;
2✔
523
    auto col_id = table.add_column(type_Int, "id");
2✔
524
    auto col_str = table.add_column(type_String, "2");
2✔
525

526
    table.create_object().set_all(0, "foo");
2✔
527
    table.create_object().set_all(1, "foobar");
2✔
528
    table.create_object().set_all(2, "barfoo");
2✔
529
    table.create_object().set_all(3, "barfoobaz");
2✔
530
    table.create_object().set_all(4, "fo");
2✔
531
    table.create_object().set_all(5, "fobar");
2✔
532
    table.create_object().set_all(6, "barfo");
2✔
533

534
    Query q1 = table.where().contains(col_str, StringData("foo"));
2✔
535
    TableView tv1 = q1.find_all();
2✔
536
    CHECK_EQUAL(4, tv1.size());
2✔
537
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
538
    CHECK_EQUAL(1, tv1[1].get<Int>(col_id));
2✔
539
    CHECK_EQUAL(2, tv1[2].get<Int>(col_id));
2✔
540
    CHECK_EQUAL(3, tv1[3].get<Int>(col_id));
2✔
541

542
    q1 = table.where().like(col_str, StringData("*foo*"));
2✔
543
    tv1 = q1.find_all();
2✔
544
    CHECK_EQUAL(4, tv1.size());
2✔
545
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
546
    CHECK_EQUAL(1, tv1[1].get<Int>(col_id));
2✔
547
    CHECK_EQUAL(2, tv1[2].get<Int>(col_id));
2✔
548
    CHECK_EQUAL(3, tv1[3].get<Int>(col_id));
2✔
549
}
2✔
550

551
TEST(Query_FindAllLikeStackOverflow)
552
{
2✔
553
    std::string str(100000, 'x');
2✔
554
    StringData sd(str);
2✔
555

556
    Table table;
2✔
557
    auto col = table.add_column(type_String, "strings");
2✔
558
    ObjKey k = table.create_object().set(col, sd).get_key();
2✔
559

560
    auto res = table.where().like(col, sd).find();
2✔
561
    CHECK_EQUAL(res, k);
2✔
562
}
2✔
563

564
TEST(Query_FindAllLikeCaseInsensitive)
565
{
2✔
566
    Table table;
2✔
567
    auto col_id = table.add_column(type_Int, "id");
2✔
568
    auto col_str = table.add_column(type_String, "2");
2✔
569

570
    table.create_object().set_all(0, "Foo");
2✔
571
    table.create_object().set_all(1, "FOOBAR");
2✔
572
    table.create_object().set_all(2, "BaRfOo");
2✔
573
    table.create_object().set_all(3, "barFOObaz");
2✔
574
    table.create_object().set_all(4, "Fo");
2✔
575
    table.create_object().set_all(5, "Fobar");
2✔
576
    table.create_object().set_all(6, "baRFo");
2✔
577

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

587
TEST(Query_Binary)
588
{
2✔
589
    Table t;
2✔
590
    t.add_column(type_Int, "1");
2✔
591
    auto c1 = t.add_column(type_Binary, "2");
2✔
592

593
    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✔
594
                          1, 2, 0, 8, 3, 8, 0, 9, 6, 8, 4, 7, 3, 4, 9, 5, 2, 3, 6, 2, 7, 4,
2✔
595
                          0, 3, 7, 6, 2, 3, 5, 9, 3, 1, 2, 1, 0, 5, 5, 2, 9, 4, 5, 9};
2✔
596

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

599
    std::vector<ObjKey> keys;
2✔
600
    t.create_objects(9, keys);
2✔
601

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

612
    CHECK_EQUAL(0, t.where().equal(c1, BinaryData(bin + 16, 16)).count());
2✔
613
    CHECK_EQUAL(1, t.where().equal(c1, BinaryData(bin + 0, 16)).count());
2✔
614
    CHECK_EQUAL(1, t.where().equal(c1, BinaryData(bin + 48, 16)).count());
2✔
615
    CHECK_EQUAL(2, t.where().equal(c1, BinaryData(bin + 0, 32)).count());
2✔
616

617
    CHECK_EQUAL(9, t.where().not_equal(c1, BinaryData(bin + 16, 16)).count());
2✔
618
    CHECK_EQUAL(8, t.where().not_equal(c1, BinaryData(bin + 0, 16)).count());
2✔
619

620
    CHECK_EQUAL(0, t.where().begins_with(c1, BinaryData(bin + 8, 16)).count());
2✔
621
    CHECK_EQUAL(1, t.where().begins_with(c1, BinaryData(bin + 16, 16)).count());
2✔
622
    CHECK_EQUAL(4, t.where().begins_with(c1, BinaryData(bin + 0, 32)).count());
2✔
623
    CHECK_EQUAL(5, t.where().begins_with(c1, BinaryData(bin + 0, 16)).count());
2✔
624
    CHECK_EQUAL(1, t.where().begins_with(c1, BinaryData(bin + 48, 16)).count());
2✔
625
    CHECK_EQUAL(9, t.where().begins_with(c1, BinaryData(bin + 0, 0)).count());
2✔
626

627
    CHECK_EQUAL(0, t.where().ends_with(c1, BinaryData(bin + 40, 16)).count());
2✔
628
    CHECK_EQUAL(1, t.where().ends_with(c1, BinaryData(bin + 32, 16)).count());
2✔
629
    CHECK_EQUAL(3, t.where().ends_with(c1, BinaryData(bin + 32, 32)).count());
2✔
630
    CHECK_EQUAL(4, t.where().ends_with(c1, BinaryData(bin + 48, 16)).count());
2✔
631
    CHECK_EQUAL(1, t.where().ends_with(c1, BinaryData(bin + 0, 16)).count());
2✔
632
    CHECK_EQUAL(9, t.where().ends_with(c1, BinaryData(bin + 64, 0)).count());
2✔
633

634
    CHECK_EQUAL(0, t.where().contains(c1, BinaryData(bin_2)).count());
2✔
635
    CHECK_EQUAL(5, t.where().contains(c1, BinaryData(bin + 0, 16)).count());
2✔
636
    CHECK_EQUAL(5, t.where().contains(c1, BinaryData(bin + 16, 16)).count());
2✔
637
    CHECK_EQUAL(4, t.where().contains(c1, BinaryData(bin + 24, 16)).count());
2✔
638
    CHECK_EQUAL(4, t.where().contains(c1, BinaryData(bin + 32, 16)).count());
2✔
639
    CHECK_EQUAL(9, t.where().contains(c1, BinaryData(bin + 0, 0)).count());
2✔
640

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

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

664
TEST(Query_Enums)
665
{
2✔
666
    Table table;
2✔
667
    auto col_int = table.add_column(type_Int, "1");
2✔
668
    auto col_str = table.add_column(type_String, "2");
2✔
669

670

671
    for (size_t i = 0; i < 5; ++i) {
12✔
672
        table.create_object().set_all(1, "abd");
10✔
673
        table.create_object().set_all(2, "eftg");
10✔
674
        table.create_object().set_all(5, "hijkl");
10✔
675
        table.create_object().set_all(8, "mnopqr");
10✔
676
        table.create_object().set_all(9, "stuvxyz");
10✔
677
    }
10✔
678

679
    table.enumerate_string_column(col_str);
2✔
680

681
    Query q1 = table.where().equal(col_str, "eftg");
2✔
682
    TableView tv1 = q1.find_all();
2✔
683

684
    CHECK_EQUAL(5, tv1.size());
2✔
685
    CHECK_EQUAL(2, tv1[0].get<Int>(col_int));
2✔
686
    CHECK_EQUAL(2, tv1[1].get<Int>(col_int));
2✔
687
    CHECK_EQUAL(2, tv1[2].get<Int>(col_int));
2✔
688
    CHECK_EQUAL(2, tv1[3].get<Int>(col_int));
2✔
689
    CHECK_EQUAL(2, tv1[4].get<Int>(col_int));
2✔
690
}
2✔
691

692

693
TEST_TYPES(Query_CaseSensitivity, std::true_type, std::false_type)
694
{
4✔
695
    constexpr bool nullable = TEST_TYPE::value;
4✔
696

697
    Table ttt;
4✔
698
    auto col = ttt.add_column(type_String, "2", nullable);
4✔
699

700
    ObjKey k = ttt.create_object().set(col, "BLAAbaergroed").get_key();
4✔
701
    ttt.create_object().set(col, "BLAAbaergroedandMORE");
4✔
702
    ttt.create_object().set(col, "BLAAbaergroedZ");
4✔
703
    ttt.create_object().set(col, "BLAAbaergroedZ");
4✔
704
    ttt.create_object().set(col, "BLAAbaergroedZ");
4✔
705

706
    Query q1 = ttt.where().equal(col, "blaabaerGROED", false);
4✔
707
    TableView tv1 = q1.find_all();
4✔
708
    CHECK_EQUAL(1, tv1.size());
4✔
709
    CHECK_EQUAL(k, tv1.get_key(0));
4✔
710

711
    Query q2 = ttt.where().equal(col, "blaabaerGROEDz", false);
4✔
712
    TableView tv2 = q2.find_all();
4✔
713
    CHECK_EQUAL(3, tv2.size());
4✔
714

715
    ttt.add_search_index(col);
4✔
716

717
    Query q3 = ttt.where().equal(col, "blaabaerGROEDz", false);
4✔
718
    TableView tv3 = q3.find_all();
4✔
719
    CHECK_EQUAL(3, tv3.size());
4✔
720
}
4✔
721

722
#define uY "\x0CE\x0AB"            // greek capital letter upsilon with dialytika (U+03AB)
723
#define uYd "\x0CE\x0A5\x0CC\x088" // decomposed form (Y followed by two dots)
724
#define uy "\x0CF\x08B"            // greek small letter upsilon with dialytika (U+03AB)
725
#define uyd "\x0cf\x085\x0CC\x088" // decomposed form (Y followed by two dots)
726

727
#define uA "\x0c3\x085"       // danish capital A with ring above (as in BLAABAERGROED)
728
#define uAd "\x041\x0cc\x08a" // decomposed form (A (41) followed by ring)
4✔
729
#define ua "\x0c3\x0a5"       // danish lower case a with ring above (as in blaabaergroed)
730
#define uad "\x061\x0cc\x08a" // decomposed form (a (41) followed by ring)
26✔
731

732
#if (defined(_WIN32) || defined(__WIN32__) || defined(_WIN64))
733

734
TEST(Query_Unicode2)
735
{
736
    Table table;
737
    auto col_id = table.add_column(type_Int, "id");
738
    auto col_str = table.add_column(type_String, "2");
739

740
    table.create_object().set_all(0, uY);
741
    table.create_object().set_all(1, uYd);
742
    table.create_object().set_all(2, uy);
743
    table.create_object().set_all(3, uyd);
744

745
    Query q1 = table.where().equal(col_str, uY, false);
746
    TableView tv1 = q1.find_all();
747
    CHECK_EQUAL(2, tv1.size());
748
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
749
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
750

751
    Query q2 = table.where().equal(col_str, uYd, false);
752
    TableView tv2 = q2.find_all();
753
    CHECK_EQUAL(2, tv2.size());
754
    CHECK_EQUAL(1, tv2[0].get<Int>(col_id));
755
    CHECK_EQUAL(3, tv2[1].get<Int>(col_id));
756

757
    Query q3 = table.where().equal(col_str, uYd, true);
758
    TableView tv3 = q3.find_all();
759
    CHECK_EQUAL(1, tv3.size());
760
    CHECK_EQUAL(1, tv3[0].get<Int>(col_id));
761
}
762

763

764
TEST(Query_Unicode3)
765
{
766
    Table table;
767
    auto col_id = table.add_column(type_Int, "id");
768
    auto col_str = table.add_column(type_String, "2");
769

770
    table.create_object().set_all(0, uA);
771
    table.create_object().set_all(1, uAd);
772
    table.create_object().set_all(2, ua);
773
    table.create_object().set_all(3, uad);
774

775
    Query q1 = table.where().equal(col_str, uA, false);
776
    TableView tv1 = q1.find_all();
777
    CHECK_EQUAL(2, tv1.size());
778
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
779
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
780

781
    Query q2 = table.where().equal(col_str, ua, false);
782
    TableView tv2 = q2.find_all();
783
    CHECK_EQUAL(2, tv2.size());
784
    CHECK_EQUAL(0, tv2[0].get<Int>(col_id));
785
    CHECK_EQUAL(2, tv2[1].get<Int>(col_id));
786

787
    Query q3 = table.where().equal(col_str, uad, false);
788
    TableView tv3 = q3.find_all();
789
    CHECK_EQUAL(2, tv3.size());
790
    CHECK_EQUAL(1, tv3[0].get<Int>(col_id));
791
    CHECK_EQUAL(3, tv3[1].get<Int>(col_id));
792

793
    Query q4 = table.where().equal(col_str, uad, true);
794
    TableView tv4 = q4.find_all();
795
    CHECK_EQUAL(1, tv4.size());
796
    CHECK_EQUAL(3, tv4[0].get<Int>(col_id));
797
}
798

799
#endif
800

801
TEST(Query_FindAllBeginsUnicode)
802
{
2✔
803
    Table table;
2✔
804
    auto col_id = table.add_column(type_Int, "id");
2✔
805
    auto col_str = table.add_column(type_String, "2");
2✔
806

807
    table.create_object().set_all(0, uad "fo");
2✔
808
    table.create_object().set_all(1, uad "foo");
2✔
809
    table.create_object().set_all(2, uad "foobar");
2✔
810

811
    Query q1 = table.where().begins_with(col_str, StringData(uad "foo"));
2✔
812
    TableView tv1 = q1.find_all();
2✔
813
    CHECK_EQUAL(2, tv1.size());
2✔
814
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
815
    CHECK_EQUAL(2, tv1[1].get<Int>(col_id));
2✔
816
}
2✔
817

818

819
TEST(Query_FindAllEndsUnicode)
820
{
2✔
821
    Table table;
2✔
822
    auto col_id = table.add_column(type_Int, "id");
2✔
823
    auto col_str = table.add_column(type_String, "2");
2✔
824

825
    table.create_object().set_all(0, "barfo");
2✔
826
    table.create_object().set_all(1, "barfoo" uad);
2✔
827
    table.create_object().set_all(2, "barfoobar");
2✔
828

829
    Query q1 = table.where().ends_with(col_str, StringData("foo" uad));
2✔
830
    TableView tv1 = q1.find_all();
2✔
831
    CHECK_EQUAL(1, tv1.size());
2✔
832
    CHECK_EQUAL(1, tv1[0].get<Int>(col_id));
2✔
833

834
    Query q2 = table.where().ends_with(col_str, StringData("foo" uAd), false);
2✔
835
    TableView tv2 = q2.find_all();
2✔
836
    CHECK_EQUAL(1, tv2.size());
2✔
837
    CHECK_EQUAL(1, tv2[0].get<Int>(col_id));
2✔
838
}
2✔
839

840

841
TEST(Query_FindAllContainsUnicode)
842
{
2✔
843
    Table table;
2✔
844
    auto col_id = table.add_column(type_Int, "id");
2✔
845
    auto col_str = table.add_column(type_String, "2");
2✔
846

847
    table.create_object().set_all(0, uad "foo");
2✔
848
    table.create_object().set_all(1, uad "foobar");
2✔
849
    table.create_object().set_all(2, "bar" uad "foo");
2✔
850
    table.create_object().set_all(3, uad "bar" uad "foobaz");
2✔
851
    table.create_object().set_all(4, uad "fo");
2✔
852
    table.create_object().set_all(5, uad "fobar");
2✔
853
    table.create_object().set_all(6, uad "barfo");
2✔
854

855
    Query q1 = table.where().contains(col_str, StringData(uad "foo"));
2✔
856
    TableView tv1 = q1.find_all();
2✔
857
    CHECK_EQUAL(4, tv1.size());
2✔
858
    CHECK_EQUAL(0, tv1[0].get<Int>(col_id));
2✔
859
    CHECK_EQUAL(1, tv1[1].get<Int>(col_id));
2✔
860
    CHECK_EQUAL(2, tv1[2].get<Int>(col_id));
2✔
861
    CHECK_EQUAL(3, tv1[3].get<Int>(col_id));
2✔
862

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

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

879
    table.create_object().set_all(1, "a");
2✔
880
    table.create_object().set_all(2, "a");
2✔
881
    table.create_object().set_all(3, "c");
2✔
882

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

885
    Query q1 = table.where(&v);
2✔
886
    CHECK_EQUAL(2, q1.count());
2✔
887

888
    Query q3 = table.where(&v).equal(col_str, "a");
2✔
889
    CHECK_EQUAL(1, q3.count());
2✔
890

891
    Query q4 = table.where(&v).between(col_int, 3, 6);
2✔
892
    CHECK_EQUAL(1, q4.count());
2✔
893
}
2✔
894

895

896
TEST(Query_SumMinMaxAvg)
897
{
2✔
898
    Table t;
2✔
899

900
    auto int_col = t.add_column(type_Int, "1");
2✔
901
    auto date_col = t.add_column(type_Timestamp, "3");
2✔
902
    auto float_col = t.add_column(type_Float, "4");
2✔
903
    auto double_col = t.add_column(type_Double, "5");
2✔
904
    auto decimal_col = t.add_column(type_Decimal, "6");
2✔
905
    auto mixed_col = t.add_column(type_Mixed, "7");
2✔
906

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

919
    CHECK_EQUAL(9, *t.where().sum(int_col));
2✔
920

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

937
    ObjKey resindex;
2✔
938

939
    t.where().max(int_col, &resindex);
2✔
940
    CHECK_EQUAL(keys[5], resindex);
2✔
941

942
    t.where().min(int_col, &resindex);
2✔
943
    CHECK_EQUAL(keys[6], resindex);
2✔
944

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

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

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

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

957
    t.where().max(date_col, &resindex);
2✔
958
    CHECK_EQUAL(keys[7], resindex);
2✔
959

960
    t.where().min(date_col, &resindex);
2✔
961
    CHECK_EQUAL(keys[8], resindex);
2✔
962

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

967
    t.where().not_equal(int_col, 0).min(float_col, &resindex);
2✔
968
    CHECK_EQUAL(keys[0], resindex);
2✔
969

970
    t.where().not_equal(int_col, 0).min(date_col, &resindex);
2✔
971
    CHECK_EQUAL(keys[5], resindex);
2✔
972

973
    t.where().not_equal(int_col, 0).max(date_col, &resindex);
2✔
974
    CHECK_EQUAL(keys[4], resindex);
2✔
975

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

978
    CHECK_EQUAL(t.where().max(date_col), Timestamp(3000, 0));
2✔
979
    CHECK_EQUAL(t.where().min(date_col), Timestamp(5, 0));
2✔
980
}
2✔
981

982

983
TEST(Query_Avg)
984
{
2✔
985
    Table t;
2✔
986
    auto col = t.add_column(type_Int, "1");
2✔
987

988
    t.create_object().set(col, 10);
2✔
989
    CHECK_EQUAL(10, t.where().avg(col));
2✔
990
    t.create_object().set(col, 30);
2✔
991
    CHECK_EQUAL(20, t.where().avg(col));
2✔
992
}
2✔
993

994
TEST(Query_Avg2)
995
{
2✔
996
    Table t;
2✔
997
    auto col_int = t.add_column(type_Int, "1");
2✔
998
    auto col_str = t.add_column(type_String, "2");
2✔
999

1000
    size_t cnt;
2✔
1001

1002
    t.create_object().set_all(10, "a");
2✔
1003
    t.create_object().set_all(100, "b");
2✔
1004
    t.create_object().set_all(20, "a");
2✔
1005
    t.create_object().set_all(100, "b");
2✔
1006
    t.create_object().set_all(100, "b");
2✔
1007
    t.create_object().set_all(30, "a");
2✔
1008

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

1011
    CHECK_EQUAL(20, t.where().equal(col_str, "a").avg(col_int, &cnt));
2✔
1012
    CHECK_EQUAL(3, cnt);
2✔
1013
    CHECK_EQUAL(100, t.where().equal(col_str, "b").avg(col_int, &cnt));
2✔
1014
    CHECK_EQUAL(3, cnt);
2✔
1015
}
2✔
1016

1017

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

1028
    // Top
1029
    Obj obj0 = t.get_object(0);
2✔
1030
    obj0.set(col_int, 0);
2✔
1031
    ObjKey res = t.where().equal(col_int, 0).find();
2✔
1032
    CHECK_EQUAL(obj0.get_key(), res);
2✔
1033
    obj0.set(col_int, 1); // reset
2✔
1034

1035
    // Before split
1036
    Obj obj1 = t.get_object(cluster_size - 1);
2✔
1037
    obj1.set(col_int, 0);
2✔
1038
    res = t.where().equal(col_int, 0).find();
2✔
1039
    CHECK_EQUAL(obj1.get_key(), res);
2✔
1040
    obj1.set(col_int, 1); // reset
2✔
1041

1042
    // After split
1043
    Obj obj2 = t.get_object(cluster_size);
2✔
1044
    obj2.set(col_int, 0);
2✔
1045
    res = t.where().equal(col_int, 0).find();
2✔
1046
    CHECK_EQUAL(obj2.get_key(), res);
2✔
1047
    obj2.set(col_int, 1); // reset
2✔
1048

1049
    // Before end
1050
    Obj obj3 = t.get_object((cluster_size * 2) - 1);
2✔
1051
    obj3.set(col_int, 0);
2✔
1052
    res = t.where().equal(col_int, 0).find();
2✔
1053
    CHECK_EQUAL(obj3.get_key(), res);
2✔
1054
    obj3.set(col_int, 1); // reset
2✔
1055
}
2✔
1056

1057

1058
TEST(Query_AllTypesDynamicallyTyped)
1059
{
2✔
1060
    for (int nullable = 0; nullable < 2; nullable++) {
6✔
1061
        bool n = (nullable == 1);
4✔
1062

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

1073
        const char bin[4] = {0, 1, 2, 3};
4✔
1074
        BinaryData bin1(bin, sizeof bin / 2);
4✔
1075
        BinaryData bin2(bin, sizeof bin);
4✔
1076
        Timestamp time_now(time(nullptr), 0);
4✔
1077

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

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

1092
        Query query = table.where().equal(col_boo, false);
4✔
1093

1094
        ObjKey ndx;
4✔
1095

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

1100
        CHECK_EQUAL(54, query.max(col_int));
4✔
1101
        query.max(col_int, &ndx);
4✔
1102
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1103

1104
        CHECK_EQUAL(54, query.sum(col_int));
4✔
1105
        CHECK_EQUAL(54, query.avg(col_int));
4✔
1106

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

1111
        CHECK_EQUAL(0.7f, query.max(col_flt));
4✔
1112
        query.max(col_flt, &ndx);
4✔
1113
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1114

1115
        CHECK_EQUAL(0.7f, query.sum(col_flt));
4✔
1116
        CHECK_EQUAL(0.7f, query.avg(col_flt));
4✔
1117

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

1122
        CHECK_EQUAL(0.8, query.max(col_dbl));
4✔
1123
        query.max(col_dbl, &ndx);
4✔
1124
        CHECK_EQUAL(obj0.get_key(), ndx);
4✔
1125

1126
        CHECK_EQUAL(0.8, query.sum(col_dbl));
4✔
1127
        CHECK_EQUAL(0.8, query.avg(col_dbl));
4✔
1128
    }
4✔
1129
}
2✔
1130

1131

1132
TEST(Query_AggregateSortedView)
1133
{
2✔
1134
    Table table;
2✔
1135
    auto col = table.add_column(type_Double, "col");
2✔
1136

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

1141
    TableView tv = table.where().greater(col, 1.0).find_all();
2✔
1142
    tv.sort(col, false);
2✔
1143

1144
    CHECK_EQUAL(2.0, tv.min(col));
2✔
1145
    CHECK_EQUAL(count, tv.max(col));
2✔
1146
    CHECK_APPROXIMATELY_EQUAL((count + 1) * count / 2, tv.sum(col)->get_double(), .1);
2✔
1147
}
2✔
1148

1149

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

1155
    Table t;
2✔
1156
    auto col_int = t.add_column(type_Int, "1");
2✔
1157
    auto col_str = t.add_column(type_String, "2");
2✔
1158
    auto col_dbl = t.add_column(type_Double, "3");
2✔
1159

1160
    ObjKey k0 = t.create_object().set_all(1, "1", 1.1).get_key();
2✔
1161
    t.create_object().set_all(2, "2", 2.2);
2✔
1162
    ObjKey k2 = t.create_object().set_all(3, "3", 3.3).get_key();
2✔
1163
    ObjKey k3 = t.create_object().set_all(4, "4", 4.4).get_key();
2✔
1164

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

1168

1169
    // Test if we can execute a copy
1170
    Query q2(q);
2✔
1171

1172
    CHECK_EQUAL(k2, q2.find());
2✔
1173

1174

1175
    // See if we can execute a copy of a deleted query. The copy should not contain references to the original.
1176
    Query* q3 = new Query(q);
2✔
1177
    Query* q4 = new Query(*q3);
2✔
1178
    delete q3;
2✔
1179

1180

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

1192
    CHECK_EQUAL(k2, q4->find());
2✔
1193
    delete q4;
2✔
1194

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

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

1207
    q7.greater(col_dbl, 4.0);
2✔
1208
    CHECK_EQUAL(k3, q7.find());
2✔
1209
    CHECK_EQUAL(k2, q6.find());
2✔
1210

1211

1212
    // See if we can append a criteria to a copy without modifying the original (copy should not contain references
1213
    // to original). Tests query_engine integer node.
1214
    Query q8 = t.column<Int>(col_int) > 2;
2✔
1215
    Query q9(q8);
2✔
1216

1217
    q9.greater(col_dbl, 4.0);
2✔
1218
    CHECK_EQUAL(k3, q9.find());
2✔
1219
    CHECK_EQUAL(k2, q8.find());
2✔
1220

1221

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

1227
    q11.greater(col_dbl, 4.0);
2✔
1228
    CHECK_EQUAL(k3, q11.find());
2✔
1229
    CHECK_EQUAL(k0, q10.find());
2✔
1230

1231
    // Test and_query() on a copy
1232
    Query q12 = t.column<Int>(col_int) > 2;
2✔
1233
    Query q13(q12);
2✔
1234

1235
    q13.and_query(t.column<String>(col_str) != "3");
2✔
1236
    CHECK_EQUAL(k3, q13.find());
2✔
1237
    CHECK_EQUAL(k2, q12.find());
2✔
1238
}
2✔
1239

1240
TEST(Query_TableViewMoveAssign1)
1241
{
2✔
1242
    Table t;
2✔
1243
    auto col_int = t.add_column(type_Int, "1");
2✔
1244

1245
    t.create_object().set(col_int, 1);
2✔
1246
    t.create_object().set(col_int, 2);
2✔
1247
    t.create_object().set(col_int, 3);
2✔
1248
    t.create_object().set(col_int, 4);
2✔
1249

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

1254
    // now deep copy should be destructed and replaced by new temporary
1255
    TableView tv = q.find_all();
2✔
1256

1257
    // the original should still work; destruction of temporaries and deep copies should have no references
1258
    // to original
1259
    tv = q.find_all();
2✔
1260
}
2✔
1261

1262
TEST(Query_TableViewMoveAssignLeak2)
1263
{
2✔
1264
    Table t;
2✔
1265
    auto col_int = t.add_column(type_Int, "1");
2✔
1266
    auto col_str = t.add_column(type_String, "2");
2✔
1267
    auto col_dbl = t.add_column(type_Double, "3");
2✔
1268

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

1272
    // Upon each find_all() call, tv copies the query 'q' into itself. See if this copying works
1273
    tv = q.find_all();
2✔
1274
    tv = q.find_all();
2✔
1275
    tv = q.find_all();
2✔
1276
    tv = q.find_all();
2✔
1277
    tv = q.find_all();
2✔
1278

1279
    tv.sort(col_int, true);
2✔
1280

1281
    tv = q.find_all();
2✔
1282

1283
    Query q2 = t.column<Int>(col_int) <= t.column<double>(col_dbl);
2✔
1284
    tv = q2.find_all();
2✔
1285
    q.and_query(q2);
2✔
1286
    tv = q.find_all();
2✔
1287

1288
    tv.sync_if_needed();
2✔
1289

1290
    ObjKey t2 = q.find();
2✔
1291
    static_cast<void>(t2);
2✔
1292
    tv = q.find_all();
2✔
1293
    tv.sync_if_needed();
2✔
1294
    t2 = q.find();
2✔
1295
    tv.sync_if_needed();
2✔
1296
    tv = q.find_all();
2✔
1297
    tv.sync_if_needed();
2✔
1298
    t2 = q.find();
2✔
1299
    tv.sync_if_needed();
2✔
1300
    tv = q.find_all();
2✔
1301
    tv.sync_if_needed();
2✔
1302
    tv = q.find_all();
2✔
1303
    tv.sync_if_needed();
2✔
1304

1305
    Query q3;
2✔
1306

1307
    q2 = t.column<Int>(col_int) <= t.column<double>(col_dbl);
2✔
1308
    q3 = q2;
2✔
1309

1310
    q3.find();
2✔
1311
    q2.find();
2✔
1312
}
2✔
1313

1314

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

1320
    Table t;
2✔
1321
    auto col_int = t.add_column(type_Int, "1");
2✔
1322
    auto col_dbl = t.add_column(type_Double, "3");
2✔
1323

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

1332
TEST(Query_DeepCopyTest)
1333
{
2✔
1334
    // If Query::first vector was relocated because of push_back, then Query would crash, because referenced
1335
    // pointers were pointing into it.
1336
    Table table;
2✔
1337
    table.add_column(type_Int, "first");
2✔
1338

1339
    Query q1 = table.where();
2✔
1340

1341
    Query q2(q1);
2✔
1342

1343
    q2.group();
2✔
1344
    q2.end_group();
2✔
1345
}
2✔
1346

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

1355
    Query q = table.where().equal(col, StringData(""));
2✔
1356
    q.count();
2✔
1357
    Query q2(q);
2✔
1358
}
2✔
1359

1360
TEST(Query_NullStrings)
1361
{
2✔
1362
    Table table;
2✔
1363
    auto col = table.add_column(type_String, "s", true);
2✔
1364

1365
    Query q;
2✔
1366
    TableView v;
2✔
1367

1368
    // Short strings
1369
    auto k0 = table.create_object().set<String>(col, "Albertslund").get_key(); // Normal non-empty string
2✔
1370
    auto k1 = table.create_object().set<String>(col, realm::null()).get_key(); // NULL string
2✔
1371
    auto k2 = table.create_object().set<String>(col, "").get_key();            // Empty string
2✔
1372

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

1378
    q = table.column<StringData>(col) != realm::null();
2✔
1379
    v = q.find_all();
2✔
1380
    CHECK_EQUAL(2, v.size());
2✔
1381
    CHECK_EQUAL(k0, v.get_key(0));
2✔
1382
    CHECK_EQUAL(k2, v.get_key(1));
2✔
1383

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

1391
    q = table.column<StringData>(col) == "";
2✔
1392
    v = q.find_all();
2✔
1393
    CHECK_EQUAL(1, v.size());
2✔
1394
    CHECK_EQUAL(k2, v.get_key(0));
2✔
1395

1396
    // Medium strings (16+)
1397
    table.get_object(k0).set<String>(col, "AlbertslundAlbertslundAlbert");
2✔
1398

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

1404
    q = table.column<StringData>(col) == "";
2✔
1405
    v = q.find_all();
2✔
1406
    CHECK_EQUAL(1, v.size());
2✔
1407
    CHECK_EQUAL(k2, v.get_key(0));
2✔
1408

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

1417
    q = table.column<StringData>(col) == "";
2✔
1418
    v = q.find_all();
2✔
1419
    CHECK_EQUAL(1, v.size());
2✔
1420
    CHECK_EQUAL(k2, v.get_key(0));
2✔
1421
}
2✔
1422

1423
TEST(Query_Nulls_Fuzzy)
1424
{
2✔
1425
    for (int attributes = 1; attributes < 5; attributes++) {
10✔
1426
        Random random(random_int<unsigned long>());
8✔
1427

1428
        for (size_t t = 0; t < 10; t++) {
88✔
1429
            Table table;
80✔
1430
            auto col = table.add_column(type_String, "string", true);
80✔
1431

1432
            if (attributes == 0) {
80✔
1433
            }
×
1434
            if (attributes == 1) {
80✔
1435
                table.add_search_index(col);
20✔
1436
            }
20✔
1437
            else if (attributes == 2) {
60✔
1438
                table.enumerate_string_column(col);
20✔
1439
            }
20✔
1440
            else if (attributes == 3) {
40✔
1441
                table.add_search_index(col);
20✔
1442
                table.enumerate_string_column(col);
20✔
1443
            }
20✔
1444
            else if (attributes == 4) {
20✔
1445
                table.enumerate_string_column(col);
20✔
1446
                table.add_search_index(col);
20✔
1447
            }
20✔
1448

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

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

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

1468
                    StringData sd;
3,947✔
1469
                    std::string st;
3,947✔
1470

1471
                    if (fastrand(1) == 0) {
3,947✔
1472
                        // null string
1473
                        sd = realm::null();
1,913✔
1474
                        st = "null";
1,913✔
1475
                    }
1,913✔
1476
                    else {
2,034✔
1477
                        // non-null string
1478
                        size_t len = static_cast<size_t>(fastrand(3));
2,034✔
1479
                        if (len == 0)
2,034✔
1480
                            len = 0;
511✔
1481
                        else if (len == 1)
1,523✔
1482
                            len = 7;
498✔
1483
                        else if (len == 2)
1,025✔
1484
                            len = 27;
506✔
1485
                        else
519✔
1486
                            len = 73;
519✔
1487

1488
                        if (fastrand(1) == 0) {
2,034✔
1489
                            // duplicate string
1490
                            sd = StringData(buf1, len);
1,008✔
1491
                            st = std::string(buf1, len);
1,008✔
1492
                        }
1,008✔
1493
                        else {
1,026✔
1494
                            // random string
1495
                            for (size_t s = 0; s < len; s++) {
28,854✔
1496
                                if (fastrand(100) > 20)
27,828✔
1497
                                    buf2[s] = 0; // zero byte
22,151✔
1498
                                else
5,677✔
1499
                                    buf2[s] = static_cast<char>(fastrand(255)); // random byte
5,677✔
1500
                            }
27,828✔
1501
                            // no generated string can equal "null" (our vector magic value for null) because
1502
                            // len == 4 is not possible
1503
                            sd = StringData(buf2, len);
1,026✔
1504
                            st = std::string(buf2, len);
1,026✔
1505
                        }
1,026✔
1506
                    }
2,034✔
1507

1508
                    try {
3,947✔
1509
                        size_t pos = random.draw_int_max<size_t>(100000);
3,947✔
1510
                        auto k = table.create_object(ObjKey(int64_t(pos))).set<String>(col, sd).get_key();
3,947✔
1511

1512
                        v.emplace(k, st);
3,947✔
1513
                    }
3,947✔
1514
                    catch (...) {
3,947✔
1515
                    }
×
1516
                    free(buf1);
3,947✔
1517
                }
3,947✔
1518
                else if (table.size() > 0) {
4,053✔
1519
                    // delete
1520
                    size_t row = random.draw_int_max<size_t>(table.size() - 1);
3,541✔
1521
                    Obj obj = table.get_object(row);
3,541✔
1522
                    obj.remove();
3,541✔
1523
                    v.erase(obj.get_key());
3,541✔
1524
                }
3,541✔
1525

1526

1527
                CHECK_EQUAL(table.size(), v.size());
8,000✔
1528
                for (auto& o : table) {
31,630✔
1529
                    auto k = o.get_key();
31,630✔
1530
                    if (v[k] == "null") {
31,630✔
1531
                        CHECK(o.get<String>(col).is_null());
15,780✔
1532
                    }
15,780✔
1533
                    else {
15,850✔
1534
                        CHECK(o.get<String>(col) == v[k]);
15,850✔
1535
                    }
15,850✔
1536
                }
31,630✔
1537
            }
8,000✔
1538
        }
80✔
1539
    }
8✔
1540
}
2✔
1541

1542
TEST(Query_BinaryNull)
1543
{
2✔
1544
    Table table;
2✔
1545
    auto col = table.add_column(type_Binary, "first", true);
2✔
1546

1547
    auto k0 = table.create_object().set(col, BinaryData()).get_key();
2✔
1548
    auto k1 = table.create_object()
2✔
1549
                  .set(col, BinaryData("", 0))
2✔
1550
                  .get_key(); // NOTE: Specify size = 0, else size turns into 1!
2✔
1551
    auto k2 = table.create_object().set(col, BinaryData("foo")).get_key();
2✔
1552

1553
    TableView t;
2✔
1554

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

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

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

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

1572
    t = (table.column<BinaryData>(col) != BinaryData("", 0)).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
    t = (BinaryData("", 0) != table.column<BinaryData>(col)).find_all();
2✔
1578
    CHECK_EQUAL(2, t.size());
2✔
1579
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1580
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1581

1582

1583
    // Old syntax
1584
    t = table.where().equal(col, BinaryData()).find_all();
2✔
1585
    CHECK_EQUAL(1, t.size());
2✔
1586
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1587

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

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

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

1601
    t = table.where().not_equal(col, BinaryData("", 0)).find_all();
2✔
1602
    CHECK_EQUAL(2, t.size());
2✔
1603
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1604
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1605

1606
    t = table.where().begins_with(col, BinaryData()).find_all();
2✔
1607
    CHECK_EQUAL(3, t.size());
2✔
1608

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

1614
    t = table.where().begins_with(col, BinaryData("foo")).find_all();
2✔
1615
    CHECK_EQUAL(1, t.size());
2✔
1616
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1617

1618
    t = table.where().ends_with(col, BinaryData()).find_all();
2✔
1619
    CHECK_EQUAL(3, t.size());
2✔
1620

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

1626
    t = table.where().ends_with(col, BinaryData("foo")).find_all();
2✔
1627
    CHECK_EQUAL(1, t.size());
2✔
1628
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1629
}
2✔
1630

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

1645
    auto k0 = table.create_object(ObjKey(4), {/*      */ {c1, 100}, {c2, 1}}).get_key();
2✔
1646
    auto k1 = table.create_object(ObjKey(5), {{c0, 0}, /*        */ {c2, 2}}).get_key();
2✔
1647
    auto k2 = table.create_object(ObjKey(6), {{c0, 123}, {c1, 200}, {c2, 3}}).get_key();
2✔
1648
    auto k3 = table.create_object(ObjKey(7), {/*                 */ {c2, 7}}).get_key();
2✔
1649

1650
    TableView t;
2✔
1651

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

1657
    t = table.where().equal(c1, null{}).find_all();
2✔
1658
    CHECK_EQUAL(2, t.size());
2✔
1659
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1660
    CHECK_EQUAL(k3, t.get_key(1));
2✔
1661

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

1666
    t = table.where().equal(c0, 123).find_all();
2✔
1667
    CHECK_EQUAL(1, t.size());
2✔
1668
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1669

1670
    t = table.where().not_equal(c0, null{}).find_all();
2✔
1671
    CHECK_EQUAL(2, t.size());
2✔
1672
    CHECK_EQUAL(k1, t.get_key(0));
2✔
1673
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1674

1675
    t = table.where().not_equal(c0, 0).find_all();
2✔
1676
    CHECK_EQUAL(3, t.size());
2✔
1677
    CHECK_EQUAL(k0, t.get_key(0));
2✔
1678
    CHECK_EQUAL(k2, t.get_key(1));
2✔
1679
    CHECK_EQUAL(k3, t.get_key(2));
2✔
1680

1681
    t = table.where().greater(c0, 0).find_all();
2✔
1682
    CHECK_EQUAL(1, t.size());
2✔
1683
    CHECK_EQUAL(k2, t.get_key(0));
2✔
1684

1685
    t = table.where().greater(c2, 5).find_all();
2✔
1686
    CHECK_EQUAL(1, t.size());
2✔
1687
    CHECK_EQUAL(k3, t.get_key(0));
2✔
1688
}
2✔
1689

1690
TEST(Query_IntegerNonNull)
1691
{
2✔
1692
    Table table;
2✔
1693
    auto col = table.add_column(type_Int, "first", false);
2✔
1694

1695
    table.create_object().set(col, 123);
2✔
1696
    table.create_object().set(col, 456);
2✔
1697
    table.create_object();
2✔
1698

1699
    TableView t;
2✔
1700

1701
    // Fixme, should you be able to query a non-nullable column against null?
1702
    //    t = table.where().equal(0, null{}).find_all();
1703
    //    CHECK_EQUAL(0, t.size());
1704
}
2✔
1705

1706
TEST(Query_64BitValues)
1707
{
2✔
1708
    Group g;
2✔
1709
    ObjKey m;
2✔
1710
    TableRef table = g.add_table("table");
2✔
1711
    auto c0 = table->add_column(type_Int, "key");
2✔
1712
    auto c1 = table->add_column(type_Int, "16bit");
2✔
1713

1714
    const int64_t start = 4485019129LL;
2✔
1715
    const int64_t count = 20; // First 16 SSE-searched, four fallback
2✔
1716
    const int64_t min = std::numeric_limits<int64_t>::min();
2✔
1717
    const int64_t max = std::numeric_limits<int64_t>::max();
2✔
1718

1719
    for (size_t i = 0; i < count; ++i) {
42✔
1720
        table->create_object().set(c0, start + i);
40✔
1721
    }
40✔
1722

1723
    auto it = table->begin();
2✔
1724
    for (int64_t v = 5; v > 0; v--) {
12✔
1725
        // Insert values 5, 4, 3, 2, 1
1726
        it->set(c1, v);
10✔
1727
        ++it;
10✔
1728
    }
10✔
1729

1730
    m = table->where().less(c1, 4).find();
2✔
1731
    CHECK_EQUAL(2, m.value);
2✔
1732

1733
    m = table->where().less(c1, 5).find();
2✔
1734
    CHECK_EQUAL(1, m.value);
2✔
1735

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

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

1747
    CHECK_EQUAL(count, table->where().greater(c0, min).count());
2✔
1748
    CHECK_EQUAL(count - 1, table->where().greater(c0, start).count());
2✔
1749
    CHECK_EQUAL(1, table->where().greater(c0, start + count - 2).count());
2✔
1750
    CHECK_EQUAL(0, table->where().greater(c0, start + count - 1).count());
2✔
1751
    CHECK_EQUAL(0, table->where().greater(c0, max).count());
2✔
1752

1753
    CHECK_EQUAL(count, table->where().greater_equal(c0, min).count());
2✔
1754
    CHECK_EQUAL(count, table->where().greater_equal(c0, start).count());
2✔
1755
    CHECK_EQUAL(count - 1, table->where().greater_equal(c0, start + 1).count());
2✔
1756
    CHECK_EQUAL(1, table->where().greater_equal(c0, start + count - 1).count());
2✔
1757
    CHECK_EQUAL(0, table->where().greater_equal(c0, start + count).count());
2✔
1758
    CHECK_EQUAL(0, table->where().greater_equal(c0, max).count());
2✔
1759
}
2✔
1760

1761
namespace {
1762

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

1774
bool equals(TableView& tv, const std::vector<int64_t>& keys)
1775
{
178✔
1776
    if (tv.size() != keys.size()) {
178✔
1777
        return false;
×
1778
    }
×
1779

1780
    size_t sz = tv.size();
178✔
1781
    for (size_t i = 0; i < sz; i++) {
24,372✔
1782
        if (tv.get_key(i).value != keys[i]) {
24,194✔
1783
            return false;
×
1784
        }
×
1785
    }
24,194✔
1786

1787
    return true;
178✔
1788
}
178✔
1789

1790
void fill_data(TableRef table)
1791
{
2✔
1792
    table->create_object().set_all(1, null(), null(), 1.1, true, Timestamp(12345, 0));
2✔
1793
    table->create_object().set_all(null(), null(), "foo", 2.2, null(), null());
2✔
1794
    table->create_object().set_all(3, 30.f, "bar", null(), false, Timestamp(12345, 67));
2✔
1795
}
2✔
1796
} // unnamed namespace
1797

1798
TEST(Query_NullShowcase)
1799
{
2✔
1800
    /*
1801
    Here we show how comparisons and arithmetic with null works in queries. Basic rules:
1802

1803
    null    +, -, *, /          value   ==   null
1804
    null    +, -, *, /          null    ==   null
1805

1806
    null    ==, >=, <=]         null    ==   true
1807
    null    !=, >, <            null    ==   false
1808

1809
    null    ==, >=, <=, >, <    value   ==   false
1810
    null    !=                  value   ==   true
1811

1812
    This does NOT follow SQL! In particular, (null == null) == true and
1813
    (null != value) == true.
1814

1815
    NOTE NOTE: There is currently only very little syntax checking.
1816

1817
    NOTE NOTE: For BinaryData, use BinaryData() instead of null().
1818

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

1828
    Group g;
2✔
1829
    TableRef table = g.add_table("Inventory");
2✔
1830
    create_columns(table);
2✔
1831

1832
    Obj obj0 = table->create_object();
2✔
1833
    Obj obj1 = table->create_object();
2✔
1834
    Obj obj2 = table->create_object();
2✔
1835

1836
    // Default values for all nullable columns
1837
    for (auto col : table->get_column_keys()) {
14✔
1838
        CHECK(obj0.is_null(col));
14✔
1839
    }
14✔
1840

1841
    obj0.set_all(null(), null(), null(), 1.1, true, Timestamp(12345, 0), BinaryData("foo"));
2✔
1842
    obj1.set_all(10, null(), "foo", 2.2, null(), null(), BinaryData("", 0));
2✔
1843
    obj2.set_all(20, 30.f, "bar", 3.3, false, Timestamp(12345, 67), null());
2✔
1844

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

1856
    // check int/double type mismatch error handling
1857
    CHECK_THROW_ANY(table->column<Int>(table->get_column_key("Description")));
2✔
1858

1859
    TableView tv;
2✔
1860

1861
    tv = (price == null()).find_all();
2✔
1862
    CHECK(equals(tv, {0}));
2✔
1863

1864
    tv = (price != null()).find_all();
2✔
1865
    CHECK(equals(tv, {1, 2}));
2✔
1866

1867
    // Note that this returns rows with null, which differs from SQL!
1868
    tv = (price == shipping).find_all();
2✔
1869
    CHECK(equals(tv, {0})); // null == null
2✔
1870

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

1875
    tv = (price != shipping).find_all();
2✔
1876
    CHECK(equals(tv, {1, 2})); // 10 != null
2✔
1877

1878
    tv = (price < 0 || price > 0).find_all();
2✔
1879
    CHECK(equals(tv, {1, 2}));
2✔
1880

1881
    // Shows that null + null == null, and 10 + null == null, and null < 100 == false
1882
    tv = table->query("Price + Shipping < 100").find_all();
2✔
1883
    CHECK(equals(tv, {2}));
2✔
1884

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

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

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

1897
    // Doubles
1898
    // (null > double) == false
1899
    tv = (price > rating).find_all();
2✔
1900
    CHECK(equals(tv, {1, 2}));
2✔
1901

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

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

1908

1909
    // Booleans
1910
    tv = (stock == true).find_all();
2✔
1911
    CHECK(equals(tv, {0}));
2✔
1912

1913
    tv = (stock == false).find_all();
2✔
1914
    CHECK(equals(tv, {2}));
2✔
1915

1916
    tv = (stock == null()).find_all();
2✔
1917
    CHECK(equals(tv, {1}));
2✔
1918

1919
    tv = (stock != null()).find_all();
2✔
1920
    CHECK(equals(tv, {0, 2}));
2✔
1921

1922
    // Dates
1923
    tv = (delivery == Timestamp(12345, 67)).find_all();
2✔
1924
    CHECK(equals(tv, {2}));
2✔
1925

1926
    tv = (delivery != Timestamp(12345, 67)).find_all();
2✔
1927
    CHECK(equals(tv, {0, 1}));
2✔
1928

1929
    tv = (delivery == null()).find_all();
2✔
1930
    CHECK(equals(tv, {1}));
2✔
1931

1932
    tv = (delivery != null()).find_all();
2✔
1933
    CHECK(equals(tv, {0, 2}));
2✔
1934

1935
    // BinaryData
1936
    //
1937
    // BinaryData only supports == and !=, and you cannot compare two columns - only a column and a constant
1938
    tv = (photo == BinaryData("foo")).find_all();
2✔
1939
    CHECK(equals(tv, {0}));
2✔
1940

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

1944
    tv = (photo == BinaryData()).find_all();
2✔
1945
    CHECK(equals(tv, {2}));
2✔
1946

1947
    tv = (photo != BinaryData("foo")).find_all();
2✔
1948
    CHECK(equals(tv, {1, 2}));
2✔
1949

1950
    // Old query syntax
1951
    tv = table->where().equal(col_price, null()).find_all();
2✔
1952
    CHECK(equals(tv, {0}));
2✔
1953

1954
    tv = table->where().not_equal(col_price, null()).find_all();
2✔
1955
    CHECK(equals(tv, {1, 2}));
2✔
1956

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

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

1968
    // Nullable floats in old syntax
1969
    tv = table->where().equal(col_shipping, null()).find_all();
2✔
1970
    CHECK(equals(tv, {0, 1}));
2✔
1971

1972
    tv = table->where().not_equal(col_shipping, null()).find_all();
2✔
1973
    CHECK(equals(tv, {2}));
2✔
1974

1975
    tv = table->where().greater(col_shipping, 0.0f).find_all();
2✔
1976
    CHECK(equals(tv, {2}));
2✔
1977

1978
    tv = table->where().less(col_shipping, 20.0f).find_all();
2✔
1979
    CHECK(equals(tv, {}));
2✔
1980

1981
    // TableView
1982
    size_t count;
2✔
1983
    std::optional<Mixed> m;
2✔
1984
    tv = table->where().find_all();
2✔
1985

1986
    // Integer column
1987
    m = tv.max(col_price);
2✔
1988
    CHECK_EQUAL(m, 20);
2✔
1989

1990
    m = tv.min(col_price);
2✔
1991
    CHECK_EQUAL(m, 10);
2✔
1992

1993
    count = 123;
2✔
1994
    m = tv.avg(col_price, &count);
2✔
1995
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 15., 0.001);
2✔
1996
    CHECK_EQUAL(count, 2);
2✔
1997

1998
    m = tv.sum(col_price);
2✔
1999
    CHECK_EQUAL(m, 30);
2✔
2000

2001

2002
    // Float column
2003
    m = tv.max(col_shipping);
2✔
2004
    CHECK_EQUAL(m, 30.);
2✔
2005

2006
    m = tv.min(col_shipping);
2✔
2007
    CHECK_EQUAL(m, 30.);
2✔
2008

2009
    count = 123;
2✔
2010
    m = tv.avg(col_shipping, &count);
2✔
2011
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 30., 0.001);
2✔
2012
    CHECK_EQUAL(count, 1);
2✔
2013

2014
    m = tv.sum(col_shipping);
2✔
2015
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 30., 0.001);
2✔
2016

2017
    // double column
2018
    m = tv.max(col_rating);
2✔
2019
    CHECK_EQUAL(m, 3.3);
2✔
2020
    m = tv.min(col_rating);
2✔
2021
    CHECK_EQUAL(m, 1.1);
2✔
2022
    m = tv.avg(col_rating);
2✔
2023
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), (1.1 + 2.2 + 3.3) / 3, 0.001);
2✔
2024
    m = tv.sum(col_rating);
2✔
2025
    CHECK_APPROXIMATELY_EQUAL(m->get_double(), 1.1 + 2.2 + 3.3, 0.001);
2✔
2026

2027
    // OldDateTime column
2028
    m = tv.max(col_date);
2✔
2029
    CHECK_EQUAL(m, Timestamp(12345, 67));
2✔
2030
    m = tv.min(col_date);
2✔
2031
    CHECK_EQUAL(m, Timestamp(12345, 0));
2✔
2032

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

2041
    obj0.set<Float>(col_shipping, std::numeric_limits<float>::signaling_NaN());
2✔
2042
    obj1.set<Float>(col_shipping, std::numeric_limits<float>::quiet_NaN());
2✔
2043

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

2050

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

2059
#if !defined(_WIN32) && !REALM_ARCHITECTURE_X86_32
2✔
2060
    CHECK(null::is_signaling(obj0.get<Float>(col_shipping)));
2✔
2061
#endif
2✔
2062

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

2067
    CHECK(!obj0.is_null(col_shipping));
2✔
2068
    CHECK(!obj1.is_null(col_shipping));
2✔
2069

2070
    obj0.set<Double>(col_rating, std::numeric_limits<double>::signaling_NaN());
2✔
2071
    obj1.set<Double>(col_rating, std::numeric_limits<double>::quiet_NaN());
2✔
2072
    CHECK(std::isnan(obj0.get<Double>(col_rating)));
2✔
2073
    CHECK(std::isnan(obj1.get<Double>(col_rating)));
2✔
2074

2075
// signaling_NaN() broken in VS2015, and broken in 32bit intel
2076
#if !defined(_WIN32) && !REALM_ARCHITECTURE_X86_32
2✔
2077
    CHECK(null::is_signaling(obj0.get<Double>(col_rating)));
2✔
2078
    CHECK(!null::is_signaling(obj1.get<Double>(col_rating)));
2✔
2079
#endif
2✔
2080

2081
    CHECK(!obj0.is_null(col_rating));
2✔
2082
    CHECK(!obj1.is_null(col_rating));
2✔
2083

2084
    // NOTE NOTE Queries on float/double columns that contain user-given NaNs are undefined.
2085
}
2✔
2086

2087

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

2098
        Obj obj = table->create_object();
2✔
2099

2100
        auto all_cols = table->get_column_keys();
2✔
2101

2102
        for (auto col : all_cols) {
14✔
2103
            CHECK(!table->is_nullable(col));
14✔
2104
        }
14✔
2105

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

2112
        for (auto col : all_cols) {
14✔
2113
            CHECK_THROW_ANY(obj.set_null(col));
14✔
2114
        }
14✔
2115

2116
        // verify that set_null() did not have any side effects
2117
        for (auto col : all_cols) {
14✔
2118
            CHECK(!obj.is_null(col));
14✔
2119
        }
14✔
2120
    }
2✔
2121

2122
    // Nullable columns: Tests that default value is null, and tests is_nullable() and set_null()
2123
    {
2✔
2124
        Group g;
2✔
2125
        TableRef table = g.add_table("Inventory");
2✔
2126
        create_columns(table);
2✔
2127

2128
        Obj obj = table->create_object();
2✔
2129

2130
        auto all_cols = table->get_column_keys();
2✔
2131

2132
        for (auto col : all_cols) {
14✔
2133
            CHECK(table->is_nullable(col));
14✔
2134
        }
14✔
2135

2136
        // default values should be null
2137
        for (auto col : all_cols) {
14✔
2138
            CHECK(obj.is_null(col));
14✔
2139
        }
14✔
2140

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

2153
        for (auto col : all_cols) {
14✔
2154
            CHECK(!obj.is_null(col));
14✔
2155
        }
14✔
2156

2157
        for (auto col : all_cols) {
14✔
2158
            obj.set_null(col);
14✔
2159
        }
14✔
2160

2161
        for (auto col : all_cols) {
14✔
2162
            CHECK(obj.is_null(col));
14✔
2163
        }
14✔
2164
    }
2✔
2165
}
2✔
2166

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

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

2189
    TableView tv;
2✔
2190

2191
    /*
2192
    Price<int>      Shipping<float>     Description<String>     Rating<double>      Stock<bool> Delivery<Timestamp>
2193
    ----------------------------------------------------------------------------------------------------------------
2194
    0   1           null                null                    1.1                 true          12345, 0
2195
    1   null        null                "foo"                   2.2                 null          null
2196
    2   3           30.0                "bar"                   null                false         12345, 67
2197
    */
2198

2199
    tv = (shipping > rating).find_all();
2✔
2200
    CHECK(equals(tv, {}));
2✔
2201

2202
    tv = (shipping < rating).find_all();
2✔
2203
    CHECK(equals(tv, {}));
2✔
2204

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

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

2211
    tv = (shipping == rating).find_all();
2✔
2212
    CHECK(equals(tv, {}));
2✔
2213

2214
    tv = (shipping != rating).find_all();
2✔
2215
    CHECK(equals(tv, {0, 1, 2}));
2✔
2216

2217
    // Comparison column with itself
2218
    tv = (shipping == shipping).find_all();
2✔
2219
    CHECK(equals(tv, {0, 1, 2}));
2✔
2220

2221
    tv = (shipping > shipping).find_all();
2✔
2222
    CHECK(equals(tv, {}));
2✔
2223

2224
    tv = (shipping < shipping).find_all();
2✔
2225
    CHECK(equals(tv, {}));
2✔
2226

2227
    tv = (shipping <= shipping).find_all();
2✔
2228
    CHECK(equals(tv, {0, 1, 2}));
2✔
2229

2230
    tv = (shipping >= shipping).find_all();
2✔
2231
    CHECK(equals(tv, {0, 1, 2}));
2✔
2232

2233
    tv = (rating == rating).find_all();
2✔
2234
    CHECK(equals(tv, {0, 1, 2}));
2✔
2235

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

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

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

2245
    tv = (rating >= rating).find_all();
2✔
2246
    CHECK(equals(tv, {0, 1, 2}));
2✔
2247

2248
    tv = (rating <= rating).find_all();
2✔
2249
    CHECK(equals(tv, {0, 1, 2}));
2✔
2250

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

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

2257
    tv = (price == price).find_all();
2✔
2258
    CHECK(equals(tv, {0, 1, 2}));
2✔
2259

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

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

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

2269
    tv = (price >= price).find_all();
2✔
2270
    CHECK(equals(tv, {0, 1, 2}));
2✔
2271

2272
    tv = (price <= price).find_all();
2✔
2273
    CHECK(equals(tv, {0, 1, 2}));
2✔
2274

2275
    tv = (delivery == delivery).find_all();
2✔
2276
    CHECK(equals(tv, {0, 1, 2}));
2✔
2277

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

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

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

2287
    tv = (delivery >= delivery).find_all();
2✔
2288
    CHECK(equals(tv, {0, 1, 2}));
2✔
2289

2290
    tv = (delivery <= delivery).find_all();
2✔
2291
    CHECK(equals(tv, {0, 1, 2}));
2✔
2292

2293
    tv = (description == description).find_all();
2✔
2294
    CHECK(equals(tv, {0, 1, 2}));
2✔
2295

2296
    tv = (description != description).find_all();
2✔
2297
    CHECK(equals(tv, {}));
2✔
2298

2299
    // Test a few untested things
2300
    tv = table->where().equal(col_rating, null()).find_all();
2✔
2301
    CHECK(equals(tv, {2}));
2✔
2302

2303
    tv = table->where().equal(col_price, null()).find_all();
2✔
2304
    CHECK(equals(tv, {1}));
2✔
2305

2306
    tv = table->where().not_equal(col_rating, null()).find_all();
2✔
2307
    CHECK(equals(tv, {0, 1}));
2✔
2308

2309
    tv = table->where().between(col_price, 2, 4).find_all();
2✔
2310
    CHECK(equals(tv, {2}));
2✔
2311

2312
    // between for floats
2313
    tv = table->where().between(col_shipping, 10.f, 40.f).find_all();
2✔
2314
    CHECK(equals(tv, {2}));
2✔
2315

2316
    tv = table->where().between(col_shipping, 0.f, 20.f).find_all();
2✔
2317
    CHECK(equals(tv, {}));
2✔
2318

2319
    tv = table->where().between(col_shipping, 40.f, 100.f).find_all();
2✔
2320
    CHECK(equals(tv, {}));
2✔
2321

2322
    // between for doubles
2323
    tv = table->where().between(col_rating, 0., 100.).find_all();
2✔
2324
    CHECK(equals(tv, {0, 1}));
2✔
2325

2326
    tv = table->where().between(col_rating, 1., 2.).find_all();
2✔
2327
    CHECK(equals(tv, {0}));
2✔
2328

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

2332
    tv = table->where().between(col_rating, 3., 100.).find_all();
2✔
2333
    CHECK(equals(tv, {}));
2✔
2334
}
2✔
2335

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

2348
    /*
2349
    Price<int>      Shipping<float>     Description<String>     Rating<double>      Stock<bool>
2350
    Delivery<OldDateTime>     ts<Timestamp>
2351
    --------------------------------------------------------------------------------------------------------------------------------------
2352
    null            null                null                    null                null            null null
2353
    */
2354

2355
    TableView tv;
2✔
2356
    ObjKey match;
2✔
2357
    size_t count;
2✔
2358

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

2367
        match = ObjKey(123);
4✔
2368
        tv.min(col_price, &match);
4✔
2369
        CHECK_EQUAL(match, realm::null_key);
4✔
2370

2371
        CHECK_EQUAL(tv.sum(col_price), 0);
4✔
2372
        count = 123;
4✔
2373
        CHECK(tv.avg(col_price, &count)->is_null());
4✔
2374
        CHECK_EQUAL(count, 0);
4✔
2375

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

2381
        match = ObjKey(123);
4✔
2382
        CHECK(tv.min(col_shipping, &match)->is_null());
4✔
2383
        CHECK_EQUAL(match, realm::null_key);
4✔
2384

2385
        CHECK_EQUAL(tv.sum(col_shipping), 0.);
4✔
2386
        count = 123;
4✔
2387
        CHECK(tv.avg(col_shipping, &count)->is_null());
4✔
2388
        CHECK_EQUAL(count, 0);
4✔
2389

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

2395
        match = ObjKey(123);
4✔
2396
        CHECK(tv.min(col_rating, &match)->is_null());
4✔
2397
        CHECK_EQUAL(match, realm::null_key);
4✔
2398

2399
        CHECK_EQUAL(tv.sum(col_rating), 0.);
4✔
2400
        count = 123;
4✔
2401
        CHECK(tv.avg(col_rating, &count)->is_null());
4✔
2402
        CHECK_EQUAL(count, 0);
4✔
2403

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

2409
        match = ObjKey(123);
4✔
2410
        tv.min(col_date, &match);
4✔
2411
        CHECK_EQUAL(match, realm::null_key);
4✔
2412
    };
4✔
2413

2414
    // There are rows in TableView but they all point to null
2415
    tv = table->where().find_all();
2✔
2416
    test_tv();
2✔
2417

2418
    // There are 0 rows in TableView
2419
    tv = table->where().equal(col_price, 123).find_all();
2✔
2420
    test_tv();
2✔
2421

2422
    // Now we test that average does not include nulls in row count:
2423
    /*
2424
    Price<int>      Shipping<float>     Description<String>     Rating<double>      Stock<bool> Delivery<OldDateTime>
2425
    ----------------------------------------------------------------------------------------------------------------
2426
    null            null                null                    null                null            null
2427
    10              10.f                null                    10.                 null            null
2428
    */
2429

2430
    table->create_object().set_all(10, 10.f, null(), 10.);
2✔
2431

2432
    tv = table->where().find_all();
2✔
2433
    count = 123;
2✔
2434
    CHECK_EQUAL(tv.avg(col_price, &count), 10);
2✔
2435
    CHECK_EQUAL(count, 1);
2✔
2436
    count = 123;
2✔
2437
    CHECK_EQUAL(tv.avg(col_shipping, &count), 10.);
2✔
2438
    CHECK_EQUAL(count, 1);
2✔
2439
    count = 123;
2✔
2440
    CHECK_EQUAL(tv.avg(col_rating, &count), 10.);
2✔
2441
    CHECK_EQUAL(count, 1);
2✔
2442
}
2✔
2443

2444

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

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

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

2471
    // Reference lists used to verify query results
2472
    std::vector<int64_t> nulls;     // List of rows that have all fields set to null
2✔
2473
    std::vector<int64_t> non_nulls; // List of non-null rows
2✔
2474

2475
    auto all_cols = table->get_column_keys();
2✔
2476

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

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

2493
    // Fill out non_nulls vector
2494
    for (auto& o : *table) {
4,000✔
2495
        if (!o.is_null(col_price))
4,000✔
2496
            non_nulls.push_back(o.get_key().value);
3,600✔
2497
    }
4,000✔
2498

2499
    std::sort(nulls.begin(), nulls.end());
2✔
2500
    TableView tv;
2✔
2501

2502
    // Search for nulls and non-nulls and verify matches against our manually created `nulls` and non_nulls vectors.
2503
    // Do that for all Realm data types
2504
    tv = (price == null()).find_all();
2✔
2505
    CHECK(equals(tv, nulls));
2✔
2506

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

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

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

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

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

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

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

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

2531
    tv = (stock != null()).find_all();
2✔
2532
    CHECK(equals(tv, non_nulls));
2✔
2533

2534
    tv = (delivery == null()).find_all();
2✔
2535
    CHECK(equals(tv, nulls));
2✔
2536

2537
    tv = (delivery != null()).find_all();
2✔
2538
    CHECK(equals(tv, non_nulls));
2✔
2539
}
2✔
2540

2541
TEST(Query_Null_Sort)
2542
{
2✔
2543
    Group g;
2✔
2544
    TableRef table = g.add_table("Inventory");
2✔
2545
    create_columns(table);
2✔
2546

2547
    auto k0 = table->create_object().set_all(0, 0.f, "0", 0.0, false, Timestamp(0, 0)).get_key();
2✔
2548
    auto k1 = table->create_object().get_key();
2✔
2549
    auto k2 = table->create_object().set_all(2, 2.f, "2", 2.0, true, Timestamp(2, 0)).get_key();
2✔
2550

2551
    auto all_cols = table->get_column_keys();
2✔
2552
    for (int i = 0; i <= 5; i++) {
14✔
2553
        TableView tv = table->where().find_all();
12✔
2554
        CHECK(tv.size() == 3);
12✔
2555

2556
        tv.sort(all_cols[i], true);
12✔
2557
        CHECK_EQUAL(tv.get_key(0), k1);
12✔
2558
        CHECK_EQUAL(tv.get_key(1), k0);
12✔
2559
        CHECK_EQUAL(tv.get_key(2), k2);
12✔
2560

2561
        tv = table->where().find_all();
12✔
2562
        tv.sort(all_cols[i], false);
12✔
2563
        CHECK_EQUAL(tv.get_key(0), k2);
12✔
2564
        CHECK_EQUAL(tv.get_key(1), k0);
12✔
2565
        CHECK_EQUAL(tv.get_key(2), k1);
12✔
2566
    }
12✔
2567
}
2✔
2568

2569
TEST(Query_LinkCounts)
2570
{
2✔
2571
    Group group;
2✔
2572
    TableRef table1 = group.add_table("table1");
2✔
2573
    auto col_str = table1->add_column(type_String, "str");
2✔
2574

2575
    auto k0 = table1->create_object().set(col_str, "abc").get_key();
2✔
2576
    auto k1 = table1->create_object().set(col_str, "def").get_key();
2✔
2577
    auto k2 = table1->create_object().set(col_str, "ghi").get_key();
2✔
2578

2579
    TableRef table2 = group.add_table("table2");
2✔
2580
    auto col_int = table2->add_column(type_Int, "int");
2✔
2581
    auto col_link = table2->add_column(*table1, "link");
2✔
2582
    auto col_linklist = table2->add_column_list(*table1, "linklist");
2✔
2583

2584
    table2->create_object().set_all(0);
2✔
2585
    table2->create_object().set_all(1, k1).get_linklist(col_linklist).add(k1);
2✔
2586
    auto ll = table2->create_object().set_all(2, k2).get_linklist(col_linklist);
2✔
2587
    ll.add(k1);
2✔
2588
    ll.add(k2);
2✔
2589

2590
    Query q;
2✔
2591
    ObjKey match;
2✔
2592

2593
    // Verify that queries against the count of a LinkList column work.
2594
    q = table2->column<Link>(col_linklist).count() == 0;
2✔
2595
    match = q.find();
2✔
2596
    CHECK_EQUAL(k0, match);
2✔
2597

2598
    q = table2->column<Link>(col_linklist).count() == 1;
2✔
2599
    match = q.find();
2✔
2600
    CHECK_EQUAL(k1, match);
2✔
2601

2602
    q = table2->column<Link>(col_linklist).count() >= 1;
2✔
2603
    auto tv = q.find_all();
2✔
2604
    CHECK_EQUAL(tv.size(), 2);
2✔
2605
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
2606
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
2607

2608

2609
    // Verify that queries against the count of a Link column work.
2610
    q = table2->column<Link>(col_link).count() == 0;
2✔
2611
    match = q.find();
2✔
2612
    CHECK_EQUAL(k0, match);
2✔
2613

2614
    q = table2->column<Link>(col_link).count() == 1;
2✔
2615
    tv = q.find_all();
2✔
2616
    CHECK_EQUAL(tv.size(), 2);
2✔
2617
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
2618
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
2619

2620
    // Verify that reusing the count expression works.
2621
    auto link_count = table2->column<Link>(col_linklist).count();
2✔
2622
    size_t match_count = (link_count == 0).count();
2✔
2623
    CHECK_EQUAL(1, match_count);
2✔
2624

2625
    match_count = (link_count >= 1).count();
2✔
2626
    CHECK_EQUAL(2, match_count);
2✔
2627

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

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

2655

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

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

2699
    size_t keys_added = 0;
2700
};
2701

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

2710
    // table1
2711
    // 0: 789 789.0f 789.0
2712
    // 1: 456 456.0f 456.0
2713
    // 2: 123 123.0f 123.0
2714
    // 3: null null null
2715

2716
    auto k0 = table1->create_object().set_all(789, 789.f, 789.).get_key();
6✔
2717
    auto k1 = table1->create_object().set_all(456, 456.f, 456.).get_key();
6✔
2718
    auto k2 = table1->create_object().set_all(123, 123.f, 123.).get_key();
6✔
2719
    auto k3 = table1->create_object().get_key();
6✔
2720

2721
    TEST_TYPE test_container;
6✔
2722
    TableRef table2 = group.add_table("table2");
6✔
2723
    ColKey col_linktest = test_container.add_link_column(table2, table1);
6✔
2724

2725
    // table2
2726
    // 0: { }
2727
    // 1: { 1 }
2728
    // 2: { 1, 2 }
2729
    // 3: { 1, 2, 3 }
2730

2731
    test_container.create_object_with_links(table2, col_linktest, {});
6✔
2732
    test_container.create_object_with_links(table2, col_linktest, {k1});
6✔
2733
    test_container.create_object_with_links(table2, col_linktest, {k1, k2});
6✔
2734
    test_container.create_object_with_links(table2, col_linktest, {k1, k2, k3});
6✔
2735

2736
    Query q;
6✔
2737
    TableView tv;
6✔
2738

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

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

2750
    q = table2->column<Link>(col_linktest).column<Int>(col_int).min() == null();
6✔
2751
    tv = q.find_all();
6✔
2752
    CHECK_EQUAL(tv.size(), 1);
6✔
2753
    CHECK_EQUAL(k0, tv.get_key(0));
6✔
2754

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

2761
    q = table2->column<Link>(col_linktest).column<Float>(col_float).min() == 456.0f;
6✔
2762
    tv = q.find_all();
6✔
2763
    CHECK_EQUAL(tv.size(), 1);
6✔
2764
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2765

2766
    q = table2->column<Link>(col_linktest).column<Double>(col_double).min() == 123.0;
6✔
2767
    tv = q.find_all();
6✔
2768
    CHECK_EQUAL(tv.size(), 2);
6✔
2769
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2770
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2771

2772
    q = table2->column<Link>(col_linktest).column<Double>(col_double).min() == 456.0;
6✔
2773
    tv = q.find_all();
6✔
2774
    CHECK_EQUAL(tv.size(), 1);
6✔
2775
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2776
}
6✔
2777

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

2786
    // table1
2787
    // 0: 123 123.0f 123.0
2788
    // 1: 456 456.0f 456.0
2789
    // 2: 789 789.0f 789.0
2790
    // 3: null null null
2791

2792
    ObjKeys keys({3, 5, 7, 9});
6✔
2793
    table1->create_objects(keys);
6✔
2794
    auto it = table1->begin();
6✔
2795
    it->set_all(123, 123.f, 123.);
6✔
2796
    (++it)->set_all(456, 456.f, 456.);
6✔
2797
    (++it)->set_all(789, 789.f, 789.);
6✔
2798

2799
    TEST_TYPE test_container;
6✔
2800
    TableRef table2 = group.add_table("table2");
6✔
2801
    auto col_double = table2->add_column(type_Double, "double");
6✔
2802
    auto col_link = table2->add_column(*table1, "link");
6✔
2803
    ColKey col_linktest = test_container.add_link_column(table2, table1);
6✔
2804

2805
    // table2
2806
    // 0: 456.0 ->0 { }
2807
    // 1: 456.0 ->1 { 1 }
2808
    // 2: 456.0 ->2 { 1, 2 }
2809
    // 3: 456.0 ->3 { 1, 2, 3 }
2810

2811
    auto k0 = table2->create_object().set_all(456.0, keys[0]).get_key();
6✔
2812
    auto k1 = table2->create_object().set_all(456.0, keys[1]).get_key();
6✔
2813
    auto k2 = table2->create_object().set_all(456.0, keys[2]).get_key();
6✔
2814
    auto k3 = table2->create_object().set_all(456.0, keys[3]).get_key();
6✔
2815

2816
    test_container.add_links_to(table2, col_linktest, k0, {});
6✔
2817
    test_container.add_links_to(table2, col_linktest, k1, {keys[1]});
6✔
2818
    test_container.add_links_to(table2, col_linktest, k2, {keys[1], keys[2]});
6✔
2819
    test_container.add_links_to(table2, col_linktest, k3, {keys[1], keys[2], keys[3]});
6✔
2820

2821
    Query q;
6✔
2822
    TableView tv;
6✔
2823

2824
    // Maximum.
2825

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

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

2837
    q = table2->column<Link>(col_linktest).column<Int>(col_int).max() == null();
6✔
2838
    tv = q.find_all();
6✔
2839
    CHECK_EQUAL(tv.size(), 1);
6✔
2840
    CHECK_EQUAL(k0, tv.get_key(0));
6✔
2841

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

2848
    q = table2->column<Link>(col_linktest).column<Int>(col_int).max() == table2->column<Double>(col_double);
6✔
2849
    tv = q.find_all();
6✔
2850
    CHECK_EQUAL(tv.size(), 1);
6✔
2851
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2852

2853

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

2860
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).max() == 456.0f;
6✔
2861
    tv = q.find_all();
6✔
2862
    CHECK_EQUAL(tv.size(), 1);
6✔
2863
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2864

2865

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

2872
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).max() == 456.0;
6✔
2873
    tv = q.find_all();
6✔
2874
    CHECK_EQUAL(tv.size(), 1);
6✔
2875
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2876

2877

2878
    // Sum.
2879
    // Floating point results below may be inexact for some combination of architectures, compilers, and compiler
2880
    // flags.
2881

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

2888
    q = table2->column<Link>(col_linktest).column<Int>(col_int).sum() == 456;
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->link(col_link).column<Int>(col_int);
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
    q = table2->column<Link>(col_linktest).column<Int>(col_int).sum() == table2->column<Double>(col_double);
6✔
2899
    tv = q.find_all();
6✔
2900
    CHECK_EQUAL(tv.size(), 1);
6✔
2901
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2902

2903

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

2910
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).sum() == 456.0f;
6✔
2911
    tv = q.find_all();
6✔
2912
    CHECK_EQUAL(tv.size(), 1);
6✔
2913
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2914

2915

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

2922
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).sum() == 456.0;
6✔
2923
    tv = q.find_all();
6✔
2924
    CHECK_EQUAL(tv.size(), 1);
6✔
2925
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2926

2927

2928
    // Average.
2929
    // Floating point results below may be inexact for some combination of architectures, compilers, and compiler
2930
    // flags.
2931

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

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

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

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

2954
    q = table2->column<Link>(col_linktest).column<Int>(col_int).average() == table2->column<Double>(col_double);
6✔
2955
    tv = q.find_all();
6✔
2956
    CHECK_EQUAL(tv.size(), 1);
6✔
2957
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2958

2959

2960
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).average() == 622.5;
6✔
2961
    tv = q.find_all();
6✔
2962
    CHECK_EQUAL(tv.size(), 2);
6✔
2963
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2964
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2965

2966
    q = table2->column<Link>(col_linktest).column<Float>(col_flt).average() == 456.0f;
6✔
2967
    tv = q.find_all();
6✔
2968
    CHECK_EQUAL(tv.size(), 1);
6✔
2969
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2970

2971

2972
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).average() == 622.5;
6✔
2973
    tv = q.find_all();
6✔
2974
    CHECK_EQUAL(tv.size(), 2);
6✔
2975
    CHECK_EQUAL(k2, tv.get_key(0));
6✔
2976
    CHECK_EQUAL(k3, tv.get_key(1));
6✔
2977

2978
    q = table2->column<Link>(col_linktest).column<Double>(col_dbl).average() == 456.0;
6✔
2979
    tv = q.find_all();
6✔
2980
    CHECK_EQUAL(tv.size(), 1);
6✔
2981
    CHECK_EQUAL(k1, tv.get_key(0));
6✔
2982
}
6✔
2983

2984
TEST_TYPES(Query_OperatorsOverLink, TestLinkList, TestLinkSet, TestDictionaryLinkValues)
2985
{
6✔
2986
    Group group;
6✔
2987
    TableRef table1 = group.add_table("table1");
6✔
2988
    table1->add_column(type_Int, "int");
6✔
2989
    table1->add_column(type_Double, "double");
6✔
2990

2991
    // table1
2992
    // 0: 2 2.0
2993
    // 1: 3 3.0
2994

2995
    ObjKeys keys({5, 6});
6✔
2996
    table1->create_objects(keys);
6✔
2997
    table1->get_object(keys[0]).set_all(2, 2.0);
6✔
2998
    table1->get_object(keys[1]).set_all(3, 3.0);
6✔
2999

3000
    TEST_TYPE test_container;
6✔
3001
    TableRef table2 = group.add_table("table2");
6✔
3002
    table2->add_column(type_Int, "int");
6✔
3003
    ColKey col_linktest = test_container.add_link_column(table2, table1);
6✔
3004

3005
    // table2
3006
    // 0:  0 { }
3007
    // 1:  4 { 0 }
3008
    // 2:  4 { 1, 0 }
3009

3010
    table2->create_object();
6✔
3011
    auto k1 = table2->create_object().set_all(4).get_key();
6✔
3012
    auto k2 = table2->create_object().set_all(4).get_key();
6✔
3013

3014
    test_container.add_links_to(table2, col_linktest, k1, {keys[0]});
6✔
3015
    test_container.add_links_to(table2, col_linktest, k2, {keys[1], keys[0]});
6✔
3016

3017
    Query q;
6✔
3018
    TableView tv;
6✔
3019

3020
    // Binary operators.
3021

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

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

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

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

3056
TEST(Query_CompareLinkedColumnVsColumn)
3057
{
2✔
3058
    Group group;
2✔
3059
    TableRef table1 = group.add_table("table1");
2✔
3060
    auto col_int = table1->add_column(type_Int, "int");
2✔
3061
    auto col_dbl = table1->add_column(type_Double, "double");
2✔
3062

3063
    // table1
3064
    // 0: 2 2.0
3065
    // 1: 3 3.0
3066

3067
    ObjKeys keys({5, 6});
2✔
3068
    table1->create_objects(keys);
2✔
3069
    table1->get_object(keys[0]).set_all(2, 2.0);
2✔
3070
    table1->get_object(keys[1]).set_all(3, 3.0);
2✔
3071

3072
    TableRef table2 = group.add_table("table2");
2✔
3073
    auto col_int2 = table2->add_column(type_Int, "int");
2✔
3074
    auto col_link1 = table2->add_column(*table1, "link1");
2✔
3075
    auto col_link2 = table2->add_column(*table1, "link2");
2✔
3076

3077
    // table2
3078
    // 0: 2 {   } { 0 }
3079
    // 1: 4 { 0 } { 1 }
3080
    // 2: 4 { 1 } {   }
3081

3082
    auto k0 = table2->create_object().set_all(2, null(), keys[0]).get_key();
2✔
3083
    auto k1 = table2->create_object().set_all(4, keys[0], keys[1]).get_key();
2✔
3084
    auto k2 = table2->create_object().set_all(4, keys[1], null()).get_key();
2✔
3085

3086
    Query q;
2✔
3087
    TableView tv;
2✔
3088

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

3095
    q = table2->link(col_link1).column<Double>(col_dbl) < table2->column<Int>(col_int2);
2✔
3096
    tv = q.find_all();
2✔
3097
    CHECK_EQUAL(tv.size(), 2);
2✔
3098
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
3099
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
3100

3101
    q = table2->link(col_link2).column<Int>(col_int) == table2->column<Int>(col_int2);
2✔
3102
    tv = q.find_all();
2✔
3103
    CHECK_EQUAL(tv.size(), 1);
2✔
3104
    CHECK_EQUAL(k0, tv.get_key(0));
2✔
3105
}
2✔
3106

3107
TEST(Query_CompareThroughUnaryLinks)
3108
{
2✔
3109
    Group group;
2✔
3110
    TableRef table1 = group.add_table("table1");
2✔
3111
    auto col_int = table1->add_column(type_Int, "int");
2✔
3112
    auto col_dbl = table1->add_column(type_Double, "double");
2✔
3113
    auto col_str = table1->add_column(type_String, "string");
2✔
3114

3115
    // table1
3116
    // 0: 2 2.0 "abc"
3117
    // 1: 3 3.0 "def"
3118
    // 2: 8 8.0 "def"
3119

3120
    ObjKeys keys({5, 6, 7});
2✔
3121
    table1->create_objects(keys);
2✔
3122
    table1->get_object(keys[0]).set_all(2, 2.0, "abc");
2✔
3123
    table1->get_object(keys[1]).set_all(3, 3.0, "def");
2✔
3124
    table1->get_object(keys[2]).set_all(8, 8.0, "def");
2✔
3125

3126
    TableRef table2 = group.add_table("table2");
2✔
3127
    auto col_link1 = table2->add_column(*table1, "link1");
2✔
3128
    auto col_link2 = table2->add_column(*table1, "link2");
2✔
3129

3130
    // table2
3131
    // 0: {   } { 0 }
3132
    // 1: { 0 } { 1 }
3133
    // 2: { 1 } { 2 }
3134
    // 3: { 2 } {   }
3135

3136
    table2->create_object().set_all(null(), keys[0]).get_key();
2✔
3137
    auto k1 = table2->create_object().set_all(keys[0], keys[1]).get_key();
2✔
3138
    auto k2 = table2->create_object().set_all(keys[1], keys[2]).get_key();
2✔
3139
    table2->create_object().set_all(keys[2], null()).get_key();
2✔
3140

3141
    Query q;
2✔
3142
    TableView tv;
2✔
3143

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

3150
    q = table2->link(col_link1).column<Double>(col_dbl) < table2->link(col_link2).column<Double>(col_dbl);
2✔
3151
    tv = q.find_all();
2✔
3152
    CHECK_EQUAL(tv.size(), 2);
2✔
3153
    CHECK_EQUAL(k1, tv.get_key(0));
2✔
3154
    CHECK_EQUAL(k2, tv.get_key(1));
2✔
3155

3156
    q = table2->link(col_link1).column<String>(col_str) == table2->link(col_link2).column<String>(col_str);
2✔
3157
    tv = q.find_all();
2✔
3158
    CHECK_EQUAL(tv.size(), 1);
2✔
3159
    CHECK_EQUAL(k2, tv.get_key(0));
2✔
3160
}
2✔
3161

3162
TEST(Query_DeepLink)
3163
{
2✔
3164

3165
    //
3166
    // +---------+--------+------------+
3167
    // | int     | bool   | list       |
3168
    // +---------+--------+------------+
3169
    // |       0 | true   | null       |
3170
    // |       1 | false  | 0          |
3171
    // |       2 | true   | 0, 1       |
3172
    // |       N | even(N)| 0, .., N-1 |
3173
    // +---------+--------+-------------+
3174

3175
    const int N = 10;
2✔
3176

3177
    Group group;
2✔
3178
    TableRef table = group.add_table("test");
2✔
3179
    table->add_column(type_Int, "int");
2✔
3180
    auto col_bool = table->add_column(type_Bool, "bool");
2✔
3181
    auto col_linklist = table->add_column_list(*table, "list");
2✔
3182

3183
    for (int j = 0; j < N; ++j) {
22✔
3184
        TableView view = table->where().find_all();
20✔
3185

3186
        Obj obj = table->create_object().set_all(j, (j % 2) == 0);
20✔
3187
        auto ll = obj.get_linklist(col_linklist);
20✔
3188
        for (size_t i = 0; i < view.size(); ++i) {
110✔
3189
            ll.add(view.get_key(i));
90✔
3190
        }
90✔
3191
    }
20✔
3192

3193
    Query query = table->link(col_linklist).column<Bool>(col_bool) == true;
2✔
3194
    TableView view = query.find_all();
2✔
3195
    CHECK_EQUAL(N - 1, view.size());
2✔
3196
}
2✔
3197

3198
TEST(Query_LinksToDeletedOrMovedRow)
3199
{
2✔
3200
    // This test is not that relevant with stable keys
3201
    Group group;
2✔
3202

3203
    TableRef source = group.add_table("source");
2✔
3204
    TableRef target = group.add_table("target");
2✔
3205

3206
    auto col_link = source->add_column(*target, "link");
2✔
3207
    auto col_name = target->add_column(type_String, "name");
2✔
3208

3209
    ObjKeys keys({4, 6, 8});
2✔
3210
    target->create_objects(keys);
2✔
3211
    target->get_object(keys[0]).set(col_name, "A");
2✔
3212
    target->get_object(keys[1]).set(col_name, "B");
2✔
3213
    target->get_object(keys[2]).set(col_name, "C");
2✔
3214

3215
    source->create_object().set(col_link, keys[0]);
2✔
3216
    source->create_object().set(col_link, keys[1]).get_key();
2✔
3217
    source->create_object().set(col_link, keys[2]);
2✔
3218

3219
    Query qA = source->column<Link>(col_link) == target->get_object(keys[0]);
2✔
3220
    Query qB = source->column<Link>(col_link) == target->get_object(keys[1]);
2✔
3221
    Query qC = source->column<Link>(col_link) == target->get_object(keys[2]);
2✔
3222

3223
    // Remove first object
3224
    target->remove_object(keys[0]);
2✔
3225

3226
    // Row A should not be found as it has been removed.
3227
    TableView tvA = qA.find_all();
2✔
3228
    CHECK_EQUAL(0, tvA.size());
2✔
3229

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

3236
    // Row C should still be found
3237
    TableView tvC = qC.find_all();
2✔
3238
    CHECK_EQUAL(1, tvC.size());
2✔
3239
    CHECK_EQUAL(keys[2], tvC[0].get<ObjKey>(col_link));
2✔
3240
    CHECK_EQUAL("C", tvC.get_object(0).get_linked_object(col_link).get<String>(col_name));
2✔
3241
}
2✔
3242

3243
// Triggers bug in compare_relation()
3244
TEST(Query_BrokenFindGT)
3245
{
2✔
3246
    Group group;
2✔
3247
    TableRef table = group.add_table("test");
2✔
3248
    auto col = table->add_column(type_Int, "int");
2✔
3249

3250
    const size_t rows = 12;
2✔
3251
    for (size_t i = 0; i < rows; ++i) {
26✔
3252
        table->create_object().set(col, int64_t(i + 2));
24✔
3253
    }
24✔
3254

3255
    table->create_object().set(col, 1);
2✔
3256
    table->create_object().set(col, 1);
2✔
3257
    table->create_object().set(col, 1);
2✔
3258

3259
    for (size_t i = 0; i < 3; ++i) {
8✔
3260
        table->create_object().set(col, int64_t(i + 2));
6✔
3261
    }
6✔
3262

3263
    CHECK_EQUAL(18, table->size());
2✔
3264

3265
    Query q = table->where().greater(col, 1);
2✔
3266
    TableView tv = q.find_all();
2✔
3267
    CHECK_EQUAL(15, tv.size());
2✔
3268

3269
    for (size_t i = 0; i < tv.size(); ++i) {
32✔
3270
        CHECK_NOT_EQUAL(1, tv[i].get<Int>(col));
30✔
3271
    }
30✔
3272
}
2✔
3273

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

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

3289
            table->create_object().set(col, t);
1,800✔
3290
        }
1,800✔
3291

3292
        for (int64_t s = -2; s < 18; s++) {
2,100✔
3293
            Query q_g = table->where().greater(col, s);
2,000✔
3294
            TableView tv_g = q_g.find_all();
2,000✔
3295
            for (size_t i = 0; i < tv_g.size(); ++i) {
18,577✔
3296
                CHECK(tv_g[i].get<Int>(col) > s);
16,577✔
3297
            }
16,577✔
3298

3299
            Query q_l = table->where().less(col, s);
2,000✔
3300
            TableView tv_l = q_l.find_all();
2,000✔
3301
            for (size_t i = 0; i < tv_l.size(); ++i) {
19,701✔
3302
                CHECK(tv_l[i].get<Int>(col) < s);
17,701✔
3303
            }
17,701✔
3304

3305
            Query q_le = table->where().less_equal(col, s);
2,000✔
3306
            TableView tv_le = q_le.find_all();
2,000✔
3307
            for (size_t i = 0; i < tv_le.size(); ++i) {
21,423✔
3308
                CHECK(tv_le[i].get<Int>(col) <= s);
19,423✔
3309
            }
19,423✔
3310

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

3318
TEST(Query_AverageNullableColumns)
3319
{
2✔
3320
    Table table;
2✔
3321
    auto col_int = table.add_column(type_Int, "int", true);
2✔
3322
    auto col_float = table.add_column(type_Float, "float", true);
2✔
3323
    auto col_double = table.add_column(type_Double, "double", true);
2✔
3324

3325
    CHECK(table.where().avg(col_int)->is_null());
2✔
3326
    CHECK(table.where().avg(col_float)->is_null());
2✔
3327
    CHECK(table.where().avg(col_double)->is_null());
2✔
3328

3329
    //
3330
    // +-----+-------+--------+
3331
    // | int | float | double |
3332
    // +-----+-------+--------+
3333
    // |   2 |     2 |      2 |
3334
    // |   4 |     4 |      4 |
3335
    // +-----+-------+--------+
3336

3337
    table.create_object().set_all(2, 2.0f, 2.0);
2✔
3338
    table.create_object().set_all(4, 4.0f, 4.0);
2✔
3339

3340
    CHECK_EQUAL(3, table.where().avg(col_int));
2✔
3341
    CHECK_EQUAL(3, table.where().avg(col_float));
2✔
3342
    CHECK_EQUAL(3, table.where().avg(col_double));
2✔
3343

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

3348
    CHECK_EQUAL(3, table.where().avg(col_int));
2✔
3349
    CHECK_EQUAL(3, table.where().avg(col_float));
2✔
3350
    CHECK_EQUAL(3, table.where().avg(col_double));
2✔
3351
}
2✔
3352

3353
TEST(Query_NegativeNumbers)
3354
{
2✔
3355
    for (size_t nullable = 0; nullable < 2; ++nullable) {
6✔
3356
        Group group;
4✔
3357
        TableRef table = group.add_table("test");
4✔
3358
        auto c0 = table->add_column(type_Int, "int", nullable == 0);
4✔
3359

3360
        int64_t id = -1;
4✔
3361
        for (size_t i = 0; i < 10; ++i) {
44✔
3362
            table->create_object().set_all(id--);
40✔
3363
        }
40✔
3364

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

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

3393
template <class T>
3394
int64_t unbox(const T& val)
3395
{
2,020✔
3396
    return val;
2,020✔
3397
}
2,020✔
3398

3399
template <>
3400
int64_t unbox(const util::Optional<int64_t>& val)
3401
{
2,020✔
3402
    return *val;
2,020✔
3403
}
2,020✔
3404

3405
TEST_TYPES(Query_EqualityInts, int64_t, util::Optional<int64_t>)
3406
{
4✔
3407
    Group group;
4✔
3408
    TableRef table = group.add_table("test");
4✔
3409
    auto col_ndx = table->add_column(type_Int, "int", std::is_same<TEST_TYPE, util::Optional<int64_t>>::value);
4✔
3410

3411
    int64_t id = -1;
4✔
3412
    int64_t sum = 0;
4✔
3413
    constexpr static size_t num_rows = REALM_MAX_BPNODE_SIZE + 10;
4✔
3414
    for (size_t i = 0; i < num_rows; ++i) {
4,044✔
3415
        sum += id;
4,040✔
3416
        table->create_object().set<Int>(col_ndx, id++);
4,040✔
3417
    }
4,040✔
3418

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

3428
        Query q_neq = table->where().not_equal(col_ndx, target);
4,040✔
3429
        CHECK_EQUAL(q_neq.find(), first ? ObjKey(1) : ObjKey(0));
4,040✔
3430
        CHECK_EQUAL(q_neq.count(), num_rows - 1);
4,040✔
3431
        CHECK_EQUAL(q_neq.sum(col_ndx), sum - target);
4,040✔
3432
        CHECK_EQUAL(q_neq.avg(col_ndx), (sum - target) / double(num_rows - 1));
4,040✔
3433
        first = false;
4,040✔
3434
    }
4,040✔
3435
}
4✔
3436

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

3449
        // Create three identical columns with values: For the nullable case:
3450
        //      3, 4, null
3451
        // For non-nullable iteration:
3452
        //      3, 4
3453

3454
        table1->create_object().set_all(3, 3, 3.0);
4✔
3455
        table1->create_object().set_all(4, 4, 4.0);
4✔
3456
        if (n)
4✔
3457
            table1->create_object();
2✔
3458

3459
        // Average
3460
        {
4✔
3461
            double d;
4✔
3462

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

3467
            d = table1->where().avg(c1)->get_double();
4✔
3468
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3469

3470
            // Criteria on same column as average
3471
            d = table1->where().not_equal(c0, 1234).avg(c0)->get_double();
4✔
3472
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3473

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

3478
            // Average of double, criteria on integer
3479
            d = table1->where().not_equal(c0, 1234).avg(c2)->get_double();
4✔
3480
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3481

3482
            d = table1->where().not_equal(c2, 1234.).avg(c2)->get_double();
4✔
3483
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3484

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

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

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

3494
            d = table1->where().avg(c1)->get_double();
4✔
3495
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3496

3497
            // Criteria on same column as average
3498
            d = table1->where().equal(c0, 3).avg(c0)->get_double();
4✔
3499
            CHECK_APPROXIMATELY_EQUAL(d, 3., 0.001);
4✔
3500

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

3505
            // Average of double, criteria on integer
3506
            d = table1->where().not_equal(c0, 3).avg(c2)->get_double();
4✔
3507
            CHECK_APPROXIMATELY_EQUAL(d, 4., 0.001);
4✔
3508

3509
            d = table1->where().equal(c2, 3.).avg(c2)->get_double();
4✔
3510
            CHECK_APPROXIMATELY_EQUAL(d, 3., 0.001);
4✔
3511

3512
            // Now using null as criteria
3513
            d = (table1->column<Int>(c0) != null()).avg(c2)->get_double();
4✔
3514
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3515

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

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

3522
            d = (table1->column<Int>(c1) != null()).avg(c0)->get_double();
4✔
3523
            CHECK_APPROXIMATELY_EQUAL(d, 7. / 2., 0.001);
4✔
3524
        }
4✔
3525

3526

3527
        // Maximum
3528
        {
4✔
3529
            std::optional<Mixed> m;
4✔
3530
            // Those that have criterias include all rows, also those with null
3531
            m = table1->where().max(c0);
4✔
3532
            CHECK_EQUAL(m, 4);
4✔
3533

3534
            m = table1->where().max(c1);
4✔
3535
            CHECK_EQUAL(m, 4);
4✔
3536

3537
            // Criteria on same column as maximum
3538
            m = table1->where().not_equal(c0, 1234).max(c0);
4✔
3539
            CHECK_EQUAL(m, 4);
4✔
3540

3541
            // Criteria on other column than maximum (triggers different code paths)
3542
            m = table1->where().not_equal(c0, 1234).max(c1);
4✔
3543
            CHECK_EQUAL(m, 4);
4✔
3544

3545
            // Average of double, criteria on integer
3546
            m = table1->where().not_equal(c0, 1234).max(c2);
4✔
3547
            CHECK_EQUAL(m, 4);
4✔
3548

3549
            m = table1->where().not_equal(c2, 1234.).max(c2);
4✔
3550
            CHECK_EQUAL(m, 4.);
4✔
3551

3552
            // Those with criteria now only include some rows, whereof none are null
3553
            m = table1->where().max(c0);
4✔
3554
            CHECK_EQUAL(m, 4);
4✔
3555

3556
            m = table1->where().max(c1);
4✔
3557
            CHECK_EQUAL(m, 4);
4✔
3558

3559
            // Criteria on same column as maximum
3560
            m = table1->where().equal(c0, 4).max(c0);
4✔
3561
            CHECK_EQUAL(m, 4);
4✔
3562

3563
            // Criteria on other column than maximum (triggers different code paths)
3564
            m = table1->where().equal(c0, 4).max(c1);
4✔
3565
            CHECK_EQUAL(m, 4);
4✔
3566

3567
            // Average of double, criteria on integer
3568
            m = table1->where().not_equal(c0, 3).max(c2);
4✔
3569
            CHECK_EQUAL(m, 4.);
4✔
3570

3571
            m = table1->where().equal(c2, 3.).max(c2);
4✔
3572
            CHECK_EQUAL(m, 3.);
4✔
3573

3574
            // Now using null as criteria
3575
            m = (table1->column<Int>(c0) != null()).max(c2);
4✔
3576
            CHECK_EQUAL(m, 4.);
4✔
3577

3578
            m = (table1->column<Double>(c2) != null()).max(c2);
4✔
3579
            CHECK_EQUAL(m, 4.);
4✔
3580

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

3584
            m = (table1->column<Int>(c1) != null()).max(c0);
4✔
3585
            CHECK_EQUAL(m, 4);
4✔
3586
        }
4✔
3587

3588

3589
        // Minimum
3590
        {
4✔
3591
            std::optional<Mixed> m;
4✔
3592
            // Those that have criterias include all rows, also those with null
3593
            m = table1->where().min(c0);
4✔
3594
            CHECK_EQUAL(m, 3);
4✔
3595

3596
            m = table1->where().min(c1);
4✔
3597
            CHECK_EQUAL(m, 3);
4✔
3598

3599
            // Criteria on same column as minimum
3600
            m = table1->where().not_equal(c0, 1234).min(c0);
4✔
3601
            CHECK_EQUAL(m, 3);
4✔
3602

3603
            // Criteria on other column than minimum (triggers different code paths)
3604
            m = table1->where().not_equal(c0, 1234).min(c1);
4✔
3605
            CHECK_EQUAL(m, 3);
4✔
3606

3607
            // Average of double, criteria on integer
3608
            m = table1->where().not_equal(c0, 1234).min(c2);
4✔
3609
            CHECK_EQUAL(m, 3);
4✔
3610

3611
            m = table1->where().not_equal(c2, 1234.).min(c2);
4✔
3612
            CHECK_EQUAL(m, 3.);
4✔
3613

3614

3615
            // Those with criteria now only include some rows, whereof none are null
3616
            m = table1->where().min(c0);
4✔
3617
            CHECK_EQUAL(m, 3);
4✔
3618

3619
            m = table1->where().min(c1);
4✔
3620
            CHECK_EQUAL(m, 3);
4✔
3621

3622
            // Criteria on same column as minimum
3623
            m = table1->where().equal(c0, 4).min(c0);
4✔
3624
            CHECK_EQUAL(m, 4);
4✔
3625

3626
            // Criteria on other column than minimum (triggers different code paths)
3627
            m = table1->where().equal(c0, 4).min(c1);
4✔
3628
            CHECK_EQUAL(m, 4);
4✔
3629

3630
            // Average of double, criteria on integer
3631
            m = table1->where().not_equal(c0, 3).min(c2);
4✔
3632
            CHECK_EQUAL(m, 4.);
4✔
3633

3634
            m = table1->where().equal(c2, 3.).min(c2);
4✔
3635
            CHECK_EQUAL(m, 3.);
4✔
3636

3637
            // Now using null as criteria
3638
            m = (table1->column<Int>(c0) != null()).min(c2);
4✔
3639
            CHECK_EQUAL(m, 3.);
4✔
3640

3641
            m = (table1->column<Double>(c2) != null()).min(c2);
4✔
3642
            CHECK_EQUAL(m, 3.);
4✔
3643

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

3647
            m = (table1->column<Int>(c1) != null()).min(c0);
4✔
3648
            CHECK_EQUAL(m, 3);
4✔
3649
        }
4✔
3650

3651
        // Sum
3652
        {
4✔
3653
            std::optional<Mixed> m;
4✔
3654
            // Those that have criterias include all rows, also those with null
3655
            m = table1->where().sum(c0);
4✔
3656
            CHECK_EQUAL(m, 7);
4✔
3657

3658
            // Criteria on same column as maximum
3659
            m = table1->where().not_equal(c0, 1234).sum(c0);
4✔
3660
            CHECK_EQUAL(m, 7);
4✔
3661

3662
            // Criteria on other column than maximum (triggers different code paths)
3663
            m = table1->where().not_equal(c0, 1234).sum(c1);
4✔
3664
            CHECK_EQUAL(m, 7);
4✔
3665

3666
            m = (table1->column<Int>(c0) == null()).sum(c0);
4✔
3667
            CHECK_EQUAL(m, 0);
4✔
3668

3669
            m = (table1->column<Int>(c0) != null()).sum(c0);
4✔
3670
            CHECK_EQUAL(m, 7);
4✔
3671

3672
            // Average of double, criteria on integer
3673
            m = table1->where().not_equal(c0, 1234).sum(c2);
4✔
3674
            CHECK_EQUAL(m, 7.);
4✔
3675

3676
            m = table1->where().not_equal(c2, 1234.).sum(c2);
4✔
3677
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 7., 0.001);
4✔
3678

3679

3680
            // Those with criteria now only include some rows, whereof none are null
3681
            m = table1->where().sum(c0);
4✔
3682
            CHECK_EQUAL(m, 7);
4✔
3683

3684
            m = table1->where().sum(c1);
4✔
3685
            CHECK_EQUAL(m, 7);
4✔
3686

3687
            // Criteria on same column as maximum
3688
            m = table1->where().equal(c0, 4).sum(c0);
4✔
3689
            CHECK_EQUAL(m, 4);
4✔
3690

3691
            // Criteria on other column than maximum (triggers different code paths)
3692
            m = table1->where().equal(c0, 4).sum(c1);
4✔
3693
            CHECK_EQUAL(m, 4);
4✔
3694

3695
            // Average of double, criteria on integer
3696
            m = table1->where().not_equal(c0, 3).sum(c2);
4✔
3697
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 4., 0.001);
4✔
3698

3699
            m = table1->where().equal(c2, 3.).sum(c2);
4✔
3700
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 3., 0.001);
4✔
3701

3702
            // Now using null as criteria
3703
            m = (table1->column<Int>(c0) != null()).sum(c2);
4✔
3704
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 7., 0.001);
4✔
3705

3706
            m = (table1->column<Double>(c2) != null()).sum(c2);
4✔
3707
            CHECK_APPROXIMATELY_EQUAL(m->get_double(), 7., 0.001);
4✔
3708

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

3712
            m = (table1->column<Int>(c1) != null()).sum(c0);
4✔
3713
            CHECK_EQUAL(m, 7);
4✔
3714
        }
4✔
3715

3716

3717
        // Count
3718
        {
4✔
3719
            int64_t d;
4✔
3720
            d = table1->where().count();
4✔
3721
            CHECK_EQUAL(d, n ? 3 : 2);
4✔
3722

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

3726
            d = table1->where().equal(c0, 4).count();
4✔
3727
            CHECK_EQUAL(d, 1);
4✔
3728

3729
            d = table1->where().not_equal(c0, 3).count();
4✔
3730
            CHECK_EQUAL(d, n ? 2 : 1);
4✔
3731

3732
            d = table1->where().equal(c2, 3.).count();
4✔
3733
            CHECK_EQUAL(d, 1);
4✔
3734

3735
            // Now using null as criteria
3736
            d = (table1->column<Int>(c0) != null()).count();
4✔
3737
            CHECK_EQUAL(d, 2);
4✔
3738

3739
            d = (table1->column<Double>(c2) != null()).count();
4✔
3740
            CHECK_EQUAL(d, 2);
4✔
3741

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

3745
            d = (table1->column<Int>(c0) != null()).count();
4✔
3746
            CHECK_EQUAL(d, 2);
4✔
3747

3748
            d = (table1->column<Int>(c1) != null()).count();
4✔
3749
            CHECK_EQUAL(d, 2);
4✔
3750
        }
4✔
3751
    }
4✔
3752
}
2✔
3753

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

3767
    // TableView that depends on LinkView soon to be deleted
3768
    TableView tv_sorted = links.get_sorted_view(col_int);
2✔
3769

3770
    // First test depends_on_deleted_object()
3771
    CHECK(!tv_sorted.depends_on_deleted_object());
2✔
3772
    TableView tv2 = table->where(&tv).find_all();
2✔
3773
    CHECK(!tv2.depends_on_deleted_object());
2✔
3774

3775
    // Delete LinkList so LinkView gets detached
3776
    table->remove_object(table->begin());
2✔
3777
    CHECK(!links.is_attached());
2✔
3778
    CHECK(tv_sorted.depends_on_deleted_object());
2✔
3779

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

3789
    tv_sorted.sync_if_needed();
2✔
3790
    // See if "TableView that depends on LinkView" returns sane "empty"-like values
3791
    tv_sorted.avg(col_int, &rows);
2✔
3792
    CHECK_EQUAL(rows, 0);
2✔
3793

3794
    // Now check a "Query that depends on (TableView that depends on LinkView)"
3795
    Query q2 = table->where(&tv_sorted);
2✔
3796
    CHECK_EQUAL(q2.count(), 0);
2✔
3797
    CHECK_EQUAL(q2.find(), null_key);
2✔
3798

3799
    CHECK(!links.is_attached());
2✔
3800
    tv.sync_if_needed();
2✔
3801

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

3806
    // Before executing any methods on a LinkList, you must still always check is_attached(). If you
3807
    // call links->add() on a deleted LinkViewRef (where is_attached() == false), it will assert
3808
    CHECK(!links.is_attached());
2✔
3809
}
2✔
3810

3811
TEST(Query_SubQueries)
3812
{
2✔
3813
    Group group;
2✔
3814

3815
    TableRef origin = group.add_table("origin");
2✔
3816
    TableRef target = group.add_table("target");
2✔
3817

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

3824

3825
    // add some rows
3826
    origin->create_object(ObjKey(0));
2✔
3827
    origin->create_object(ObjKey(1));
2✔
3828
    origin->create_object(ObjKey(2));
2✔
3829

3830
    target->create_object(ObjKey(0)).set_all(400, "hello");
2✔
3831
    target->create_object(ObjKey(1)).set_all(500, "world");
2✔
3832
    target->create_object(ObjKey(2)).set_all(600, "!");
2✔
3833
    target->create_object(ObjKey(3)).set_all(600, "world");
2✔
3834

3835
    // set some links
3836
    auto links0 = origin->get_object(ObjKey(0)).get_linklist(col_link_o);
2✔
3837
    links0.add(ObjKey(1));
2✔
3838

3839
    auto links1 = origin->get_object(ObjKey(1)).get_linklist(col_link_o);
2✔
3840
    links1.add(ObjKey(1));
2✔
3841
    links1.add(ObjKey(2));
2✔
3842

3843
    ObjKey match;
2✔
3844
    TableView tv;
2✔
3845
    Query q;
2✔
3846
    Query sub_query;
2✔
3847

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

3856
    // No linked rows match ("world, 600).
3857
    sub_query = target->column<String>(col_string_t) == "world" && target->column<Int>(col_int_t) == 600;
2✔
3858
    q = origin->column<Link>(col_link_o, sub_query).count() >= 1;
2✔
3859
    match = q.find();
2✔
3860
    CHECK_EQUAL(match, null_key);
2✔
3861

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

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

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

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

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

3905
    {
2✔
3906
        Query q1(ref, std::unique_ptr<TableView>(new TableView()));
2✔
3907
        Query q2;
2✔
3908
        q2 = std::move(q1);
2✔
3909
    }
2✔
3910
}
2✔
3911

3912
TEST(Query_Timestamp)
3913
{
2✔
3914
    ObjKey match;
2✔
3915
    size_t cnt;
2✔
3916
    Table table;
2✔
3917
    auto col_first = table.add_column(type_Timestamp, "first", true);
2✔
3918
    auto col_second = table.add_column(type_Timestamp, "second", true);
2✔
3919
    Columns<Timestamp> first = table.column<Timestamp>(col_first);
2✔
3920
    Columns<Timestamp> second = table.column<Timestamp>(col_second);
2✔
3921

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

3931

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

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

3937
    match = (first != Timestamp(111, 222)).find();
2✔
3938
    CHECK_EQUAL(match, keys[1]);
2✔
3939

3940
    match = (first > Timestamp(111, 222)).find();
2✔
3941
    CHECK_EQUAL(match, keys[1]);
2✔
3942

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

3946
    match = (first == Timestamp(0, 0)).find();
2✔
3947
    CHECK_EQUAL(match, keys[4]);
2✔
3948

3949
    match = (first < Timestamp(111, 333)).find();
2✔
3950
    CHECK_EQUAL(match, keys[0]);
2✔
3951

3952
    match = (first < Timestamp(0, 0)).find();
2✔
3953
    CHECK_EQUAL(match, keys[5]);
2✔
3954

3955
    // Note: .count(), not find()
3956
    cnt = (first < Timestamp(0, 0)).count();
2✔
3957
    CHECK_EQUAL(cnt, 1);
2✔
3958

3959
    cnt = (first != Timestamp{}).count();
2✔
3960
    CHECK_EQUAL(cnt, 5);
2✔
3961

3962
    cnt = (first != null{}).count();
2✔
3963
    CHECK_EQUAL(cnt, 5);
2✔
3964

3965
    cnt = (first != Timestamp(0, 0)).count();
2✔
3966
    CHECK_EQUAL(cnt, 5);
2✔
3967

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

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

3974
    cnt = (first >= null{}).count();
2✔
3975
    CHECK_EQUAL(cnt, 1);
2✔
3976

3977
    cnt = (first <= null{}).count();
2✔
3978
    CHECK_EQUAL(cnt, 1);
2✔
3979

3980
    cnt = (first != Timestamp(0, 0)).count();
2✔
3981
    CHECK_EQUAL(cnt, 5);
2✔
3982

3983
    match = (first < Timestamp(-100, 0)).find();
2✔
3984
    CHECK_EQUAL(match, keys[5]);
2✔
3985

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

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

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

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

3998
    // Left-hand-side being Timestamp() constant, right being column
3999
    match = (Timestamp(111, 222) == first).find();
2✔
4000
    CHECK_EQUAL(match, keys[0]);
2✔
4001

4002
    match = (Timestamp{} == first).find();
2✔
4003
    CHECK_EQUAL(match, keys[3]);
2✔
4004

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

4008
    match = (Timestamp(111, 333) < first).find();
2✔
4009
    CHECK_EQUAL(match, keys[2]);
2✔
4010

4011
    match = (Timestamp(111, 222) >= first).find();
2✔
4012
    CHECK_EQUAL(match, keys[0]);
2✔
4013

4014
    match = (Timestamp(111, 111) >= first).find();
2✔
4015
    CHECK_EQUAL(match, keys[4]);
2✔
4016

4017
    match = (Timestamp(333, 444) <= first).find();
2✔
4018
    CHECK_EQUAL(match, keys[2]);
2✔
4019

4020
    match = (Timestamp(111, 300) <= first).find();
2✔
4021
    CHECK_EQUAL(match, keys[1]);
2✔
4022

4023
    match = (Timestamp(111, 222) != first).find();
2✔
4024
    CHECK_EQUAL(match, keys[1]);
2✔
4025

4026
    // Compare column with self
4027
    match = (first == first).find();
2✔
4028
    CHECK_EQUAL(match, keys[0]);
2✔
4029

4030
    match = (first != first).find();
2✔
4031
    CHECK_EQUAL(match, null_key);
2✔
4032

4033
    match = (first > first).find();
2✔
4034
    CHECK_EQUAL(match, null_key);
2✔
4035

4036
    match = (first < first).find();
2✔
4037
    CHECK_EQUAL(match, null_key);
2✔
4038

4039
    match = (first >= first).find();
2✔
4040
    CHECK_EQUAL(match, keys[0]);
2✔
4041

4042
    match = (first <= first).find();
2✔
4043
    CHECK_EQUAL(match, keys[0]);
2✔
4044

4045
    // Two different columns
4046
    match = (first == second).find();
2✔
4047
    CHECK_EQUAL(match, keys[3]); // null == null
2✔
4048

4049
    match = (first > second).find();
2✔
4050
    CHECK_EQUAL(match, keys[2]); // Timestamp(333, 444) > Timestamp(111, 222)
2✔
4051

4052
    match = (first < second).find();
2✔
4053
    CHECK_EQUAL(match, null_key); // Note that (null < null) == false
2✔
4054
}
2✔
4055

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

4065
    // Timestamps : {0,0}, {0,1}, {0,2}, {0,3}, {1,0}, {}, {1,2}, {1,3}, {2,0}, {2,1}
4066

4067
    auto timestamps = table.column<Timestamp>(col_date);
2✔
4068

4069
    CHECK_EQUAL((timestamps > Timestamp(0, 3)).count(), 5);
2✔
4070
    CHECK_EQUAL((timestamps >= Timestamp(0, 3)).count(), 6);
2✔
4071
    CHECK_EQUAL((timestamps < Timestamp(1, 3)).count(), 6);
2✔
4072
    CHECK_EQUAL((timestamps <= Timestamp(1, 3)).count(), 7);
2✔
4073
    CHECK_EQUAL((timestamps == Timestamp(0, 2)).count(), 1);
2✔
4074
    CHECK_EQUAL((timestamps != Timestamp(0, 2)).count(), 9);
2✔
4075
    CHECK_EQUAL((timestamps == Timestamp()).count(), 1);
2✔
4076
    CHECK_EQUAL((timestamps != Timestamp()).count(), 9);
2✔
4077
}
2✔
4078

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

4086
    auto col0 = table.add_column(type_Timestamp, "first", false);
2✔
4087
    auto col1 = table.add_column(type_Timestamp, "second", true);
2✔
4088
    ObjKey k0 = table.create_object().get_key();
2✔
4089

4090
    Columns<Timestamp> first = table.column<Timestamp>(col0);
2✔
4091
    Columns<Timestamp> second = table.column<Timestamp>(col1);
2✔
4092

4093
    match = (first == Timestamp{}).find();
2✔
4094
    CHECK_EQUAL(match, null_key);
2✔
4095

4096
    match = (second == Timestamp{}).find();
2✔
4097
    CHECK_EQUAL(match, k0);
2✔
4098
}
2✔
4099

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

4109
        // Reset the source query, destroying the original TableView.
4110
        q1 = {};
2✔
4111

4112
        // Operations on the copied query that touch the restricting view should not crash.
4113
        CHECK_EQUAL(0, q2.count());
2✔
4114
    }
2✔
4115

4116
    {
2✔
4117
        Query q1(ref, std::unique_ptr<TableView>(new TableView()));
2✔
4118
        Query q2;
2✔
4119
        q2 = q1;
2✔
4120

4121
        // Reset the source query, destroying the original TableView.
4122
        q1 = {};
2✔
4123

4124
        // Operations on the copied query that touch the restricting view should not crash.
4125
        CHECK_EQUAL(0, q2.count());
2✔
4126
    }
2✔
4127
}
2✔
4128

4129
TEST(Query_SyncViewIfNeeded)
4130
{
2✔
4131
    Group group;
2✔
4132
    TableRef source = group.add_table("source");
2✔
4133
    TableRef target = group.add_table("target");
2✔
4134

4135
    auto col_links = source->add_column_list(*target, "link");
2✔
4136
    auto col_id = target->add_column(type_Int, "id");
2✔
4137

4138
    auto reset_table_contents = [&] {
8✔
4139
        source->clear();
8✔
4140
        target->clear();
8✔
4141

4142
        for (int64_t i = 0; i < 15; ++i) {
128✔
4143
            target->create_object(ObjKey(i)).set(col_id, i);
120✔
4144
        }
120✔
4145

4146
        LnkLst ll = source->create_object().get_linklist(col_links);
8✔
4147
        for (size_t i = 6; i < 15; ++i) {
80✔
4148
            ll.add(ObjKey(i));
72✔
4149
        }
72✔
4150
    };
8✔
4151

4152
    // Restricting TableView. Query::sync_view_if_needed() syncs the TableView if needed.
4153
    {
2✔
4154
        reset_table_contents();
2✔
4155
        TableView restricting_view = target->where().greater(col_id, 5).find_all();
2✔
4156
        Query q = target->where(&restricting_view).less(col_id, 10);
2✔
4157

4158
        // Bring the view out of sync with the table.
4159
        target->get_object(ObjKey(7)).set(col_id, -7);
2✔
4160
        target->get_object(ObjKey(8)).set(col_id, -8);
2✔
4161

4162
        // Verify that the query uses the view as-is.
4163
        CHECK_EQUAL(4, q.count());
2✔
4164
        CHECK_EQUAL(false, restricting_view.is_in_sync());
2✔
4165

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

4174
    // Restricting LinkView.
4175
    {
2✔
4176
        reset_table_contents();
2✔
4177
        LnkLst restricting_view = source->begin()->get_linklist(col_links);
2✔
4178
        Query q = target->where(restricting_view).less(col_id, 10);
2✔
4179
        CHECK_EQUAL(restricting_view.size(), 9);
2✔
4180
        CHECK_EQUAL(q.count(), 4);
2✔
4181

4182
        auto content = source->get_content_version();
2✔
4183
        // Modify the underlying table to remove rows from the LinkView.
4184
        target->remove_object(ObjKey(7));
2✔
4185
        target->remove_object(ObjKey(8));
2✔
4186
        CHECK_NOT_EQUAL(content, source->get_content_version());
2✔
4187

4188
        // A LnkLst is always in sync:
4189
        CHECK_EQUAL(true, restricting_view.is_in_sync());
2✔
4190
        CHECK_EQUAL(restricting_view.size(), 7);
2✔
4191
        // The query correctly takes into account the changes to the restricting view
4192
        CHECK_EQUAL(2, q.count());
2✔
4193

4194
        // And that syncing the query does nothing.
4195
        auto version = q.sync_view_if_needed();
2✔
4196
        CHECK_EQUAL(true, restricting_view.is_in_sync());
2✔
4197
        CHECK_EQUAL(version[0].first, target->get_key());
2✔
4198
        CHECK_EQUAL(version[0].second, target->get_content_version());
2✔
4199
        CHECK_EQUAL(2, q.count());
2✔
4200
    }
2✔
4201

4202
    // No restricting view. Query::sync_view_if_needed() does nothing.
4203
    {
2✔
4204
        reset_table_contents();
2✔
4205
        Query q = target->where().greater(col_id, 5).less(col_id, 10);
2✔
4206

4207
        target->get_object(ObjKey(7)).set(col_id, -7);
2✔
4208
        target->get_object(ObjKey(8)).set(col_id, -8);
2✔
4209

4210
        CHECK_EQUAL(2, q.count());
2✔
4211

4212
        auto version = q.sync_view_if_needed();
2✔
4213
        CHECK_EQUAL(version[0].first, target->get_key());
2✔
4214
        CHECK_EQUAL(version[0].second, target->get_content_version());
2✔
4215
        CHECK_EQUAL(2, q.count());
2✔
4216
    }
2✔
4217

4218
    // Query that is not associated with a Table. Query::sync_view_if_needed() does nothing.
4219
    {
2✔
4220
        reset_table_contents();
2✔
4221
        Query q;
2✔
4222

4223
        auto version = q.sync_view_if_needed();
2✔
4224
        CHECK(version.empty());
2✔
4225
    }
2✔
4226
}
2✔
4227

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

4234
    table.create_object().set(col_id, 0);
2✔
4235
    table.create_object().set(col_id, 1);
2✔
4236
    table.create_object().set(col_id, 2);
2✔
4237

4238
    {
2✔
4239
        Query q = table.where().equal(col_id, 1);
2✔
4240
        q.and_query(table.where());
2✔
4241
        CHECK_EQUAL(1, q.find_all().size());
2✔
4242
    }
2✔
4243

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

4251
    {
2✔
4252
        Query q1 = table.where().equal(col_id, 1);
2✔
4253
        Query q2 = table.where();
2✔
4254
        q2.and_query(q1);
2✔
4255
        CHECK_EQUAL(1, q2.count());
2✔
4256
    }
2✔
4257

4258
    {
2✔
4259
        Query q = table.where();
2✔
4260
        q.and_query(table.where().equal(col_id, 1));
2✔
4261
        CHECK_EQUAL(1, q.count());
2✔
4262
    }
2✔
4263

4264
    {
2✔
4265
        Query q1 = table.where().equal(col_id, 1);
2✔
4266
        Query q2 = q1 && table.where();
2✔
4267
        CHECK_EQUAL(1, q2.count());
2✔
4268

4269
        Query q3 = table.where() && q1;
2✔
4270
        CHECK_EQUAL(1, q3.count());
2✔
4271
    }
2✔
4272

4273
    {
2✔
4274
        Query q1 = table.where().equal(col_id, 1);
2✔
4275
        Query q2 = q1 || table.where();
2✔
4276
        CHECK_EQUAL(1, q2.count());
2✔
4277

4278
        Query q3 = table.where() || q1;
2✔
4279
        CHECK_EQUAL(1, q3.count());
2✔
4280
    }
2✔
4281
}
2✔
4282

4283
// Check that queries take into account restricting views, but still
4284
// return row index into the underlying table
4285
TEST(Query_AccountForRestrictingViews)
4286
{
2✔
4287
    Table table;
2✔
4288
    auto col_id = table.add_column(type_Int, "id");
2✔
4289

4290
    table.create_object().set(col_id, 42);
2✔
4291
    table.create_object().set(col_id, 43);
2✔
4292
    table.create_object().set(col_id, 44);
2✔
4293

4294
    {
2✔
4295
        // Create initial table view
4296
        TableView results = table.where().equal(col_id, 44).find_all();
2✔
4297
        CHECK_EQUAL(1, results.size());
2✔
4298
        CHECK_EQUAL(44, results[0].get<Int>(col_id));
2✔
4299

4300
        // Create query based on restricting view
4301
        Query q = Query(results.get_parent()->where(&results));
2✔
4302
        ObjKey obj_key = q.find();
2✔
4303
        CHECK_EQUAL(obj_key, results.get_key(0));
2✔
4304
    }
2✔
4305
}
2✔
4306

4307
/*
4308

4309
// These tests fail on Windows due to lack of tolerance for invalid UTF-8 in the case mapping methods
4310

4311
TEST(Query_UTF8_Contains)
4312
{
4313
    Group group;
4314
    TableRef table1 = group.add_table("table1");
4315
    table1->add_column(type_String, "str1");
4316
    table1->add_empty_row();
4317
    table1->set_string(0, 0, StringData("\x0ff\x000", 2));
4318
    size_t m = table1->column<String>(0).contains(StringData("\x0ff\x000", 2), false).count();
4319
    CHECK_EQUAL(1, m);
4320
}
4321

4322

4323
TEST(Query_UTF8_Contains_Fuzzy)
4324
{
4325
    Table table;
4326
    table.add_column(type_String, "str1");
4327
    table.add_empty_row();
4328

4329
    for (size_t t = 0; t < 10000; t++) {
4330
        char haystack[10];
4331
        char needle[7];
4332

4333
        for (size_t c = 0; c < 10; c++)
4334
            haystack[c] = char(fastrand());
4335

4336
        for (size_t c = 0; c < 7; c++)
4337
            needle[c] = char(fastrand());
4338

4339
        table.set_string(0, 0, StringData(haystack, 10));
4340

4341
        table.column<String>(0).contains(StringData(needle, fastrand(7)), false).count();
4342
        table.column<String>(0).contains(StringData(needle, fastrand(7)), true).count();
4343
    }
4344
}
4345
*/
4346

4347
TEST(Query_ArrayLeafRelocate)
4348
{
2✔
4349
    for (size_t iter = 0; iter < 10; iter++) {
22✔
4350
        // Tests crash where a query node would have a SequentialGetter that pointed to an old array leaf
4351
        // that was relocated. https://github.com/realm/realm-core/issues/2269
4352
        // The above description does not apply to the cluster based implementation.
4353
        Group group;
20✔
4354

4355
        TableRef contact = group.add_table("contact");
20✔
4356
        TableRef contact_type = group.add_table("contact_type");
20✔
4357

4358
        auto col_int = contact_type->add_column(type_Int, "id");
20✔
4359
        auto col_str = contact_type->add_column(type_String, "str");
20✔
4360
        auto col_link = contact->add_column_list(*contact_type, "link");
20✔
4361

4362
        std::vector<ObjKey> contact_type_keys;
20✔
4363
        std::vector<ObjKey> contact_keys;
20✔
4364
        contact_type->create_objects(10, contact_type_keys);
20✔
4365
        contact->create_objects(10, contact_keys);
20✔
4366

4367
        Query q1 = (contact->link(col_link).column<Int>(col_int) == 0);
20✔
4368
        Query q2 = contact_type->where().equal(col_int, 0);
20✔
4369
        Query q3 = contact_type->query("id + id == 0");
20✔
4370
        Query q4 = (contact_type->column<Int>(col_int) == 0);
20✔
4371
        Query q5 = (contact_type->column<String>(col_str) == "hejsa");
20✔
4372

4373
        TableView tv = q1.find_all();
20✔
4374
        TableView tv2 = q2.find_all();
20✔
4375
        TableView tv3 = q3.find_all();
20✔
4376
        TableView tv4 = q4.find_all();
20✔
4377
        TableView tv5 = q5.find_all();
20✔
4378

4379
        contact->add_column(type_Float, "extra");
20✔
4380
        contact_type->add_column(type_Float, "extra");
20✔
4381

4382
        for (size_t t = 0; t < REALM_MAX_BPNODE_SIZE + 1; t++) {
20,040✔
4383
            Obj contact_obj = contact->create_object();
20,020✔
4384
            Obj contact_type_obj = contact_type->create_object();
20,020✔
4385
            //  contact_type.get()->set_string(1, t, "hejsa");
4386

4387
            auto ll = contact_obj.get_linklist(col_link);
20,020✔
4388
            ll.add(contact_type_obj.get_key());
20,020✔
4389

4390
            if (t == 0 || t == REALM_MAX_BPNODE_SIZE) {
20,020✔
4391
                tv.sync_if_needed();
40✔
4392
                tv2.sync_if_needed();
40✔
4393
                tv3.sync_if_needed();
40✔
4394
                tv4.sync_if_needed();
40✔
4395
                tv5.sync_if_needed();
40✔
4396
            }
40✔
4397
        }
20,020✔
4398
    }
20✔
4399
}
2✔
4400

4401

4402
TEST(Query_ColumnDeletionSimple)
4403
{
2✔
4404
    Table foo;
2✔
4405
    auto col_int0 = foo.add_column(type_Int, "a");
2✔
4406
    auto col_int1 = foo.add_column(type_Int, "b");
2✔
4407

4408
    std::vector<ObjKey> keys;
2✔
4409
    foo.create_objects(10, keys);
2✔
4410

4411
    foo.get_object(keys[3]).set(col_int0, 123);
2✔
4412
    foo.get_object(keys[4]).set(col_int0, 123);
2✔
4413
    foo.get_object(keys[7]).set(col_int0, 123);
2✔
4414
    foo.get_object(keys[2]).set(col_int1, 456);
2✔
4415
    foo.get_object(keys[4]).set(col_int1, 456);
2✔
4416

4417
    auto q1 = foo.column<Int>(col_int0) == 123;
2✔
4418
    auto q2 = foo.column<Int>(col_int1) == 456;
2✔
4419
    auto q3 = q1 || q2;
2✔
4420
    TableView tv1 = q1.find_all();
2✔
4421
    TableView tv2 = q2.find_all();
2✔
4422
    TableView tv3 = q3.find_all();
2✔
4423
    CHECK_EQUAL(tv1.size(), 3);
2✔
4424
    CHECK_EQUAL(tv2.size(), 2);
2✔
4425
    CHECK_EQUAL(tv3.size(), 4);
2✔
4426

4427
    foo.remove_column(col_int0);
2✔
4428

4429
    size_t x = 0;
2✔
4430
    CHECK_LOGIC_ERROR(x = q1.count(), ErrorCodes::InvalidProperty);
2✔
4431
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4432
    CHECK_EQUAL(x, 0);
2✔
4433
    CHECK_EQUAL(tv1.size(), 0);
2✔
4434

4435
    // This one should succeed in spite the column index is 1 and we
4436
    x = q2.count();
2✔
4437
    tv2.sync_if_needed();
2✔
4438
    CHECK_EQUAL(x, 2);
2✔
4439
    CHECK_EQUAL(tv2.size(), 2);
2✔
4440

4441
    x = 0;
2✔
4442
    CHECK_LOGIC_ERROR(x = q3.count(), ErrorCodes::InvalidProperty);
2✔
4443
    CHECK_LOGIC_ERROR(tv3.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4444
    CHECK_EQUAL(x, 0);
2✔
4445
    CHECK_EQUAL(tv3.size(), 0);
2✔
4446
}
2✔
4447

4448

4449
TEST(Query_ColumnDeletionExpression)
4450
{
2✔
4451
    Table foo;
2✔
4452
    auto col_int0 = foo.add_column(type_Int, "a");
2✔
4453
    auto col_int1 = foo.add_column(type_Int, "b");
2✔
4454
    auto col_date2 = foo.add_column(type_Timestamp, "c");
2✔
4455
    auto col_date3 = foo.add_column(type_Timestamp, "d");
2✔
4456
    auto col_str4 = foo.add_column(type_String, "e");
2✔
4457
    auto col_float5 = foo.add_column(type_Float, "f");
2✔
4458
    auto col_bin6 = foo.add_column(type_Binary, "g");
2✔
4459

4460
    Obj obj0 = foo.create_object();
2✔
4461
    Obj obj1 = foo.create_object();
2✔
4462
    Obj obj2 = foo.create_object();
2✔
4463
    Obj obj3 = foo.create_object();
2✔
4464
    Obj obj4 = foo.create_object();
2✔
4465
    obj0.set(col_int0, 0);
2✔
4466
    obj1.set(col_int0, 1);
2✔
4467
    obj2.set(col_int0, 2);
2✔
4468
    obj3.set(col_int0, 3);
2✔
4469
    obj4.set(col_int0, 4);
2✔
4470
    obj0.set(col_int1, 0);
2✔
4471
    obj1.set(col_int1, 0);
2✔
4472
    obj2.set(col_int1, 3);
2✔
4473
    obj3.set(col_int1, 5);
2✔
4474
    obj4.set(col_int1, 3);
2✔
4475
    obj0.set(col_date2, Timestamp(100, 100));
2✔
4476
    obj0.set(col_date3, Timestamp(200, 100));
2✔
4477
    obj0.set(col_str4, StringData("Hello, world"));
2✔
4478
    obj0.set(col_float5, 3.141592f);
2✔
4479
    obj1.set(col_float5, 1.0f);
2✔
4480
    obj0.set(col_bin6, BinaryData("Binary", 6));
2✔
4481

4482
    // Expression
4483
    auto q = foo.query("a == b + 1");
2✔
4484
    // TwoColumnsNode
4485
    auto q1 = foo.column<Int>(col_int0) == foo.column<Int>(col_int1);
2✔
4486
    TableView tv = q.find_all();
2✔
4487
    TableView tv1 = q1.find_all();
2✔
4488
    CHECK_EQUAL(tv.size(), 2);
2✔
4489
    CHECK_EQUAL(tv1.size(), 1);
2✔
4490

4491
    foo.remove_column(col_int0);
2✔
4492
    size_t x = 0;
2✔
4493
    CHECK_LOGIC_ERROR(x = q.count(), ErrorCodes::InvalidProperty);
2✔
4494
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4495
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4496
    CHECK_EQUAL(x, 0);
2✔
4497
    CHECK_EQUAL(tv.size(), 0);
2✔
4498

4499
    q = foo.column<Timestamp>(col_date2) < foo.column<Timestamp>(col_date3);
2✔
4500
    // TimestampNode
4501
    q1 = foo.column<Timestamp>(col_date3) == Timestamp(200, 100);
2✔
4502
    tv = q.find_all();
2✔
4503
    tv1 = q1.find_all();
2✔
4504
    CHECK_EQUAL(tv.size(), 1);
2✔
4505
    CHECK_EQUAL(tv1.size(), 1);
2✔
4506
    foo.remove_column(col_date3);
2✔
4507
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4508
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4509

4510
    // StringNodeBase
4511
    q = foo.column<String>(col_str4) == StringData("Hello, world");
2✔
4512
    q1 = !(foo.column<String>(col_str4) == StringData("Hello, world"));
2✔
4513
    tv = q.find_all();
2✔
4514
    tv1 = q1.find_all();
2✔
4515
    CHECK_EQUAL(tv.size(), 1);
2✔
4516
    CHECK_EQUAL(tv1.size(), 4);
2✔
4517
    foo.remove_column(col_str4);
2✔
4518
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4519
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4520

4521
    // FloatDoubleNode
4522
    q = foo.column<Float>(col_float5) > 0.0f;
2✔
4523
    tv = q.find_all();
2✔
4524
    CHECK_EQUAL(tv.size(), 2);
2✔
4525
    foo.remove_column(col_float5);
2✔
4526
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4527

4528
    // BinaryNode
4529
    q = foo.column<Binary>(col_bin6) != BinaryData("Binary", 6);
2✔
4530
    tv = q.find_all();
2✔
4531
    CHECK_EQUAL(tv.size(), 4);
2✔
4532
    foo.remove_column(col_bin6);
2✔
4533
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4534
}
2✔
4535

4536

4537
TEST(Query_ColumnDeletionLinks)
4538
{
2✔
4539
    Group g;
2✔
4540
    TableRef foo = g.add_table("foo");
2✔
4541
    TableRef bar = g.add_table("bar");
2✔
4542
    TableRef foobar = g.add_table("foobar");
2✔
4543

4544
    auto col_int0 = foobar->add_column(type_Int, "int");
2✔
4545

4546
    auto col_int1 = bar->add_column(type_Int, "int");
2✔
4547
    auto col_link0 = bar->add_column(*foobar, "link");
2✔
4548

4549
    auto col_link1 = foo->add_column(*bar, "link");
2✔
4550

4551
    std::vector<ObjKey> foobar_keys;
2✔
4552
    std::vector<ObjKey> bar_keys;
2✔
4553
    std::vector<ObjKey> foo_keys;
2✔
4554
    foobar->create_objects(5, foobar_keys);
2✔
4555
    bar->create_objects(5, bar_keys);
2✔
4556
    foo->create_objects(10, foo_keys);
2✔
4557

4558
    for (int i = 0; i < 5; i++) {
12✔
4559
        foobar->get_object(foobar_keys[i]).set(col_int0, i);
10✔
4560
        bar->get_object(bar_keys[i]).set(col_int1, i);
10✔
4561
        bar->get_object(bar_keys[i]).set(col_link0, foobar_keys[i]);
10✔
4562
        foo->get_object(foo_keys[i]).set(col_link1, bar_keys[i]);
10✔
4563
    }
10✔
4564
    auto q = foo->link(col_link1).link(col_link0).column<Int>(col_int0) == 2;
2✔
4565
    auto q1 = foo->column<Link>(col_link1).is_null();
2✔
4566
    auto q2 = foo->column<Link>(col_link1) == bar->get_object(bar_keys[2]);
2✔
4567

4568
    auto tv = q.find_all();
2✔
4569
    auto cnt = q1.count();
2✔
4570
    CHECK_EQUAL(tv.size(), 1);
2✔
4571
    CHECK_EQUAL(cnt, 5);
2✔
4572
    cnt = q2.count();
2✔
4573
    CHECK_EQUAL(cnt, 1);
2✔
4574

4575
    // remove integer column, should not affect query
4576
    bar->remove_column(col_int1);
2✔
4577
    tv.sync_if_needed();
2✔
4578
    CHECK_EQUAL(tv.size(), 1);
2✔
4579
    // remove link column, disaster
4580
    bar->remove_column(col_link0);
2✔
4581
    CHECK_LOGIC_ERROR(bar->check_column(col_link0), ErrorCodes::InvalidProperty);
2✔
4582
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4583
    foo->remove_column(col_link1);
2✔
4584
    CHECK_LOGIC_ERROR(foo->check_column(col_link1), ErrorCodes::InvalidProperty);
2✔
4585
    CHECK_LOGIC_ERROR(q1.count(), ErrorCodes::InvalidProperty);
2✔
4586
    CHECK_LOGIC_ERROR(q2.count(), ErrorCodes::InvalidProperty);
2✔
4587
}
2✔
4588

4589

4590
TEST(Query_CaseInsensitiveIndexEquality_CommonNumericPrefix)
4591
{
2✔
4592
    Table table;
2✔
4593
    auto col_ndx = table.add_column(type_String, "id");
2✔
4594
    table.add_search_index(col_ndx);
2✔
4595

4596
    ObjKey key0 = table.create_object().set(col_ndx, "111111111111111111111111").get_key();
2✔
4597
    table.create_object().set(col_ndx, "111111111111111111111112");
2✔
4598

4599
    Query q = table.where().equal(col_ndx, "111111111111111111111111", false);
2✔
4600
    CHECK_EQUAL(q.count(), 1);
2✔
4601
    TableView tv = q.find_all();
2✔
4602
    CHECK_EQUAL(tv.size(), 1);
2✔
4603
    CHECK_EQUAL(tv[0].get_key(), key0);
2✔
4604
}
2✔
4605

4606

4607
TEST_TYPES(Query_CaseInsensitiveNullable, std::true_type, std::false_type)
4608
{
4✔
4609
    Table table;
4✔
4610
    bool nullable = true;
4✔
4611
    constexpr bool with_index = TEST_TYPE::value;
4✔
4612
    auto col_ndx = table.add_column(type_String, "id", nullable);
4✔
4613
    if (with_index) {
4✔
4614
        table.add_search_index(col_ndx);
2✔
4615
    }
2✔
4616

4617
    table.create_object().set(col_ndx, "test");
4✔
4618
    table.create_object().set(col_ndx, "words");
4✔
4619
    ObjKey key2 = table.create_object().get_key();
4✔
4620
    ObjKey key3 = table.create_object().get_key();
4✔
4621
    table.create_object().set(col_ndx, "");
4✔
4622
    table.create_object().set(col_ndx, "");
4✔
4623

4624
    bool case_sensitive = true;
4✔
4625
    StringData null_string;
4✔
4626
    Query q = table.where().equal(col_ndx, null_string, case_sensitive);
4✔
4627
    CHECK_EQUAL(q.count(), 2);
4✔
4628
    TableView tv = q.find_all();
4✔
4629
    CHECK_EQUAL(tv.size(), 2);
4✔
4630
    CHECK_EQUAL(tv.get_key(0), key2);
4✔
4631
    CHECK_EQUAL(tv.get_key(1), key3);
4✔
4632
    Query q2 = table.where().contains(col_ndx, null_string, case_sensitive);
4✔
4633
    CHECK_EQUAL(q2.count(), 6);
4✔
4634
    tv = q2.find_all();
4✔
4635
    CHECK_EQUAL(tv.size(), 6);
4✔
4636

4637
    case_sensitive = false;
4✔
4638
    q = table.where().equal(col_ndx, null_string, case_sensitive);
4✔
4639
    CHECK_EQUAL(q.count(), 2);
4✔
4640
    tv = q.find_all();
4✔
4641
    CHECK_EQUAL(tv.size(), 2);
4✔
4642
    CHECK_EQUAL(tv.get_key(0), key2);
4✔
4643
    CHECK_EQUAL(tv.get_key(1), key3);
4✔
4644
    q2 = table.where().contains(col_ndx, null_string, case_sensitive);
4✔
4645
    CHECK_EQUAL(q2.count(), 6);
4✔
4646
    tv = q2.find_all();
4✔
4647
    CHECK_EQUAL(tv.size(), 6);
4✔
4648
}
4✔
4649

4650

4651
TEST_TYPES(Query_Rover, std::true_type, std::false_type)
4652
{
4✔
4653
    constexpr bool nullable = TEST_TYPE::value;
4✔
4654

4655
    Table table;
4✔
4656
    auto col = table.add_column(type_String, "name", nullable);
4✔
4657
    table.add_search_index(col);
4✔
4658

4659
    table.create_object().set(col, "ROVER");
4✔
4660
    table.create_object().set(col, "Rover");
4✔
4661

4662
    Query q = table.where().equal(col, "rover", false);
4✔
4663
    CHECK_EQUAL(q.count(), 2);
4✔
4664
    TableView tv = q.find_all();
4✔
4665
    CHECK_EQUAL(tv.size(), 2);
4✔
4666
}
4✔
4667

4668
TEST(Query_StringPrimaryKey)
4669
{
2✔
4670
    Table table;
2✔
4671
    auto col = table.add_column(type_String, "name");
2✔
4672
    table.set_primary_key_column(col);
2✔
4673

4674
    table.create_object_with_primary_key("RASMUS");
2✔
4675
    table.create_object_with_primary_key("Rasmus");
2✔
4676

4677
    Query q = table.where().equal(col, "rasmus", false);
2✔
4678
    CHECK_EQUAL(q.count(), 2);
2✔
4679
    TableView tv = q.find_all();
2✔
4680
    CHECK_EQUAL(tv.size(), 2);
2✔
4681
}
2✔
4682

4683
TEST(Query_IntOnly)
4684
{
2✔
4685
    Table table;
2✔
4686
    auto c0 = table.add_column(type_Int, "i1");
2✔
4687
    auto c1 = table.add_column(type_Int, "i2");
2✔
4688

4689
    table.create_object(ObjKey(7)).set_all(7, 6);
2✔
4690
    table.create_object(ObjKey(19)).set_all(19, 9);
2✔
4691
    table.create_object(ObjKey(5)).set_all(19, 22);
2✔
4692
    table.create_object(ObjKey(21)).set_all(2, 6);
2✔
4693

4694
    auto q = table.column<Int>(c1) == 6;
2✔
4695
    ObjKey key = q.find();
2✔
4696
    CHECK_EQUAL(key, ObjKey(7));
2✔
4697

4698
    TableView tv = q.find_all();
2✔
4699
    CHECK_EQUAL(tv.size(), 2);
2✔
4700
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(7));
2✔
4701
    CHECK_EQUAL(tv.get_object(1).get_key(), ObjKey(21));
2✔
4702

4703
    auto q1 = table.where(&tv).equal(c0, 2);
2✔
4704
    TableView tv1 = q1.find_all();
2✔
4705
    CHECK_EQUAL(tv1.size(), 1);
2✔
4706
    CHECK_EQUAL(tv1.get_object(0).get_key(), ObjKey(21));
2✔
4707

4708
    q1 = table.where(&tv).greater(c0, 5);
2✔
4709
    tv1 = q1.find_all();
2✔
4710
    CHECK_EQUAL(tv1.size(), 1);
2✔
4711
    CHECK_EQUAL(tv1.get_object(0).get_key(), ObjKey(7));
2✔
4712

4713
    q = table.column<Int>(c0) == 19 && table.column<Int>(c1) == 9;
2✔
4714
    key = q.find();
2✔
4715
    CHECK_EQUAL(key.value, 19);
2✔
4716

4717
    tv = q.find_all();
2✔
4718
    CHECK_EQUAL(tv.size(), 1);
2✔
4719
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(19));
2✔
4720

4721
    // Two column expression
4722
    q = table.column<Int>(c0) < table.column<Int>(c1);
2✔
4723
    tv = q.find_all();
2✔
4724
    CHECK_EQUAL(tv.size(), 2);
2✔
4725
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(5));
2✔
4726
    CHECK_EQUAL(tv.get_object(1).get_key(), ObjKey(21));
2✔
4727
}
2✔
4728

4729
TEST(Query_LinksTo)
4730
{
2✔
4731
    Query q;
2✔
4732
    ObjKey found_key;
2✔
4733
    Group group;
2✔
4734

4735
    TableRef source = group.add_table("source");
2✔
4736
    TableRef target = group.add_table("target");
2✔
4737

4738
    auto col_link = source->add_column(*target, "link");
2✔
4739
    auto col_linklist = source->add_column_list(*target, "linklist");
2✔
4740

4741
    std::vector<ObjKey> target_keys;
2✔
4742
    target->create_objects(10, target_keys);
2✔
4743

4744
    std::vector<ObjKey> source_keys;
2✔
4745
    source->create_objects(10, source_keys);
2✔
4746

4747
    source->get_object(source_keys[2]).set(col_link, target_keys[2]);
2✔
4748
    source->get_object(source_keys[5]).set(col_link, target_keys[5]);
2✔
4749
    source->get_object(source_keys[8]).set(col_link, target_keys[5]);
2✔
4750
    source->get_object(source_keys[9]).set(col_link, target_keys[9]);
2✔
4751

4752
    q = source->column<Link>(col_link) == target->get_object(target_keys[2]);
2✔
4753
    found_key = q.find();
2✔
4754
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4755

4756
    q = source->where().equal(col_link, Mixed(target_keys[2]));
2✔
4757
    found_key = q.find();
2✔
4758
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4759

4760
    q = source->column<Link>(col_link) == target->get_object(target_keys[5]);
2✔
4761
    found_key = q.find();
2✔
4762
    CHECK_EQUAL(found_key, source_keys[5]);
2✔
4763
    q = source->where().equal(col_link, Mixed(target_keys[5]));
2✔
4764
    auto tv = q.find_all();
2✔
4765
    CHECK_EQUAL(tv.size(), 2);
2✔
4766
    q = source->where().not_equal(col_link, Mixed(target_keys[5]));
2✔
4767
    tv = q.find_all();
2✔
4768
    CHECK_EQUAL(tv.size(), 8);
2✔
4769
    q = source->where().equal(col_link, Mixed(ObjLink(source->get_key(), target_keys[5]))); // Wrong table
2✔
4770
    tv = q.find_all();
2✔
4771
    CHECK_EQUAL(tv.size(), 0);
2✔
4772

4773
    q = source->column<Link>(col_link) == target->get_object(target_keys[9]);
2✔
4774
    found_key = q.find();
2✔
4775
    CHECK_EQUAL(found_key, source_keys[9]);
2✔
4776

4777
    q = source->column<Link>(col_link) == target->get_object(target_keys[0]);
2✔
4778
    found_key = q.find();
2✔
4779
    CHECK_EQUAL(found_key, null_key);
2✔
4780

4781
    q = source->column<Link>(col_link).is_null();
2✔
4782
    tv = q.find_all();
2✔
4783
    CHECK_EQUAL(tv.size(), 6);
2✔
4784
    q = source->where().equal(col_link, Mixed()); // Null
2✔
4785
    tv = q.find_all();
2✔
4786
    CHECK_EQUAL(tv.size(), 6);
2✔
4787

4788
    q = source->column<Link>(col_link) != null();
2✔
4789
    found_key = q.find();
2✔
4790
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4791
    q = source->where().not_equal(col_link, Mixed()); // Null
2✔
4792
    tv = q.find_all();
2✔
4793
    CHECK_EQUAL(tv.size(), 4);
2✔
4794

4795
    auto linklist = source->get_object(source_keys[1]).get_linklist_ptr(col_linklist);
2✔
4796
    linklist->add(target_keys[6]);
2✔
4797
    linklist = source->get_object(source_keys[2]).get_linklist_ptr(col_linklist);
2✔
4798
    linklist->add(target_keys[0]);
2✔
4799
    linklist->add(target_keys[1]);
2✔
4800
    linklist->add(target_keys[2]);
2✔
4801
    linklist = source->get_object(source_keys[8]).get_linklist_ptr(col_linklist);
2✔
4802
    linklist->add(target_keys[0]);
2✔
4803
    linklist->add(target_keys[5]);
2✔
4804
    linklist->add(target_keys[7]);
2✔
4805

4806
    q = source->column<Link>(col_linklist) == target->get_object(target_keys[5]);
2✔
4807
    found_key = q.find();
2✔
4808
    CHECK_EQUAL(found_key, source_keys[8]);
2✔
4809

4810
    q = source->column<Link>(col_linklist) != target->get_object(target_keys[6]);
2✔
4811
    found_key = q.find();
2✔
4812
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4813

4814
    q = source->where().equal(col_linklist, Mixed(target_keys[0]));
2✔
4815
    tv = q.find_all();
2✔
4816
    CHECK_EQUAL(tv.size(), 2);
2✔
4817
    q = source->where().not_equal(col_linklist, Mixed(target_keys[6]));
2✔
4818
    tv = q.find_all();
2✔
4819
    CHECK_EQUAL(tv.size(), 2);
2✔
4820

4821
    q = source->where().equal(col_linklist, Mixed());
2✔
4822
    tv = q.find_all();
2✔
4823
    CHECK_EQUAL(tv.size(), 0); // LinkList never matches null
2✔
4824
    q = source->where().not_equal(col_linklist, Mixed());
2✔
4825
    tv = q.find_all();
2✔
4826
    CHECK_EQUAL(tv.size(), 3);
2✔
4827
}
2✔
4828

4829
TEST(Query_Group_bug)
4830
{
2✔
4831
    // Tests for a bug in queries with OR nodes at different nesting levels
4832

4833
    Group g;
2✔
4834
    TableRef service_table = g.add_table("service");
2✔
4835
    TableRef profile_table = g.add_table("profile");
2✔
4836
    TableRef person_table = g.add_table("person");
2✔
4837

4838
    auto col_service_id = service_table->add_column(type_String, "id");
2✔
4839
    auto col_service_link = service_table->add_column_list(*profile_table, "profiles");
2✔
4840

4841
    auto col_profile_id = profile_table->add_column(type_String, "role");
2✔
4842
    auto col_profile_link = profile_table->add_column(*service_table, "services");
2✔
4843

4844
    auto col_person_id = person_table->add_column(type_String, "id");
2✔
4845
    auto col_person_link = person_table->add_column_list(*service_table, "services");
2✔
4846

4847
    auto sk0 = service_table->create_object().set(col_service_id, "service_1").get_key();
2✔
4848
    auto sk1 = service_table->create_object().set(col_service_id, "service_2").get_key();
2✔
4849

4850
    auto pk0 = profile_table->create_object().set(col_profile_id, "profile_1").get_key();
2✔
4851
    auto pk1 = profile_table->create_object().set(col_profile_id, "profile_2").get_key();
2✔
4852
    auto pk2 = profile_table->create_object().set(col_profile_id, "profile_3").get_key();
2✔
4853
    auto pk3 = profile_table->create_object().set(col_profile_id, "profile_4").get_key();
2✔
4854
    auto pk4 = profile_table->create_object().set(col_profile_id, "profile_5").get_key();
2✔
4855

4856
    {
2✔
4857
        auto ll0 = service_table->get_object(sk0).get_linklist(col_service_link);
2✔
4858
        auto ll1 = service_table->get_object(sk1).get_linklist(col_service_link);
2✔
4859
        ll0.add(pk0);
2✔
4860
        ll0.add(pk1);
2✔
4861
        ll1.add(pk2);
2✔
4862
        ll0.add(pk3);
2✔
4863
        ll0.add(pk4);
2✔
4864
    }
2✔
4865

4866
    profile_table->get_object(pk0).set(col_profile_link, sk0);
2✔
4867
    profile_table->get_object(pk1).set(col_profile_link, sk0);
2✔
4868
    profile_table->get_object(pk2).set(col_profile_link, sk1);
2✔
4869
    profile_table->get_object(pk3).set(col_profile_link, sk0);
2✔
4870
    profile_table->get_object(pk4).set(col_profile_link, sk0);
2✔
4871

4872
    person_table->create_object().set(col_person_id, "person_1").get_linklist(col_person_link).add(sk0);
2✔
4873
    person_table->create_object().set(col_person_id, "person_2").get_linklist(col_person_link).add(sk0);
2✔
4874
    person_table->create_object().set(col_person_id, "person_3").get_linklist(col_person_link).add(sk1);
2✔
4875
    person_table->create_object().set(col_person_id, "person_4").get_linklist(col_person_link).add(sk0);
2✔
4876
    person_table->create_object().set(col_person_id, "person_5").get_linklist(col_person_link).add(sk0);
2✔
4877

4878
    realm::Query q0 =
2✔
4879
        person_table->where()
2✔
4880
            .group()
2✔
4881

4882
            .group()
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_1"))
2✔
4887
            .Or()
2✔
4888
            .and_query(person_table->link(col_person_link)
2✔
4889
                           .link(col_service_link)
2✔
4890
                           .column<String>(col_profile_id)
2✔
4891
                           .equal("profile_2"))
2✔
4892
            .end_group()
2✔
4893

4894
            .group()
2✔
4895
            .and_query(person_table->link(col_person_link).column<String>(col_service_id).equal("service_1"))
2✔
4896
            .end_group()
2✔
4897

4898
            .end_group()
2✔
4899

4900
            .Or()
2✔
4901

4902
            .group()
2✔
4903
            .equal(col_person_id, "person_3")
2✔
4904
            .end_group();
2✔
4905

4906
    CHECK_EQUAL(5, q0.count());
2✔
4907
}
2✔
4908

4909
TEST(Query_TwoColumnUnaligned)
4910
{
2✔
4911
    Group g;
2✔
4912
    TableRef table = g.add_table("table");
2✔
4913
    ColKey a_col_ndx = table->add_column(type_Int, "a");
2✔
4914
    ColKey b_col_ndx = table->add_column(type_Int, "b");
2✔
4915

4916
    // Adding 1001 rows causes arrays in the 2 columns to be aligned differently
4917
    // (on a 0 and on an 8 address resp)
4918
    auto matches = 0;
2✔
4919
    for (int i = 0; i < 1001; ++i) {
2,004✔
4920
        Obj obj = table->create_object();
2,002✔
4921
        obj.set(a_col_ndx, i);
2,002✔
4922
        if (i % 88) {
2,002✔
4923
            obj.set(b_col_ndx, i + 5);
1,978✔
4924
        }
1,978✔
4925
        else {
24✔
4926
            obj.set(b_col_ndx, i);
24✔
4927
            matches++;
24✔
4928
        }
24✔
4929
    }
2,002✔
4930

4931
    Query q = table->column<Int>(a_col_ndx) == table->column<Int>(b_col_ndx);
2✔
4932
    size_t cnt = q.count();
2✔
4933
    CHECK_EQUAL(cnt, matches);
2✔
4934
}
2✔
4935

4936

4937
TEST(Query_IntOrQueryOptimisation)
4938
{
2✔
4939
    Group g;
2✔
4940
    TableRef table = g.add_table("table");
2✔
4941
    auto col_optype = table->add_column(type_String, "optype");
2✔
4942
    auto col_active = table->add_column(type_Bool, "active");
2✔
4943
    auto col_id = table->add_column(type_Int, "id");
2✔
4944

4945
    for (int i = 0; i < 100; i++) {
202✔
4946
        auto obj = table->create_object();
200✔
4947
        obj.set<bool>(col_active, (i % 10) != 0);
200✔
4948
        obj.set<int>(col_id, i);
200✔
4949
        if (i == 0)
200✔
4950
            obj.set(col_optype, "CREATE");
2✔
4951
        if (i == 1)
200✔
4952
            obj.set(col_optype, "DELETE");
2✔
4953
        if (i == 2)
200✔
4954
            obj.set(col_optype, "CREATE");
2✔
4955
    }
200✔
4956
    auto optype = table->column<String>(col_optype);
2✔
4957
    auto active = table->column<Bool>(col_active);
2✔
4958
    auto id = table->column<Int>(col_id);
2✔
4959

4960
    Query q;
2✔
4961
    q = (id == 0 && optype == "CREATE") || id == 1;
2✔
4962
    CHECK_EQUAL(q.count(), 2);
2✔
4963

4964
    q = id == 1 || (id == 0 && optype == "DELETE");
2✔
4965
    CHECK_EQUAL(q.count(), 1);
2✔
4966

4967
    q = table->where().equal(col_id, 1).Or().equal(col_id, 0).Or().equal(col_id, 2);
2✔
4968
    CHECK_EQUAL(q.count(), 3);
2✔
4969
}
2✔
4970

4971
TEST_IF(Query_IntOrQueryPerformance, TEST_DURATION > 0)
4972
{
×
4973
    using std::chrono::duration_cast;
×
4974
    using std::chrono::microseconds;
×
4975

4976
    Group g;
×
4977
    TableRef table = g.add_table("table");
×
4978
    auto ints_col_key = table->add_column(type_Int, "ints");
×
4979
    auto nullable_ints_col_key = table->add_column(type_Int, "nullable_ints", true);
×
4980

4981
    const int null_frequency = 1000;
×
4982
    int num_nulls_added = 0;
×
4983
    int limit = 100000;
×
4984
    for (int i = 0; i < limit; ++i) {
×
4985
        if (i % null_frequency == 0) {
×
4986
            auto o = table->create_object().set_all(i);
×
4987
            o.set_null(nullable_ints_col_key);
×
4988
            ++num_nulls_added;
×
4989
        }
×
4990
        else {
×
4991
            table->create_object().set_all(i, i);
×
4992
        }
×
4993
    }
×
4994

4995
    auto run_queries = [&](int num_matches) {
×
4996
        // std::cout << "num_matches: " << num_matches << std::endl;
4997
        Query q_ints = table->column<Int>(ints_col_key) == -1;
×
4998
        Query q_nullables =
×
4999
            (table->column<Int>(nullable_ints_col_key) == -1).Or().equal(nullable_ints_col_key, realm::null());
×
5000
        for (int i = 0; i < num_matches; ++i) {
×
5001
            q_ints = q_ints.Or().equal(ints_col_key, i);
×
5002
            q_nullables = q_nullables.Or().equal(nullable_ints_col_key, i);
×
5003
        }
×
5004

5005
        auto before = std::chrono::steady_clock().now();
×
5006
        size_t ints_count = q_ints.count();
×
5007
        auto after = std::chrono::steady_clock().now();
×
5008
        // std::cout << "ints count: " << duration_cast<microseconds>(after - before).count() << " us" << std::endl;
5009

5010
        before = std::chrono::steady_clock().now();
×
5011
        size_t nullable_ints_count = q_nullables.count();
×
5012
        after = std::chrono::steady_clock().now();
×
5013
        // std::cout << "nullable ints count: " << duration_cast<microseconds>(after - before).count() << " us"
5014
        //           << std::endl;
5015

5016
        size_t expected_nullable_query_count =
×
5017
            num_matches + num_nulls_added - (((num_matches - 1) / null_frequency) + 1);
×
5018
        CHECK_EQUAL(ints_count, num_matches);
×
5019
        CHECK_EQUAL(nullable_ints_count, expected_nullable_query_count);
×
5020
    };
×
5021

5022
    run_queries(2);
×
5023
    run_queries(2048);
×
5024

5025
    table->add_search_index(ints_col_key);
×
5026
    table->add_search_index(nullable_ints_col_key);
×
5027

5028
    run_queries(2);
×
5029
    run_queries(2048);
×
5030
}
×
5031

5032
TEST(Query_IntIndexed)
5033
{
2✔
5034
    Group g;
2✔
5035
    TableRef table = g.add_table("table");
2✔
5036
    auto col_id = table->add_column(type_Int, "id");
2✔
5037

5038
    for (int i = 0; i < 100; i++) {
202✔
5039
        table->create_object().set_all(i % 10);
200✔
5040
    }
200✔
5041

5042
    table->add_search_index(col_id);
2✔
5043
    Query q = table->where().equal(col_id, 1);
2✔
5044
    CHECK_EQUAL(q.count(), 10);
2✔
5045
    auto tv = q.find_all();
2✔
5046
    CHECK_EQUAL(tv.size(), 10);
2✔
5047
}
2✔
5048

5049
TEST(Query_IntIndexedRandom)
5050
{
2✔
5051
    Random random(random_int<int>());
2✔
5052

5053
    Group g;
2✔
5054
    TableRef table = g.add_table("table");
2✔
5055
    auto col_id = table->add_column(type_Int, "id");
2✔
5056
    auto col_val = table->add_column(type_Int, "val");
2✔
5057

5058
    for (int i = 0; i < 100000; i++) {
200,002✔
5059
        table->create_object().set(col_id, random.draw_int_max(20)).set(col_val, random.draw_int_max(100));
200,000✔
5060
    }
200,000✔
5061

5062
    for (const char* str : {"id == 1", "id == 1 and val > 50"}) {
4✔
5063
        table->remove_search_index(col_id);
4✔
5064
        Query q = table->query(str);
4✔
5065
        auto before = std::chrono::steady_clock().now();
4✔
5066
        size_t c1 = q.count();
4✔
5067
        auto after = std::chrono::steady_clock().now();
4✔
5068
        auto count_without_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5069
        before = std::chrono::steady_clock().now();
4✔
5070
        auto tv1 = q.find_all();
4✔
5071
        after = std::chrono::steady_clock().now();
4✔
5072
        auto find_all_without_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5073

5074
        table->add_search_index(col_id);
4✔
5075
        before = std::chrono::steady_clock().now();
4✔
5076
        size_t c2 = q.count();
4✔
5077
        after = std::chrono::steady_clock().now();
4✔
5078
        auto count_with_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5079
        CHECK_EQUAL(c1, c2);
4✔
5080
        before = std::chrono::steady_clock().now();
4✔
5081
        auto tv2 = q.find_all();
4✔
5082
        after = std::chrono::steady_clock().now();
4✔
5083
        auto find_all_with_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5084
        CHECK_EQUAL(tv1.size(), tv2.size());
4✔
5085
        CHECK_EQUAL(tv1.size(), c1);
4✔
5086

5087
        /*
5088
        std::cout << "Query: " << str << std::endl;
5089
        std::cout << "count without index: " << count_without_index << " us" << std::endl;
5090
        std::cout << "find all without index: " << find_all_without_index << " us" << std::endl;
5091
        std::cout << "count with index: " << count_with_index << " us" << std::endl;
5092
        std::cout << "find all with index: " << find_all_with_index << " us" << std::endl;
5093
         */
5094
        static_cast<void>(count_without_index);
4✔
5095
        static_cast<void>(find_all_without_index);
4✔
5096
        static_cast<void>(count_with_index);
4✔
5097
        static_cast<void>(find_all_with_index);
4✔
5098
    }
4✔
5099
}
2✔
5100

5101
TEST(Query_IntFindInNextLeaf)
5102
{
2✔
5103
    Group g;
2✔
5104
    TableRef table = g.add_table("table");
2✔
5105
    auto col_id = table->add_column(type_Int, "id");
2✔
5106

5107
    // num_misses > MAX_BPNODE_SIZE to check results on other leafs
5108
    constexpr int num_misses = 1000 * 2 + 10;
2✔
5109
    for (int i = 0; i < num_misses; i++) {
4,022✔
5110
        table->create_object().set(col_id, i % 10);
4,020✔
5111
    }
4,020✔
5112
    table->create_object().set(col_id, 20);
2✔
5113

5114
    auto check_results = [&]() {
4✔
5115
        for (int i = 0; i < 10; ++i) {
44✔
5116
            Query qi = table->where().equal(col_id, i);
40✔
5117
            CHECK_EQUAL(qi.count(), num_misses / 10);
40✔
5118
        }
40✔
5119
        Query q20 = table->where().equal(col_id, 20);
4✔
5120
        CHECK_EQUAL(q20.count(), 1);
4✔
5121
    };
4✔
5122
    check_results();
2✔
5123
    table->add_search_index(col_id);
2✔
5124
    check_results();
2✔
5125
}
2✔
5126

5127
TEST(Query_IntIndexOverLinkViewNotInTableOrder)
5128
{
2✔
5129
    Group g;
2✔
5130

5131
    TableRef child_table = g.add_table("child");
2✔
5132
    auto col_child_id = child_table->add_column(type_Int, "id");
2✔
5133
    child_table->add_search_index(col_child_id);
2✔
5134

5135
    auto k0 = child_table->create_object().set(col_child_id, 3).get_key();
2✔
5136
    auto k1 = child_table->create_object().set(col_child_id, 2).get_key();
2✔
5137

5138
    TableRef parent_table = g.add_table("parent");
2✔
5139
    auto col_parent_children = parent_table->add_column_list(*child_table, "children");
2✔
5140

5141
    auto parent_obj = parent_table->create_object();
2✔
5142
    auto children = parent_obj.get_linklist(col_parent_children);
2✔
5143
    // Add in reverse order so that the query node sees declining start indices
5144
    children.add(k1);
2✔
5145
    children.add(k0);
2✔
5146

5147
    // Query via linkview
5148
    Query q = child_table->where(children).equal(col_child_id, 3);
2✔
5149
    // Call find() twice. This caused a memory lead at some point. Must pass a memory leak test.
5150
    CHECK_EQUAL(k0, q.find());
2✔
5151
    CHECK_EQUAL(k0, q.find());
2✔
5152
    CHECK_EQUAL(k1, child_table->where(children).equal(col_child_id, 2).find());
2✔
5153

5154
    // Query directly
5155
    CHECK_EQUAL(k0, child_table->where().equal(col_child_id, 3).find());
2✔
5156
    CHECK_EQUAL(k1, child_table->where().equal(col_child_id, 2).find());
2✔
5157
}
2✔
5158

5159
TEST(Query_MixedTypeQuery)
5160
{
2✔
5161
    Group g;
2✔
5162
    auto table = g.add_table("Foo");
2✔
5163
    auto col_int = table->add_column(type_Int, "int");
2✔
5164
    auto col_double = table->add_column(type_Double, "double");
2✔
5165
    for (int64_t i = 0; i < 100; i++) {
202✔
5166
        table->create_object().set(col_int, i).set(col_double, 100. - i);
200✔
5167
    }
200✔
5168

5169
    auto tv = (table->column<Int>(col_int) > 9.5).find_all();
2✔
5170
    CHECK_EQUAL(tv.size(), 90);
2✔
5171
    auto tv1 = (table->column<Int>(col_int) > table->column<Double>(col_double)).find_all();
2✔
5172
    CHECK_EQUAL(tv1.size(), 49);
2✔
5173
}
2✔
5174

5175
TEST(Query_LinkListIntPastOneIsNull)
5176
{
2✔
5177
    Group g;
2✔
5178
    auto table_foo = g.add_table("Foo");
2✔
5179
    auto table_bar = g.add_table("Bar");
2✔
5180
    auto col_int = table_foo->add_column(type_Int, "int", true);
2✔
5181
    auto col_list = table_bar->add_column_list(*table_foo, "foo_link");
2✔
5182
    std::vector<util::Optional<int64_t>> values = {{0}, {1}, {2}, {util::none}};
2✔
5183
    auto bar_obj = table_bar->create_object();
2✔
5184
    auto list = bar_obj.get_linklist(col_list);
2✔
5185

5186
    for (size_t i = 0; i < values.size(); i++) {
10✔
5187
        auto obj = table_foo->create_object();
8✔
5188
        obj.set(col_int, values[i]);
8✔
5189
        list.add(obj.get_key());
8✔
5190
    }
8✔
5191

5192
    Query q = table_bar->link(col_list).column<Int>(col_int) == realm::null();
2✔
5193

5194
    CHECK_EQUAL(q.count(), 1);
2✔
5195
}
2✔
5196

5197
TEST(Query_LinkView_StrIndex)
5198
{
2✔
5199
    Group g;
2✔
5200
    auto table_foo = g.add_table_with_primary_key("class_Foo", type_String, "id");
2✔
5201
    auto col_id = table_foo->get_column_key("id");
2✔
5202

5203
    auto table_bar = g.add_table("class_Bar");
2✔
5204
    auto col_list = table_bar->add_column_list(*table_foo, "link");
2✔
5205

5206
    auto foo = table_foo->create_object_with_primary_key("97625fdc-d3f8-4c45-9a4d-dc8c83c657a1");
2✔
5207
    auto bar = table_bar->create_object();
2✔
5208
    auto ll = bar.get_linklist(col_list);
2✔
5209
    ll.add(foo.get_key());
2✔
5210

5211
    auto q = table_foo->where(ll).equal(col_id, "97625fdc-d3f8-4c45-9a4d-dc8c83c657a1");
2✔
5212
    CHECK_EQUAL(q.count(), 1);
2✔
5213
}
2✔
5214

5215
TEST(Query_StringOrShortStrings)
5216
{
2✔
5217
    Group g;
2✔
5218
    TableRef table = g.add_table("table");
2✔
5219
    auto col_value = table->add_column(type_String, "value");
2✔
5220

5221
    std::string strings[] = {"0", "1", "2"};
2✔
5222
    for (auto& str : strings) {
6✔
5223
        table->create_object().set(col_value, str);
6✔
5224
    }
6✔
5225

5226
    for (auto& str : strings) {
6✔
5227
        Query q = table->where()
6✔
5228
                      .group()
6✔
5229
                      .equal(col_value, StringData(str))
6✔
5230
                      .Or()
6✔
5231
                      .equal(col_value, StringData("not present"))
6✔
5232
                      .end_group();
6✔
5233
        CHECK_EQUAL(q.count(), 1);
6✔
5234
    }
6✔
5235
}
2✔
5236

5237
TEST(Query_StringOrMediumStrings)
5238
{
2✔
5239
    Group g;
2✔
5240
    TableRef table = g.add_table("table");
2✔
5241
    auto col_value = table->add_column(type_String, "value");
2✔
5242

5243
    std::string strings[] = {"0", "1", "2"};
2✔
5244
    for (auto& str : strings) {
6✔
5245
        str.resize(16, str[0]); // Make the strings long enough to require ArrayStringLong
6✔
5246
        table->create_object().set(col_value, str);
6✔
5247
    }
6✔
5248

5249
    for (auto& str : strings) {
6✔
5250
        Query q = table->where()
6✔
5251
                      .group()
6✔
5252
                      .equal(col_value, StringData(str))
6✔
5253
                      .Or()
6✔
5254
                      .equal(col_value, StringData("not present"))
6✔
5255
                      .end_group();
6✔
5256
        CHECK_EQUAL(q.count(), 1);
6✔
5257
    }
6✔
5258
}
2✔
5259

5260
TEST(Query_StringOrLongStrings)
5261
{
2✔
5262
    Group g;
2✔
5263
    TableRef table = g.add_table("table");
2✔
5264
    auto col_value = table->add_column(type_String, "value");
2✔
5265

5266
    std::string strings[] = {"0", "1", "2"};
2✔
5267
    for (auto& str : strings) {
6✔
5268
        str.resize(64, str[0]); // Make the strings long enough to require ArrayBigBlobs
6✔
5269
        table->create_object().set(col_value, str);
6✔
5270
    }
6✔
5271

5272
    for (auto& str : strings) {
6✔
5273
        Query q = table->where()
6✔
5274
                      .group()
6✔
5275
                      .equal(col_value, StringData(str))
6✔
5276
                      .Or()
6✔
5277
                      .equal(col_value, StringData("not present"))
6✔
5278
                      .end_group();
6✔
5279
        CHECK_EQUAL(q.count(), 1);
6✔
5280
    }
6✔
5281
}
2✔
5282

5283
TEST(Query_LinkViewAnd)
5284
{
2✔
5285
    Group g;
2✔
5286

5287
    TableRef child_table = g.add_table("child");
2✔
5288
    auto col_child_id = child_table->add_column(type_Int, "id");
2✔
5289
    auto col_child_name = child_table->add_column(type_String, "name");
2✔
5290

5291
    auto k0 = child_table->create_object().set(col_child_id, 3).set(col_child_name, "Adam").get_key();
2✔
5292
    auto k1 = child_table->create_object().set(col_child_id, 2).set(col_child_name, "Jeff").get_key();
2✔
5293

5294
    TableRef parent_table = g.add_table("parent");
2✔
5295
    auto col_parent_children = parent_table->add_column_list(*child_table, "children");
2✔
5296

5297
    auto parent_obj = parent_table->create_object();
2✔
5298
    auto children = parent_obj.get_linklist(col_parent_children);
2✔
5299
    children.add(k0);
2✔
5300
    children.add(k1);
2✔
5301

5302
    Query q1 = child_table->where(children).equal(col_child_id, 3);
2✔
5303
    Query q2 = child_table->where(children).equal(col_child_name, "Jeff");
2✔
5304
    CHECK_EQUAL(k0, q1.find());
2✔
5305
    CHECK_EQUAL(k1, q2.find());
2✔
5306
    q1.and_query(q2);
2✔
5307
    CHECK_NOT(q1.find());
2✔
5308
}
2✔
5309

5310
TEST(Query_LinksWithIndex)
5311
{
2✔
5312
    Group g;
2✔
5313

5314
    TableRef target = g.add_table("target");
2✔
5315
    auto col_value = target->add_column(type_String, "value");
2✔
5316
    auto col_date = target->add_column(type_Timestamp, "date");
2✔
5317
    target->add_search_index(col_value);
2✔
5318
    target->add_search_index(col_date);
2✔
5319

5320
    TableRef foo = g.add_table("foo");
2✔
5321
    auto col_foo = foo->add_column_list(*target, "linklist");
2✔
5322
    auto col_location = foo->add_column(type_String, "location");
2✔
5323
    auto col_score = foo->add_column(type_Int, "score");
2✔
5324
    foo->add_search_index(col_location);
2✔
5325
    foo->add_search_index(col_score);
2✔
5326

5327
    TableRef middle = g.add_table("middle");
2✔
5328
    auto col_link = middle->add_column(*target, "link");
2✔
5329

5330
    TableRef origin = g.add_table("origin");
2✔
5331
    auto col_linklist = origin->add_column_list(*middle, "linklist");
2✔
5332

5333
    std::vector<StringData> strings{"Copenhagen", "Aarhus", "Odense", "Aalborg", "Faaborg"};
2✔
5334
    auto now = std::chrono::system_clock::now();
2✔
5335
    std::chrono::seconds d{0};
2✔
5336
    for (auto& str : strings) {
10✔
5337
        target->create_object().set(col_value, str).set(col_date, Timestamp(now + d));
10✔
5338
        d = d + std::chrono::seconds{1};
10✔
5339
    }
10✔
5340

5341
    auto m0 = middle->create_object().set(col_link, target->find_first(col_value, strings[0])).get_key();
2✔
5342
    auto m1 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5343
    auto m2 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5344
    auto m3 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5345
    auto m4 = middle->create_object().set(col_link, target->find_first(col_value, strings[3])).get_key();
2✔
5346

5347
    auto obj0 = origin->create_object();
2✔
5348
    obj0.get_linklist(col_linklist).add(m3);
2✔
5349

5350
    auto obj1 = origin->create_object();
2✔
5351
    auto ll1 = obj1.get_linklist(col_linklist);
2✔
5352
    ll1.add(m1);
2✔
5353
    ll1.add(m2);
2✔
5354

5355
    origin->create_object().get_linklist(col_linklist).add(m4);
2✔
5356
    origin->create_object().get_linklist(col_linklist).add(m3);
2✔
5357
    auto obj4 = origin->create_object();
2✔
5358
    obj4.get_linklist(col_linklist).add(m0);
2✔
5359

5360
    Query q = origin->link(col_linklist).link(col_link).column<String>(col_value) == "Odense";
2✔
5361
    CHECK_EQUAL(q.find(), obj0.get_key());
2✔
5362
    auto tv = q.find_all();
2✔
5363
    CHECK_EQUAL(tv.size(), 3);
2✔
5364

5365
    auto ll = foo->create_object().set(col_location, "Fyn").set(col_score, 5).get_linklist(col_foo);
2✔
5366
    ll.add(target->find_first(col_value, strings[2]));
2✔
5367
    ll.add(target->find_first(col_value, strings[4]));
2✔
5368

5369
    Query q1 =
2✔
5370
        origin->link(col_linklist).link(col_link).backlink(*foo, col_foo).column<String>(col_location) == "Fyn";
2✔
5371
    CHECK_EQUAL(q1.find(), obj0.get_key());
2✔
5372
    Query q2 = origin->link(col_linklist).link(col_link).backlink(*foo, col_foo).column<Int>(col_score) == 5;
2✔
5373
    CHECK_EQUAL(q2.find(), obj0.get_key());
2✔
5374

5375
    // Make sure that changes in the table are reflected in the query result
5376
    middle->get_object(m3).set(col_link, target->find_first(col_value, strings[1]));
2✔
5377
    CHECK_EQUAL(q.find(), obj1.get_key());
2✔
5378

5379
    q = origin->link(col_linklist).link(col_link).column<Timestamp>(col_date) == Timestamp(now);
2✔
5380
    CHECK_EQUAL(q.find(), obj4.get_key());
2✔
5381
}
2✔
5382

5383
TEST(Query_NotImmediatelyBeforeKnownRange)
5384
{
2✔
5385
    Group g;
2✔
5386
    TableRef parent = g.add_table("parent");
2✔
5387
    TableRef child = g.add_table("child");
2✔
5388
    auto col_link = parent->add_column_list(*child, "list");
2✔
5389
    auto col_str = child->add_column(type_String, "value");
2✔
5390
    child->add_search_index(col_str);
2✔
5391

5392
    Obj obj = parent->create_object();
2✔
5393
    auto k0 = child->create_object().set(col_str, "a").get_key();
2✔
5394
    auto k1 = child->create_object().set(col_str, "b").get_key();
2✔
5395
    auto list = obj.get_linklist(col_link);
2✔
5396
    list.insert(0, k0);
2✔
5397
    list.insert(0, k1);
2✔
5398

5399
    Query q = child->where(list).Not().equal(col_str, "a");
2✔
5400
    CHECK_EQUAL(q.count(), 1);
2✔
5401
}
2✔
5402

5403
TEST_TYPES(Query_PrimaryKeySearchForNull, Prop<String>, Prop<Int>, Prop<ObjectId>, Nullable<String>, Nullable<Int>,
5404
           Nullable<ObjectId>)
5405
{
12✔
5406
    using type = typename TEST_TYPE::type;
12✔
5407
    using underlying_type = typename TEST_TYPE::underlying_type;
12✔
5408
    Table table;
12✔
5409
    TestValueGenerator gen;
12✔
5410
    auto col = table.add_column(TEST_TYPE::data_type, "property", TEST_TYPE::is_nullable);
12✔
5411
    table.set_primary_key_column(col);
12✔
5412
    underlying_type v0 = gen.convert_for_test<underlying_type>(42);
12✔
5413
    underlying_type v1 = gen.convert_for_test<underlying_type>(43);
12✔
5414
    Mixed mixed_null;
12✔
5415
    auto obj0 = table.create_object_with_primary_key(v0);
12✔
5416
    auto obj1 = table.create_object_with_primary_key(v1);
12✔
5417

5418
    auto verify_result_count = [&](Query& q, size_t expected_count) {
24✔
5419
        CHECK_EQUAL(q.count(), expected_count);
24✔
5420
        TableView tv = q.find_all();
24✔
5421
        CHECK_EQUAL(tv.size(), expected_count);
24✔
5422
    };
24✔
5423
    Query q = table.where().equal(col, v0);
12✔
5424
    verify_result_count(q, 1);
12✔
5425
    q = table.where().equal(col, v1);
12✔
5426
    verify_result_count(q, 1);
12✔
5427

5428
    CHECK_EQUAL(table.find_first(col, v0), obj0.get_key());
12✔
5429
    CHECK_EQUAL(table.find_first(col, v1), obj1.get_key());
12✔
5430
    CHECK_NOT(table.find_first(col, type{}));
12✔
5431
}
12✔
5432

5433
TEST_TYPES(Query_Mixed, std::true_type, std::false_type)
5434
{
4✔
5435
    bool has_index = TEST_TYPE::value;
4✔
5436
    constexpr bool exact_match = true;
4✔
5437
    constexpr bool insensitive_match = false;
4✔
5438
    Group g;
4✔
5439
    auto table = g.add_table("Foo");
4✔
5440
    auto origin = g.add_table("Origin");
4✔
5441
    auto col_any = table->add_column(type_Mixed, "any");
4✔
5442
    auto col_int = table->add_column(type_Int, "int");
4✔
5443
    auto col_link = origin->add_column(*table, "link");
4✔
5444
    auto col_mixed = origin->add_column(type_Mixed, "mixed");
4✔
5445
    auto col_links = origin->add_column_list(*table, "links");
4✔
5446

5447
    if (has_index)
4✔
5448
        table->add_search_index(col_any);
2✔
5449

5450
    size_t int_over_50 = 0;
4✔
5451
    size_t nb_strings = 0;
4✔
5452
    for (int64_t i = 0; i < 100; i++) {
404✔
5453
        if (i % 4) {
400✔
5454
            if (i > 50)
300✔
5455
                int_over_50++;
148✔
5456
            table->create_object().set(col_any, Mixed(i)).set(col_int, i);
300✔
5457
        }
300✔
5458
        else {
100✔
5459
            std::string str = "String" + util::to_string(i);
100✔
5460
            table->create_object().set(col_any, Mixed(str)).set(col_int, i);
100✔
5461
            nb_strings++;
100✔
5462
        }
100✔
5463
    }
400✔
5464
    std::string str2bin("String2Binary");
4✔
5465
    std::string str2bin_lowered = "string2binary";
4✔
5466

5467
    table->get_object(15).set(col_any, Mixed());
4✔
5468
    table->get_object(75).set(col_any, Mixed(75.));
4✔
5469
    table->get_object(28).set(col_any, Mixed(BinaryData(str2bin)));
4✔
5470
    table->get_object(25).set(col_any, Mixed(3.));
4✔
5471
    table->get_object(35).set(col_any, Mixed(Decimal128("3")));
4✔
5472
    table->get_object(80).set(col_any, Mixed("abcdefgh"));
4✔
5473
    table->get_object(81).set(col_any, Mixed(int64_t(0x6867666564636261)));
4✔
5474

5475
    auto it = table->begin();
4✔
5476
    for (int64_t i = 0; i < 10; i++) {
44✔
5477
        auto obj = origin->create_object();
40✔
5478
        auto ll = obj.get_linklist(col_links);
40✔
5479

5480
        obj.set(col_link, it->get_key());
40✔
5481
        if (i % 3) {
40✔
5482
            obj.set(col_mixed, Mixed(i));
24✔
5483
        }
24✔
5484
        else {
16✔
5485
            obj.set(col_mixed, Mixed(table->begin()->get_link()));
16✔
5486
        }
16✔
5487
        for (int64_t j = 0; j < 10; j++) {
440✔
5488
            ll.add(it->get_key());
400✔
5489
            ++it;
400✔
5490
        }
400✔
5491
    }
40✔
5492

5493
    // g.to_json(std::cout);
5494
    auto tv = (table->column<Mixed>(col_any) > Mixed(50)).find_all();
4✔
5495
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5496
    tv = (table->column<Mixed>(col_any) > 50).find_all();
4✔
5497
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5498
    tv = (table->column<Mixed>(col_any) == 37).find_all();
4✔
5499
    CHECK_EQUAL(tv.size(), 1);
4✔
5500
    tv = table->where().equal(col_any, Mixed(37)).find_all();
4✔
5501
    CHECK_EQUAL(tv.size(), 1);
4✔
5502
    tv = (table->column<Mixed>(col_any) >= 50).find_all();
4✔
5503
    CHECK_EQUAL(tv.size(), int_over_50 + 1);
4✔
5504
    tv = (table->column<Mixed>(col_any) <= 50).find_all();
4✔
5505
    CHECK_EQUAL(tv.size(), 100 - int_over_50 - nb_strings - 1);
4✔
5506
    tv = (table->column<Mixed>(col_any) < 50).find_all();
4✔
5507
    CHECK_EQUAL(tv.size(), 100 - int_over_50 - nb_strings - 2);
4✔
5508
    tv = (table->column<Mixed>(col_any) < 50 || table->column<Mixed>(col_any) > 50).find_all();
4✔
5509
    CHECK_EQUAL(tv.size(), 100 - nb_strings - 2);
4✔
5510
    tv = (table->column<Mixed>(col_any) != 50).find_all();
4✔
5511
    CHECK_EQUAL(tv.size(), 99);
4✔
5512

5513
    tv = table->where().greater(col_any, Mixed(50)).find_all();
4✔
5514
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5515
    tv = table->where().greater(col_any, 50).find_all();
4✔
5516
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5517

5518
    tv = table->where().equal(col_any, null()).find_all();
4✔
5519
    CHECK_EQUAL(tv.size(), 1);
4✔
5520
    tv = table->where().not_equal(col_any, null()).find_all();
4✔
5521
    CHECK_EQUAL(tv.size(), 99);
4✔
5522

5523
    tv = table->where().begins_with(col_any, StringData("String2")).find_all(); // 20, 24
4✔
5524
    CHECK_EQUAL(tv.size(), 2);
4✔
5525
    tv = table->where().begins_with(col_any, BinaryData("String2", 7)).find_all(); // 28
4✔
5526
    CHECK_EQUAL(tv.size(), 1);
4✔
5527
    tv = table->where().begins_with(col_any, Mixed(75.), exact_match).find_all();
4✔
5528
    CHECK_EQUAL(tv.size(), 0);
4✔
5529
    tv = table->where().begins_with(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5530
    CHECK_EQUAL(tv.size(), 0);
4✔
5531

5532
    tv = table->where().contains(col_any, StringData("TRIN"), insensitive_match).find_all();
4✔
5533
    CHECK_EQUAL(tv.size(), 23);
4✔
5534
    tv = table->where().contains(col_any, Mixed("TRIN"), insensitive_match).find_all();
4✔
5535
    CHECK_EQUAL(tv.size(), 23);
4✔
5536
    tv = table->where().contains(col_any, Mixed("TRIN"), exact_match).find_all();
4✔
5537
    CHECK_EQUAL(tv.size(), 0);
4✔
5538
    tv = table->where().contains(col_any, Mixed(75.), exact_match).find_all();
4✔
5539
    CHECK_EQUAL(tv.size(), 0);
4✔
5540
    tv = table->where().contains(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5541
    CHECK_EQUAL(tv.size(), 0);
4✔
5542

5543
    tv = table->where().like(col_any, StringData("Strin*")).find_all();
4✔
5544
    CHECK_EQUAL(tv.size(), 23);
4✔
5545
    tv = table->where().like(col_any, Mixed(75.), exact_match).find_all();
4✔
5546
    CHECK_EQUAL(tv.size(), 0);
4✔
5547
    tv = table->where().like(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5548
    CHECK_EQUAL(tv.size(), 0);
4✔
5549

5550
    tv = table->where().ends_with(col_any, StringData("4")).find_all(); // 4, 24, 44, 64, 84
4✔
5551
    CHECK_EQUAL(tv.size(), 5);
4✔
5552
    char bin[1] = {0x34};
4✔
5553
    tv = table->where().ends_with(col_any, BinaryData(bin)).find_all();
4✔
5554
    CHECK_EQUAL(tv.size(), 0);
4✔
5555
    bin[0] = 'y';
4✔
5556
    tv = table->where().ends_with(col_any, BinaryData(bin)).find_all(); // 28
4✔
5557
    CHECK_EQUAL(tv.size(), 1);
4✔
5558
    tv = table->where().ends_with(col_any, Mixed(75.), exact_match).find_all();
4✔
5559
    CHECK_EQUAL(tv.size(), 0);
4✔
5560
    tv = table->where().ends_with(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5561
    CHECK_EQUAL(tv.size(), 0);
4✔
5562

5563
    tv = table->where().equal(col_any, StringData(str2bin), exact_match).find_all();
4✔
5564
    CHECK_EQUAL(tv.size(), 0);
4✔
5565
    tv = table->where().equal(col_any, BinaryData(str2bin), exact_match).find_all();
4✔
5566
    CHECK_EQUAL(tv.size(), 1);
4✔
5567
    tv = table->where().equal(col_any, Mixed{BinaryData(str2bin)}, exact_match).find_all();
4✔
5568
    CHECK_EQUAL(tv.size(), 1);
4✔
5569

5570
    tv = table->where().equal(col_any, Mixed{str2bin_lowered}, insensitive_match).find_all();
4✔
5571
    CHECK_EQUAL(tv.size(), 0);
4✔
5572
    tv = table->where().equal(col_any, Mixed{BinaryData(str2bin_lowered)}, insensitive_match).find_all();
4✔
5573
    CHECK_EQUAL(tv.size(), 1);
4✔
5574

5575
    tv = table->where().not_equal(col_any, Mixed{str2bin_lowered}, insensitive_match).find_all();
4✔
5576
    CHECK_EQUAL(tv.size(), 100);
4✔
5577
    tv = table->where().not_equal(col_any, Mixed{BinaryData(str2bin_lowered)}, insensitive_match).find_all();
4✔
5578
    CHECK_EQUAL(tv.size(), 99);
4✔
5579

5580
    tv = table->where().equal(col_any, StringData(), insensitive_match).find_all();
4✔
5581
    CHECK_EQUAL(tv.size(), 1);
4✔
5582
    tv = table->where().equal(col_any, StringData(), exact_match).find_all();
4✔
5583
    CHECK_EQUAL(tv.size(), 1);
4✔
5584

5585
    tv = table->where().equal(col_any, Mixed(), insensitive_match).find_all();
4✔
5586
    CHECK_EQUAL(tv.size(), 1);
4✔
5587
    tv = table->where().equal(col_any, Mixed(), exact_match).find_all();
4✔
5588
    CHECK_EQUAL(tv.size(), 1);
4✔
5589

5590
    tv = table->where().equal(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5591
    CHECK_EQUAL(tv.size(), 1);
4✔
5592
    tv = table->where().equal(col_any, Mixed(75.), exact_match).find_all();
4✔
5593
    CHECK_EQUAL(tv.size(), 1);
4✔
5594

5595
    tv = (table->column<Mixed>(col_any) == StringData("String48")).find_all();
4✔
5596
    CHECK_EQUAL(tv.size(), 1);
4✔
5597
    tv = (table->column<Mixed>(col_any) == 3.).find_all();
4✔
5598
    CHECK_EQUAL(tv.size(), 3);
4✔
5599
    tv = (table->column<Mixed>(col_any) == table->column<Int>(col_int)).find_all();
4✔
5600
    CHECK_EQUAL(tv.size(), 71);
4✔
5601

5602
    tv = (table->column<Mixed>(col_any) == StringData("abcdefgh")).find_all();
4✔
5603
    CHECK_EQUAL(tv.size(), 1);
4✔
5604
    tv = (table->column<Mixed>(col_any) == StringData("ABCDEFGH")).find_all();
4✔
5605
    CHECK_EQUAL(tv.size(), 0);
4✔
5606

5607
    ObjLink link_to_first = table->begin()->get_link();
4✔
5608
    tv = (origin->column<Mixed>(col_mixed) == Mixed(link_to_first)).find_all();
4✔
5609
    CHECK_EQUAL(tv.size(), 4);
4✔
5610
    tv = (origin->where().links_to(col_mixed, link_to_first)).find_all();
4✔
5611
    CHECK_EQUAL(tv.size(), 4);
4✔
5612
    tv = (origin->where().equal(col_link, Mixed(link_to_first))).find_all();
4✔
5613
    CHECK_EQUAL(tv.size(), 1);
4✔
5614
    tv = (origin->where().equal(col_links, Mixed(link_to_first))).find_all();
4✔
5615
    CHECK_EQUAL(tv.size(), 1);
4✔
5616
    auto q = origin->where().not_equal(col_links, Mixed(link_to_first));
4✔
5617
    auto d = q.get_description();
4✔
5618
    tv = q.find_all();
4✔
5619
    CHECK_EQUAL(tv.size(), 10);
4✔
5620
    q = origin->query(d);
4✔
5621
    tv = q.find_all();
4✔
5622
    CHECK_EQUAL(tv.size(), 10);
4✔
5623
    tv = (origin->link(col_links).column<Mixed>(col_any) > 50).find_all();
4✔
5624
    CHECK_EQUAL(tv.size(), 5);
4✔
5625
    tv = (origin->link(col_link).column<Mixed>(col_any) > 50).find_all();
4✔
5626
    CHECK_EQUAL(tv.size(), 2);
4✔
5627
    std::string bin2str_truncated = str2bin_lowered.substr(0, 10);
4✔
5628
    tv = (origin->link(col_links).column<Mixed>(col_any).contains(bin2str_truncated, insensitive_match)).find_all();
4✔
5629
    CHECK_EQUAL(tv.size(), 0);
4✔
5630
    tv = (origin->link(col_links).column<Mixed>(col_any).contains(BinaryData(bin2str_truncated), insensitive_match))
4✔
5631
             .find_all();
4✔
5632
    CHECK_EQUAL(tv.size(), 1);
4✔
5633
    tv = (origin->link(col_links).column<Mixed>(col_any).like("*ring*", insensitive_match)).find_all();
4✔
5634
    CHECK_EQUAL(tv.size(), 10);
4✔
5635
    tv = (origin->link(col_links).column<Mixed>(col_any).begins_with("String", exact_match)).find_all();
4✔
5636
    CHECK_EQUAL(tv.size(), 10);
4✔
5637
    tv = (origin->link(col_links).column<Mixed>(col_any).ends_with("g40", exact_match)).find_all();
4✔
5638
    CHECK_EQUAL(tv.size(), 1);
4✔
5639
}
4✔
5640

5641
TEST(Query_NestedListNull)
5642
{
2✔
5643
    SHARED_GROUP_TEST_PATH(path);
2✔
5644
    auto hist = make_in_realm_history();
2✔
5645
    DBRef db = DB::create(*hist, path);
2✔
5646
    auto tr = db->start_write();
2✔
5647
    auto foo = tr->add_table("foo");
2✔
5648
    auto col_any = foo->add_column(type_Mixed, "mixed");
2✔
5649

5650
    const char* listOfListOfNull = R"([[null]])";
2✔
5651

5652
    foo->create_object().set_json(col_any, R"("not a list")");
2✔
5653
    foo->create_object().set_json(col_any, listOfListOfNull);
2✔
5654
    foo->create_object().set_json(col_any, listOfListOfNull);
2✔
5655
    foo->create_object().set_json(col_any, listOfListOfNull);
2✔
5656

5657
    CHECK_EQUAL(foo->query("mixed[0][0] == null").count(), 3);
2✔
5658
    CHECK_EQUAL(foo->query("mixed[0][5] == null").count(), 0);
2✔
5659
    CHECK_EQUAL(foo->query("mixed[0][*] == null").count(), 3);
2✔
5660
}
2✔
5661

5662
TEST(Query_NestedDictionaryNull)
5663
{
2✔
5664
    SHARED_GROUP_TEST_PATH(path);
2✔
5665
    auto hist = make_in_realm_history();
2✔
5666
    DBRef db = DB::create(*hist, path);
2✔
5667
    auto tr = db->start_write();
2✔
5668
    auto foo = tr->add_table("foo");
2✔
5669
    auto col_any = foo->add_column(type_Mixed, "mixed");
2✔
5670

5671
    const char* dictOfDictOfNull = R"({ "nestedDict": { "nullValue": null }})";
2✔
5672

5673
    foo->create_object().set_json(col_any, R"("not a dictionary")");
2✔
5674
    foo->create_object().set_json(col_any, dictOfDictOfNull);
2✔
5675
    foo->create_object().set_json(col_any, dictOfDictOfNull);
2✔
5676
    foo->create_object().set_json(col_any, dictOfDictOfNull);
2✔
5677

5678
    CHECK_EQUAL(foo->query("mixed['nestedDict']['nullValue'] == null").count(), 3);
2✔
5679
    CHECK_EQUAL(foo->query("mixed.nestedDict.nullValue == null").count(), 3);
2✔
5680
    CHECK_EQUAL(foo->query("mixed['nestedDict']['foo'] == null").count(), 3);
2✔
5681
    CHECK_EQUAL(foo->query("mixed.nestedDict.foo == null").count(), 3);
2✔
5682
    CHECK_EQUAL(foo->query("mixed.nestedDict[*] == null").count(), 3);
2✔
5683
    CHECK_EQUAL(foo->query("mixed.nestedDict[*].@type == 'null'").count(), 3);
2✔
5684
}
2✔
5685

5686
TEST(Query_ListOfMixed)
5687
{
2✔
5688
    Group g;
2✔
5689
    auto table = g.add_table("Foo");
2✔
5690
    auto origin = g.add_table("Origin");
2✔
5691
    auto col_any = table->add_column_list(type_Mixed, "any");
2✔
5692
    auto col_int = origin->add_column(type_Int, "int");
2✔
5693
    auto col_link = origin->add_column(*table, "link");
2✔
5694
    auto col_links = origin->add_column_list(*table, "links");
2✔
5695
    size_t expected = 0;
2✔
5696

5697
    for (int64_t i = 0; i < 100; i++) {
202✔
5698
        auto obj = table->create_object();
200✔
5699
        auto list = obj.get_list<Mixed>(col_any);
200✔
5700
        if (i % 4) {
200✔
5701
            list.add(i);
150✔
5702
            if (i > 50)
150✔
5703
                expected++;
74✔
5704
        }
150✔
5705
        else if ((i % 10) == 0) {
50✔
5706
            list.add(100.);
10✔
5707
            expected++;
10✔
5708
        }
10✔
5709
        if (i % 3) {
200✔
5710
            std::string str = "String" + util::to_string(i);
132✔
5711
            list.add(str);
132✔
5712
        }
132✔
5713
    }
200✔
5714
    auto it = table->begin();
2✔
5715
    for (int64_t i = 0; i < 10; i++) {
22✔
5716
        auto obj = origin->create_object();
20✔
5717
        obj.set(col_int, 100);
20✔
5718
        auto ll = obj.get_linklist(col_links);
20✔
5719

5720
        obj.set(col_link, it->get_key());
20✔
5721
        for (int64_t j = 0; j < 10; j++) {
220✔
5722
            ll.add(it->get_key());
200✔
5723
            ++it;
200✔
5724
        }
200✔
5725
    }
20✔
5726

5727
    // g.to_json(std::cout, 2);
5728
    auto tv = (table->column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5729
    CHECK_EQUAL(tv.size(), expected);
2✔
5730
    tv = (origin->link(col_links).column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5731
    CHECK_EQUAL(tv.size(), 8);
2✔
5732
    tv = (origin->link(col_link).column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5733
    CHECK_EQUAL(tv.size(), 7);
2✔
5734
    tv = (origin->link(col_links).column<Lst<Mixed>>(col_any) == origin->column<Int>(col_int)).find_all();
2✔
5735
    CHECK_EQUAL(tv.size(), 5);
2✔
5736
}
2✔
5737

5738
TEST(Query_Dictionary)
5739
{
2✔
5740
    Group g;
2✔
5741
    auto foo = g.add_table("foo");
2✔
5742
    auto origin = g.add_table("origin");
2✔
5743
    auto col_dict = foo->add_column_dictionary(type_Mixed, "dict");
2✔
5744
    auto col_link = origin->add_column(*foo, "link");
2✔
5745
    auto col_links = origin->add_column_list(*foo, "links");
2✔
5746
    size_t expected = 0;
2✔
5747

5748
    for (int64_t i = 0; i < 100; i++) {
202✔
5749
        auto obj = foo->create_object();
200✔
5750
        Dictionary dict = obj.get_dictionary(col_dict);
200✔
5751
        bool incr = false;
200✔
5752
        if (i % 4) {
200✔
5753
            dict.insert("Value", i);
150✔
5754
            if (i > 50)
150✔
5755
                incr = true;
74✔
5756
        }
150✔
5757
        else if ((i % 10) == 0) {
50✔
5758
            dict.insert("Foo", "Bar");
10✔
5759
            dict.insert("Value", 100.);
10✔
5760
            incr = true;
10✔
5761
        }
10✔
5762
        if (i % 3) {
200✔
5763
            std::string str = "String" + util::to_string(i);
132✔
5764
            dict.insert("Value", str);
132✔
5765
            incr = false;
132✔
5766
        }
132✔
5767
        if (i == 76) {
200✔
5768
            dict.insert("Value", Mixed());
2✔
5769
        }
2✔
5770
        dict.insert("Dummy", i);
200✔
5771
        if (incr) {
200✔
5772
            expected++;
30✔
5773
        }
30✔
5774
    }
200✔
5775

5776
    auto it = foo->begin();
2✔
5777
    for (int64_t i = 0; i < 10; i++) {
22✔
5778
        auto obj = origin->create_object();
20✔
5779

5780
        obj.set(col_link, it->get_key());
20✔
5781

5782
        auto ll = obj.get_linklist(col_links);
20✔
5783
        for (int64_t j = 0; j < 10; j++) {
220✔
5784
            ll.add(it->get_key());
200✔
5785
            ++it;
200✔
5786
        }
200✔
5787
    }
20✔
5788

5789
    // g.to_json(std::cout);
5790
    auto tv = (foo->column<Dictionary>(col_dict).key("Value") > Mixed(50)).find_all();
2✔
5791
    CHECK_EQUAL(tv.size(), expected);
2✔
5792
    tv = (foo->column<Dictionary>(col_dict) > 50).find_all(); // Any key will do
2✔
5793
    CHECK_EQUAL(tv.size(), 50);                               // 0 and 51..99
2✔
5794

5795
    tv = (origin->link(col_link).column<Dictionary>(col_dict).key("Value") > 50).find_all();
2✔
5796
    CHECK_EQUAL(tv.size(), 3);
2✔
5797
    tv = (origin->link(col_links).column<Dictionary>(col_dict).key("Value") > 50).find_all();
2✔
5798
    CHECK_EQUAL(tv.size(), 6);
2✔
5799
    tv = (origin->link(col_links).column<Dictionary>(col_dict) > 50).find_all();
2✔
5800
    CHECK_EQUAL(tv.size(), 6);
2✔
5801
    tv = (origin->link(col_links).column<Dictionary>(col_dict).key("Value") == null()).find_all();
2✔
5802
    CHECK_EQUAL(tv.size(), 7);
2✔
5803

5804
    tv = (foo->column<Dictionary>(col_dict).keys().begins_with("F")).find_all();
2✔
5805
    CHECK_EQUAL(tv.size(), 5);
2✔
5806
    tv = (origin->link(col_link).column<Dictionary>(col_dict).keys() == "Foo").find_all();
2✔
5807
    CHECK_EQUAL(tv.size(), 5);
2✔
5808
}
2✔
5809

5810
TEST(Query_DictionaryTypedLinks)
5811
{
2✔
5812
    Group g;
2✔
5813
    auto dog = g.add_table("dog");
2✔
5814
    auto cat = g.add_table("cat");
2✔
5815
    auto person = g.add_table("person");
2✔
5816
    auto col_data = person->add_column_dictionary(type_Mixed, "data");
2✔
5817
    auto col_dog_name = dog->add_column(type_String, "Name");
2✔
5818
    auto col_dog_parent = dog->add_column(*dog, "Parent");
2✔
5819
    auto col_cat_name = cat->add_column(type_String, "Name");
2✔
5820

5821
    auto fido = dog->create_object().set(col_dog_name, "Fido");
2✔
5822
    auto pluto = dog->create_object().set(col_dog_name, "Pluto");
2✔
5823
    pluto.set(col_dog_parent, fido.get_key());
2✔
5824
    dog->create_object().set(col_dog_name, "Vaks");
2✔
5825
    auto marie = cat->create_object().set(col_cat_name, "Marie");
2✔
5826
    cat->create_object().set(col_cat_name, "Berlioz");
2✔
5827
    cat->create_object().set(col_cat_name, "Toulouse");
2✔
5828

5829
    auto john = person->create_object().get_dictionary(col_data);
2✔
5830
    auto paul = person->create_object().get_dictionary(col_data);
2✔
5831

5832
    john.insert("Name", "John");
2✔
5833
    john.insert("Pet", pluto);
2✔
5834

5835
    paul.insert("Name", "Paul");
2✔
5836
    paul.insert("Pet", marie);
2✔
5837

5838
    // g.to_json(std::cout, 5);
5839

5840
    auto cnt = person->query("data.Pet.Name == 'Pluto'").count();
2✔
5841
    CHECK_EQUAL(cnt, 1);
2✔
5842
    cnt = person->query("data.Pet.Name == 'Marie'").count();
2✔
5843
    CHECK_EQUAL(cnt, 1);
2✔
5844
    cnt = person->query("data.Pet.Parent.Name == 'Fido'").count();
2✔
5845
    CHECK_EQUAL(cnt, 1);
2✔
5846
}
2✔
5847

5848
TEST(Query_TypeOfValue)
5849
{
2✔
5850
    Group g;
2✔
5851
    auto table = g.add_table("Foo");
2✔
5852
    auto origin = g.add_table("Origin");
2✔
5853
    auto col_any = table->add_column(type_Mixed, "mixed");
2✔
5854
    auto col_int = table->add_column(type_Int, "int");
2✔
5855
    auto col_primitive_list = table->add_column_list(type_Mixed, "list");
2✔
5856
    auto col_link = origin->add_column(*table, "link");
2✔
5857
    auto col_links = origin->add_column_list(*table, "links");
2✔
5858
    size_t nb_ints = 0;
2✔
5859
    size_t nb_strings = 0;
2✔
5860
    for (int64_t i = 0; i < 100; i++) {
202✔
5861
        if (i % 4) {
200✔
5862
            nb_ints++;
150✔
5863
            table->create_object().set(col_any, Mixed(i)).set(col_int, i);
150✔
5864
        }
150✔
5865
        else {
50✔
5866
            std::string str = "String" + util::to_string(i);
50✔
5867
            table->create_object().set(col_any, Mixed(str)).set(col_int, i);
50✔
5868
            nb_strings++;
50✔
5869
        }
50✔
5870
    }
200✔
5871
    std::string bin_data("String2Binary");
2✔
5872
    table->get_object(15).set(col_any, Mixed());
2✔
5873
    nb_ints--;
2✔
5874
    table->get_object(75).set(col_any, Mixed(75.));
2✔
5875
    nb_ints--;
2✔
5876
    table->get_object(28).set(col_any, Mixed(BinaryData(bin_data)));
2✔
5877
    nb_strings--;
2✔
5878
    table->get_object(25).set(col_any, Mixed(3.));
2✔
5879
    nb_ints--;
2✔
5880
    table->get_object(35).set(col_any, Mixed(Decimal128("3")));
2✔
5881
    nb_ints--;
2✔
5882

5883
    auto list_0 = table->get_object(0).get_list<Mixed>(col_primitive_list);
2✔
5884
    list_0.add(Mixed{1});
2✔
5885
    list_0.add(Mixed{Decimal128(10)});
2✔
5886
    list_0.add(Mixed{Double{100}});
2✔
5887
    auto list_1 = table->get_object(1).get_list<Mixed>(col_primitive_list);
2✔
5888
    list_1.add(Mixed{std::string("hello")});
2✔
5889
    list_1.add(Mixed{1000});
2✔
5890

5891
    auto it = table->begin();
2✔
5892
    for (int64_t i = 0; i < 10; i++) {
22✔
5893
        auto obj = origin->create_object();
20✔
5894
        auto ll = obj.get_linklist(col_links);
20✔
5895

5896
        obj.set(col_link, it->get_key());
20✔
5897
        for (int64_t j = 0; j < 10; j++) {
220✔
5898
            ll.add(it->get_key());
200✔
5899
            ++it;
200✔
5900
        }
200✔
5901
    }
20✔
5902

5903
    auto tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string_view("string"))).find_all();
2✔
5904
    CHECK_EQUAL(tv.size(), nb_strings);
2✔
5905
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string_view("double"))).find_all();
2✔
5906
    CHECK_EQUAL(tv.size(), 2);
2✔
5907
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string_view("Decimal128"))).find_all();
2✔
5908
    CHECK_EQUAL(tv.size(), 1);
2✔
5909
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(BinaryData(bin_data))).find_all();
2✔
5910
    CHECK_EQUAL(tv.size(), 1);
2✔
5911
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(util::none)).find_all();
2✔
5912
    CHECK_EQUAL(tv.size(), 1);
2✔
5913
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(type_String)).find_all();
2✔
5914
    CHECK_EQUAL(tv.size(), nb_strings);
2✔
5915
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(col_int)).find_all();
2✔
5916
    CHECK_EQUAL(tv.size(), nb_ints);
2✔
5917
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(col_int)).find_all();
2✔
5918
    CHECK_EQUAL(tv.size(), 2);
2✔
5919
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(type_Decimal)).find_all();
2✔
5920
    CHECK_EQUAL(tv.size(), 1);
2✔
5921
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(type_Int)).find_all();
2✔
5922
    CHECK_EQUAL(tv.size(), 2);
2✔
5923
    tv = (table->column<Lst<Mixed>>(col_primitive_list, ExpressionComparisonType::All).type_of_value() ==
2✔
5924
              TypeOfValue(TypeOfValue::Attribute::Numeric) &&
2✔
5925
          table->column<Lst<Mixed>>(col_primitive_list).size() > 0)
2✔
5926
             .find_all();
2✔
5927
    CHECK_EQUAL(tv.size(), 1);
2✔
5928
}
2✔
5929

5930
TEST(Query_links_to_with_bpnode_split)
5931
{
2✔
5932
    // The bug here is that LinksToNode would read a LinkList as a simple Array
5933
    // instead of a BPTree. So this only worked when the number of items < REALM_MAX_BPNODE_SIZE
5934
    Group g;
2✔
5935
    auto table = g.add_table("Foo");
2✔
5936
    auto origin = g.add_table("Origin");
2✔
5937
    auto col_int = table->add_column(type_Int, "int");
2✔
5938
    auto col_link = origin->add_column(*table, "link");
2✔
5939
    auto col_links = origin->add_column_list(*table, "links");
2✔
5940
    constexpr size_t num_items = REALM_MAX_BPNODE_SIZE + 1;
2✔
5941
    for (size_t i = 0; i < num_items; ++i) {
2,004✔
5942
        table->create_object().set(col_int, int64_t(i));
2,002✔
5943
    }
2,002✔
5944
    for (size_t i = 0; i < num_items; ++i) {
2,004✔
5945
        auto obj = origin->create_object();
2,002✔
5946
        auto it_i = table->begin();
2,002✔
5947
        it_i.go(i);
2,002✔
5948
        obj.set(col_link, it_i->get_key());
2,002✔
5949
        auto list = obj.get_linklist(col_links);
2,002✔
5950
        for (auto it = table->begin(); it != table->end(); ++it) {
2,006,004✔
5951
            list.add(it->get_key());
2,004,002✔
5952
        }
2,004,002✔
5953
    }
2,002✔
5954

5955
    for (auto it = table->begin(); it != table->end(); ++it) {
2,004✔
5956
        Query q = origin->where().links_to(col_links, it->get_key());
2,002✔
5957
        CHECK_EQUAL(q.count(), num_items);
2,002✔
5958
        Query q2 = origin->where().links_to(col_link, it->get_key());
2,002✔
5959
        CHECK_EQUAL(q2.count(), 1);
2,002✔
5960
    }
2,002✔
5961
}
2✔
5962

5963
TEST_TYPES(Query_ManyIn, Prop<Int>, Prop<String>, Prop<Float>, Prop<Double>, Prop<Timestamp>, Prop<UUID>,
5964
           Prop<ObjectId>, Prop<Decimal128>, Prop<BinaryData>, Prop<Mixed>, Nullable<Int>, Nullable<String>,
5965
           Nullable<Float>, Nullable<Double>, Nullable<Timestamp>, Nullable<UUID>, Nullable<ObjectId>,
5966
           Nullable<Decimal128>, Nullable<BinaryData>, Indexed<Int>, Indexed<String>, Indexed<Timestamp>,
5967
           Indexed<UUID>, Indexed<ObjectId>, Indexed<Mixed>)
5968
{
50✔
5969
    using type = typename TEST_TYPE::type;
50✔
5970
    TestValueGenerator gen;
50✔
5971
    Group g;
50✔
5972

5973
    auto t = g.add_table("foo");
50✔
5974
    auto col = t->add_column(TEST_TYPE::data_type, "value", TEST_TYPE::is_nullable);
50✔
5975
    if (TEST_TYPE::is_indexed) {
50✔
5976
        t->add_search_index(col);
12✔
5977
    }
12✔
5978
    constexpr size_t num_values = 200;
50✔
5979
    std::vector<int64_t> seed_values;
50✔
5980
    seed_values.resize(num_values);
50✔
5981
    std::iota(seed_values.begin(), seed_values.end(), 1000);
50✔
5982
    auto values = gen.values_from_int<type>(seed_values);
50✔
5983
    for (auto& v : values) {
10,000✔
5984
        t->create_object().set_any(col, v);
10,000✔
5985
    }
10,000✔
5986
    if (TEST_TYPE::is_nullable) {
50✔
5987
        t->create_object(); // null
18✔
5988
    }
18✔
5989
    std::vector<Mixed> mixed_vals;
50✔
5990
    mixed_vals.insert(mixed_vals.begin(), values.begin(), values.end());
50✔
5991

5992
    CHECK_EQUAL(t->where().in(col, mixed_vals.data(), mixed_vals.data() + mixed_vals.size()).count(), num_values);
50✔
5993

5994
    mixed_vals.push_back(Mixed{});                  // exists for nullable types
50✔
5995
    mixed_vals.push_back(Mixed{ObjectId()});        // value does not exist in data
50✔
5996
    mixed_vals.push_back(Mixed{Timestamp{-1, -1}}); // value does not exist in data
50✔
5997

5998
    CHECK_EQUAL(t->where().in(col, mixed_vals.data(), mixed_vals.data() + mixed_vals.size()).count(),
50✔
5999
                TEST_TYPE::is_nullable ? num_values + 1 : num_values);
50✔
6000
    // empty values for begin == end
6001
    CHECK_EQUAL(t->where().in(col, mixed_vals.data(), mixed_vals.data()).count(), 0);
50✔
6002
    CHECK_EQUAL(t->where().in(col, nullptr, nullptr).count(), 0);
50✔
6003
    // subset of existing values
6004
    CHECK_EQUAL(t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 2).count(),
50✔
6005
                1); // assumes test data at mixed_vals[1] is unique
50✔
6006
    CHECK_EQUAL(t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 3).count(),
50✔
6007
                2); // same for mixed_vals[2]
50✔
6008

6009
    CHECK(mixed_vals[1] != mixed_vals[2]); // the following relies on test data uniqueness
50✔
6010
    TableView tv = t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 3).find_all();
50✔
6011
    CHECK_EQUAL(tv.size(), 2);
50✔
6012
    auto first = tv.get_object(0).get<type>(col);
50✔
6013
    auto second = tv.get_object(1).get<type>(col);
50✔
6014
    bool order = first == mixed_vals[1];
50✔
6015
    CHECK_EQUAL(first, order ? mixed_vals[1] : mixed_vals[2]);
50✔
6016
    CHECK_EQUAL(second, order ? mixed_vals[2] : mixed_vals[1]);
50✔
6017
    size_t count_of_two_ins = t->where()
50✔
6018
                                  .in(col, mixed_vals.data() + 1, mixed_vals.data() + 2)
50✔
6019
                                  .Or()
50✔
6020
                                  .in(col, mixed_vals.data() + 2, mixed_vals.data() + 3)
50✔
6021
                                  .count();
50✔
6022
    size_t count_of_one_in = t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 3).count();
50✔
6023
    CHECK_EQUAL(count_of_one_in, 2);
50✔
6024
    CHECK_EQUAL(count_of_two_ins, 2);
50✔
6025
}
50✔
6026

6027
TEST(Query_ManyIntConditionsAgg)
6028
{
2✔
6029
    SHARED_GROUP_TEST_PATH(path);
2✔
6030
    DBRef db = DB::create(path);
2✔
6031

6032
    constexpr size_t num_values = 1000;
2✔
6033
    {
2✔
6034
        auto wt = db->start_write();
2✔
6035
        auto table = wt->add_table("Foo");
2✔
6036
        auto col_int = table->add_column(type_Int, "int", true);
2✔
6037
        for (size_t i = 0; i < num_values; i++) {
2,002✔
6038
            table->create_object().set(col_int, int64_t(i));
2,000✔
6039
        }
2,000✔
6040
        table->create_object(); // Contains null
2✔
6041
        wt->commit();
2✔
6042
    }
2✔
6043
    {
2✔
6044
        auto rt = db->start_read();
2✔
6045
        auto table = rt->get_table("Foo");
2✔
6046
        auto col = table->get_column_key("int");
2✔
6047

6048
        auto check_query = [&](Query& q) {
4✔
6049
            CHECK_EQUAL(q.count(), num_values);
4✔
6050
            TableView tv = q.find_all();
4✔
6051
            CHECK_EQUAL(tv.size(), num_values);
4✔
6052
            CHECK_EQUAL(q.min(col)->get<Int>(), 0);
4✔
6053
            CHECK_EQUAL(q.max(col)->get<Int>(), num_values - 1);
4✔
6054
            CHECK_EQUAL(q.avg(col)->get<double>(), double(num_values - 1) / 2.0);
4✔
6055
            CHECK_EQUAL(q.sum(col)->get<Int>(), (num_values / 2) * (num_values - 1));
4✔
6056
        };
4✔
6057

6058
        std::vector<Mixed> args;
2✔
6059
        args.reserve(num_values);
2✔
6060
        Query q = table->where();
2✔
6061
        for (size_t i = 0; i < num_values; ++i) {
2,002✔
6062
            q.equal(col, int64_t(i)).Or();
2,000✔
6063
            args.push_back(Mixed(int64_t(i)));
2,000✔
6064
        }
2,000✔
6065
        check_query(q);
2✔
6066
        Query q_in = table->where().in(col, args.data(), args.data() + args.size());
2✔
6067
        check_query(q_in);
2✔
6068
    }
2✔
6069
}
2✔
6070

6071
TEST(Query_FullText)
6072
{
2✔
6073
    Group g;
2✔
6074
    auto table = g.add_table("table");
2✔
6075
    auto col = table->add_column(type_String, "text");
2✔
6076

6077
    // Add before index creation
6078
    table->create_object().set(col, " This is a test, with  spaces!");
2✔
6079
    Obj obj2 = table->create_object().set(col, "Ål, ø og 你好世界Æbler"); // "Hello world" should be filtered out
2✔
6080
    Obj obj3 = table->create_object().set(
2✔
6081
        col,
2✔
6082
        "An object database (also object-oriented database management system) is a database management system in "
2✔
6083
        "which information is represented in the form of objects as used in object-oriented programming. Object "
2✔
6084
        "databases are different from relational databases which are table-oriented. Object-relational databases "
2✔
6085
        "are a hybrid of both approaches.");
2✔
6086
    table->create_object().set(
2✔
6087
        col,
2✔
6088
        "Object database management systems grew out of research during the early to mid-1970s into having "
2✔
6089
        "intrinsic database management support for graph-structured objects. The term 'object-oriented database "
2✔
6090
        "system' first appeared around 1985.[4] Notable research projects included Encore-Ob/Server (Brown "
2✔
6091
        "University), EXODUS (University of Wisconsin–Madison), IRIS (Hewlett-Packard), ODE (Bell Labs), ORION "
2✔
6092
        "(Microelectronics and Computer Technology Corporation or MCC), Vodak (GMD-IPSI), and Zeitgeist (Texas "
2✔
6093
        "Instruments). The ORION project had more published papers than any of the other efforts. Won Kim of MCC "
2✔
6094
        "compiled the best of those papers in a book published by The MIT Press.");
2✔
6095
    table->create_object().set(
2✔
6096
        col, "Lilleø er i mange år blevet anvendt til græsning af Askø-bøndernes kreaturer. I 1788 blev en del af "
2✔
6097
             "Askøs gårde flyttet til Lilleø, og tre gårde eksisterer fortsat på øen. Hovederhvervet på Lilleø er i "
2✔
6098
             "dag frugtavl, og der dyrkes især æbler, pærer og blommer.");
2✔
6099

6100
    // Create the fulltext index
6101
    table->add_fulltext_index(col);
2✔
6102
    CHECK_EQUAL(table->search_index_type(col), IndexType::Fulltext);
2✔
6103

6104
    table->create_object().set(col, "Alle elsker John");
2✔
6105
    table->create_object().set(col, "Johns ven kender John godt");
2✔
6106
    table->create_object().set(col, "Ich wohne in Großarl");
2✔
6107
    table->create_object().set(col, "A short story about a dog running after two cats");
2✔
6108

6109
    auto tv = table->where().fulltext(col, "object").find_all();
2✔
6110
    CHECK_EQUAL(2, tv.size());
2✔
6111

6112
    // Add after index creation
6113
    auto k5 =
2✔
6114
        table->create_object()
2✔
6115
            .set(
2✔
6116
                col,
2✔
6117
                "Early commercial products included Gemstone (Servio Logic, name changed to GemStone Systems), Gbase "
2✔
6118
                "(Graphael), and Vbase (Ontologic). The early to mid-1990s saw additional commercial products enter "
2✔
6119
                "the market. These included ITASCA (Itasca Systems), Jasmine (Fujitsu, marketed by Computer "
2✔
6120
                "Associates), Matisse (Matisse Software), Objectivity/DB (Objectivity, Inc.), ObjectStore (Progress "
2✔
6121
                "Software, acquired from eXcelon which was originally Object Design), ONTOS (Ontos, Inc., name "
2✔
6122
                "changed from Ontologic), O2[6] (O2 Technology, merged with several companies, acquired by Informix, "
2✔
6123
                "which was in turn acquired by IBM), POET (now FastObjects from Versant which acquired Poet Software)"
2✔
6124
                ", Versant Object Database (Versant Corporation), VOSS (Logic Arts) and JADE (Jade Software "
2✔
6125
                "Corporation). Some of these products remain on the market and have been joined by new open source "
2✔
6126
                "and commercial products such as InterSystems Caché.")
2✔
6127
            .get_key();
2✔
6128

6129
    tv.sync_if_needed();
2✔
6130
    CHECK_EQUAL(3, tv.size());
2✔
6131

6132
    // Add another
6133
    table->create_object().set(
2✔
6134
        col, "As the usage of web-based technology increases with the implementation of Intranets and extranets, "
2✔
6135
             "companies have a vested interest in OODBMSs to display their complex data. Using a DBMS that has been "
2✔
6136
             "specifically designed to store data as objects gives an advantage to those companies that are geared "
2✔
6137
             "towards multimedia presentation or organizations that utilize computer-aided design (CAD).[3]");
2✔
6138

6139
    tv.sync_if_needed();
2✔
6140
    CHECK_EQUAL(3, tv.size());
2✔
6141

6142
    // Delete one
6143
    table->remove_object(k5);
2✔
6144
    tv.sync_if_needed();
2✔
6145
    CHECK_EQUAL(2, tv.size());
2✔
6146

6147
    tv = table->where().fulltext(col, "hybrid").find_all();
2✔
6148
    CHECK_EQUAL(1, tv.size());
2✔
6149

6150
    // Change value in place
6151
    obj3.set(
2✔
6152
        col,
2✔
6153
        "Object database management systems added the concept of persistence to object programming languages. The "
2✔
6154
        "early commercial products were integrated with various languages: GemStone (Smalltalk), Gbase (LISP), Vbase "
2✔
6155
        "(COP) and VOSS (Virtual Object Storage System for Smalltalk). For much of the 1990s, C++ dominated the "
2✔
6156
        "commercial object database management market. Vendors added Java in the late 1990s and more recently, C#.");
2✔
6157

6158
    tv = table->where().fulltext(col, "hybrid").find_all();
2✔
6159
    CHECK_EQUAL(0, tv.size());
2✔
6160

6161
    tv = table->where().fulltext(col, "Gemstone").find_all();
2✔
6162
    CHECK_EQUAL(1, tv.size());
2✔
6163

6164
    tv = table->where().fulltext(col, "æbler").find_all();
2✔
6165
    CHECK_EQUAL(2, tv.size());
2✔
6166

6167
    table->create_object().set(
2✔
6168
        col, "The song \"Supercalifragilisticexpialidocious\" is from the 1964 Disney musical film \"Mary Poppins\"");
2✔
6169

6170
    tv = table->where().fulltext(col, "supercalifragilisticexpialidocious mary").find_all();
2✔
6171
    CHECK_EQUAL(1, tv.size());
2✔
6172

6173
    obj2.remove();
2✔
6174
    tv.sync_if_needed();
2✔
6175
    CHECK_EQUAL(1, tv.size());
2✔
6176

6177
    tv = table->where().fulltext(col, "Johns").find_all();
2✔
6178
    CHECK_EQUAL(1, tv.size());
2✔
6179
    tv = table->where().fulltext(col, "John").find_all();
2✔
6180
    CHECK_EQUAL(2, tv.size());
2✔
6181
    tv = table->where().fulltext(col, "Großarl").find_all();
2✔
6182
    CHECK_EQUAL(1, tv.size());
2✔
6183
    tv = table->where().fulltext(col, "catssadasdsa").find_all();
2✔
6184
    CHECK_EQUAL(0, tv.size());
2✔
6185

6186
    table->clear();
2✔
6187
    CHECK(table->get_search_index(col)->is_empty());
2✔
6188
}
2✔
6189

6190
TEST(Query_FullTextMulti)
6191
{
2✔
6192
    Group g;
2✔
6193
    auto table = g.add_table("table");
2✔
6194
    auto origin = g.add_table_with_primary_key("origin", type_Int, "id");
2✔
6195
    auto col_link = origin->add_column_list(*table, "link");
2✔
6196
    auto col = table->add_column(type_String, "text");
2✔
6197
    table->add_fulltext_index(col);
2✔
6198

6199
    table->create_object().set(
2✔
6200
        col,
2✔
6201
        "An object database (also object-oriented database management system) is a database management system in "
2✔
6202
        "which information is represented in the form of objects as used in object-oriented programming. Object "
2✔
6203
        "databases are different from relational databases which are table-oriented. Object-relational databases "
2✔
6204
        "are a hybrid of both approaches.");
2✔
6205
    table->create_object().set(
2✔
6206
        col,
2✔
6207
        "Object database management systems grew out of research during the early to mid-1970s into having "
2✔
6208
        "intrinsic database management support for graph-structured objects. The term 'object-oriented database "
2✔
6209
        "system' first appeared around 1985.[4] Notable research projects included Encore-Ob/Server (Brown "
2✔
6210
        "University), EXODUS (University of Wisconsin–Madison), IRIS (Hewlett-Packard), ODE (Bell Labs), ORION "
2✔
6211
        "(Microelectronics and Computer Technology Corporation or MCC), Vodak (GMD-IPSI), and Zeitgeist (Texas "
2✔
6212
        "Instruments). The ORION project had more published papers than any of the other efforts. Won Kim of MCC "
2✔
6213
        "compiled the best of those papers in a book published by The MIT Press.");
2✔
6214
    table->create_object().set(
2✔
6215
        col,
2✔
6216
        "Early commercial products included Gemstone (Servio Logic, name changed to GemStone Systems), Gbase "
2✔
6217
        "(Graphael), and Vbase (Ontologic). The early to mid-1990s saw additional commercial products enter the "
2✔
6218
        "market. These included ITASCA (Itasca Systems), Jasmine (Fujitsu, marketed by Computer Associates), Matisse "
2✔
6219
        "(Matisse Software), Objectivity/DB (Objectivity, Inc.), ObjectStore (Progress Software, acquired from "
2✔
6220
        "eXcelon which was originally Object Design), ONTOS (Ontos, Inc., name changed from Ontologic), O2[6] (O2 "
2✔
6221
        "Technology, merged with several companies, acquired by Informix, which was in turn acquired by IBM), POET "
2✔
6222
        "(now FastObjects from Versant which acquired Poet Software), Versant Object Database (Versant Corporation), "
2✔
6223
        "VOSS (Logic Arts) and JADE (Jade Software Corporation). Some of these products remain on the market and "
2✔
6224
        "have been joined by new open source and commercial products such as InterSystems Caché.");
2✔
6225
    table->create_object().set(
2✔
6226
        col, "As the usage of web-based technology increases with the implementation of Intranets and extranets, "
2✔
6227
             "companies have a vested interest in OODBMSs to display their complex data. Using a DBMS that has been "
2✔
6228
             "specifically designed to store data as objects gives an advantage to those companies that are geared "
2✔
6229
             "towards multimedia presentation or organizations that utilize computer-aided design (CAD).[3]");
2✔
6230
    table->create_object().set(
2✔
6231
        col,
2✔
6232
        "Object database management systems added the concept of persistence to object programming languages. The "
2✔
6233
        "early commercial products were integrated with various languages: GemStone (Smalltalk), Gbase (LISP), Vbase "
2✔
6234
        "(COP) and VOSS (Virtual Object Storage System for Smalltalk). For much of the 1990s, C++ dominated the "
2✔
6235
        "commercial object database management market. Vendors added Java in the late 1990s and more recently, C#.");
2✔
6236

6237
    table->create_object().set(
2✔
6238
        col, "L’archive ouverte pluridisciplinaire HAL, est destinée au dépôt et à la diffusion de documents "
2✔
6239
             "scientifiques de niveau recherche, publiés ou non, émanant des établissements d’enseignement et de "
2✔
6240
             "recherche français ou étrangers, des laboratoires publics ou privés.");
2✔
6241
    table->create_object().set(col, "object object object object object duplicates");
2✔
6242
    table->create_object().set(col, "one two three");
2✔
6243
    table->create_object().set(col, "three two one");
2✔
6244
    table->create_object().set(col, "two one");
2✔
6245

6246
    // object:              0, 1, 2, 4, 6
6247
    // objects:             0, 1, 3
6248
    // 'object-oriented':   0, 1
6249
    // 'table-oriented':    0
6250
    // oriented:            0, 1
6251
    // gemstone:            2, 4
6252
    // data:                3
6253
    // depot:               5
6254
    // emanant:             5
6255
    // database:            0, 1, 2, 4
6256
    // databases:           0
6257
    // duplicates:          6
6258

6259
    int64_t id = 1000;
2✔
6260
    for (auto& o : *table) {
20✔
6261
        auto ll = origin->create_object_with_primary_key(id++).get_linklist(col_link);
20✔
6262
        ll.add(o.get_key());
20✔
6263
    }
20✔
6264

6265
    typedef std::vector<int64_t> Keys;
2✔
6266
    auto get_keys = [&](const TableView& tv) -> Keys {
54✔
6267
        std::vector<int64_t> keys(tv.size());
54✔
6268
        for (size_t i = 0; i < tv.size(); ++i)
164✔
6269
            keys[i] = tv.get_key(i).value;
110✔
6270
        return keys;
54✔
6271
    };
54✔
6272
    auto do_fulltext_find = [&](StringData term) -> Keys {
64✔
6273
        return get_keys(table->where().fulltext(col, term).find_all());
64✔
6274
    };
64✔
6275
    auto do_query_find = [&](const TableRef& table, StringData query) -> Keys {
4✔
6276
        return get_keys(table->query(query).find_all());
4✔
6277
    };
4✔
6278

6279
    CHECK_THROW_ANY(do_fulltext_find(""));
2✔
6280

6281
    // search with multiple terms
6282
    CHECK_EQUAL(do_fulltext_find("ONE THREE"), Keys({7, 8}));
2✔
6283
    CHECK_EQUAL(do_fulltext_find("three one"), Keys({7, 8}));
2✔
6284
    CHECK_EQUAL(do_fulltext_find("1990s"), Keys({2, 4}));
2✔
6285
    CHECK_EQUAL(do_fulltext_find("1990s c++"), Keys({4}));
2✔
6286
    CHECK_EQUAL(do_fulltext_find("object gemstone"), Keys({2, 4}));
2✔
6287

6288
    // over links
6289
    CHECK_EQUAL(do_query_find(origin, "link.text TEXT 'object gemstone'"), Keys({2, 4}));
2✔
6290
    auto tv = origin->link(col_link).column<String>(col).fulltext("object gemstone").find_all();
2✔
6291
    CHECK_EQUAL(get_keys(tv), Keys({2, 4}));
2✔
6292

6293
    // through LnkLst
6294
    auto obj = tv.get_object(0);
2✔
6295
    auto ll = obj.get_linklist(col_link);
2✔
6296
    tv = table->where(ll).fulltext(col, "object gemstone").find_all();
2✔
6297
    CHECK_EQUAL(get_keys(tv), Keys({2}));
2✔
6298

6299
    // Diacritics ignorant
6300
    CHECK_EQUAL(do_fulltext_find("depot emanant archive"), Keys({5}));
2✔
6301

6302
    // search for combination that is not present
6303
    CHECK_EQUAL(do_fulltext_find("object data"), Keys());
2✔
6304

6305
    // Prefix
6306
    CHECK_EQUAL(do_fulltext_find("manage*"), Keys({0, 1, 4}));
2✔
6307
    CHECK_EQUAL(do_fulltext_find("manage* virtu*"), Keys({4}));
2✔
6308

6309
    // exclude words
6310
    CHECK_EQUAL(do_fulltext_find("-three one"), Keys({9}));
2✔
6311
    CHECK_EQUAL(do_fulltext_find("one -three"), Keys({9}));
2✔
6312
    CHECK_EQUAL(do_fulltext_find("object -databases"), Keys({1, 2, 4, 6}));
2✔
6313
    CHECK_EQUAL(do_fulltext_find("-databases object -duplicates"), Keys({1, 2, 4}));
2✔
6314
    CHECK_EQUAL(do_fulltext_find("object -objects"), Keys({2, 4, 6}));
2✔
6315
    CHECK_EQUAL(do_fulltext_find("-object objects"), Keys({3}));
2✔
6316
    CHECK_EQUAL(do_fulltext_find("databases -database"), Keys({}));
2✔
6317
    CHECK_EQUAL(do_fulltext_find("-database databases"), Keys({}));
2✔
6318
    CHECK_EQUAL(do_fulltext_find("database -databases"), Keys({1, 2, 4}));
2✔
6319
    CHECK_EQUAL(do_fulltext_find("-databases database"), Keys({1, 2, 4}));
2✔
6320
    CHECK_EQUAL(do_fulltext_find("-database"), Keys({3, 5, 6, 7, 8, 9}));
2✔
6321
    CHECK_EQUAL(do_fulltext_find("-object"), Keys({3, 5, 7, 8, 9}));
2✔
6322
    CHECK_EQUAL(do_fulltext_find("-object -objects"), Keys({5, 7, 8, 9}));
2✔
6323

6324
    // Don't include and exclude same token
6325
    CHECK_THROW_ANY(do_fulltext_find("C# -c++")); // Will both end up as 'c'
2✔
6326
    CHECK_THROW_ANY(do_fulltext_find("-object object"));
2✔
6327
    CHECK_THROW_ANY(do_fulltext_find("object -object"));
2✔
6328
    CHECK_THROW_ANY(do_fulltext_find("objects -object object"));
2✔
6329
    CHECK_THROW_ANY(do_fulltext_find("object -object object"));
2✔
6330
    CHECK_THROW_ANY(do_fulltext_find("database -database"));
2✔
6331

6332
    // many terms
6333
    CHECK_EQUAL(do_fulltext_find("object database management brown"), Keys({1}));
2✔
6334
    CHECK_EQUAL(do_query_find(table, "text TEXT 'object database management brown'"), Keys({1}));
2✔
6335

6336
    // non alphanum characters not allowed inside seach token
6337
    CHECK_THROW_ANY(do_fulltext_find("object-oriented -database"));
2✔
6338
    CHECK_THROW_ANY(do_fulltext_find("object-oriented -table-oriented"));
2✔
6339

6340
    while (table->size() > 0) {
22✔
6341
        table->begin()->remove();
20✔
6342
    }
20✔
6343

6344
    CHECK(table->get_search_index(col)->is_empty());
2✔
6345
}
2✔
6346

6347
TEST(Query_FullTextPrefix)
6348
{
2✔
6349
    Group g;
2✔
6350
    auto table = g.add_table("table");
2✔
6351
    auto col = table->add_column(type_String, "text");
2✔
6352
    table->add_fulltext_index(col);
2✔
6353

6354
    table->create_object().set(col, "Abby Abba Ada Adalee Baylee Bellamy Blaire Adalyn");
2✔
6355
    table->create_object().set(col, "Abigail Abba Barbara Beatrice Bella Blair Blake");
2✔
6356
    table->create_object().set(col, "Adaline Bellamy Blakely");
2✔
6357

6358
    // table->get_search_index(col)->do_dump_node_structure(std::cout, 0);
6359

6360
    auto q = table->query("text TEXT 'ab*'");
2✔
6361
    CHECK_EQUAL(q.count(), 2);
2✔
6362
    q = table->query("text TEXT 'ac*'"); // No match shorter than 4
2✔
6363
    CHECK_EQUAL(q.count(), 0);
2✔
6364
    q = table->query("text TEXT 'abbe*'"); // No match excatly four
2✔
6365
    CHECK_EQUAL(q.count(), 0);
2✔
6366
    q = table->query("text TEXT 'abbex*'"); // No match bigger than 4
2✔
6367
    CHECK_EQUAL(q.count(), 0);
2✔
6368
    q = table->query("text TEXT 'Bel*'");
2✔
6369
    CHECK_EQUAL(q.count(), 3);
2✔
6370
    q = table->query("text TEXT 'Blak*'");
2✔
6371
    CHECK_EQUAL(q.count(), 2);
2✔
6372
    q = table->query("text TEXT 'Bellam*'");
2✔
6373
    CHECK_EQUAL(q.count(), 2);
2✔
6374
    q = table->query("text TEXT 'Bel* Abba -Ada'");
2✔
6375
    CHECK_EQUAL(q.count(), 1);
2✔
6376
}
2✔
6377

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