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

realm / realm-core / 1771

20 Oct 2023 08:58AM UTC coverage: 91.567% (-0.009%) from 91.576%
1771

push

Evergreen

web-flow
Fix blocked DB::open on multiprocess access on exFAT filesystem (#6959)

Fix double file lock and DB::open being blocked with multiple concurrent realm access on fat32/exfat file systems.

When file is truncated on fat32/exfat its uid is available for other files. With multiple processes opening and truncating the same set of files could lead to the situation when within one process get_unique_id will return the same value for different files in timing is right. This breaks proper initialization of static data for interprocess mutexes, so that subsequent locks will hang by trying to lock essentially the same file twice.

94304 of 173552 branches covered (0.0%)

59 of 82 new or added lines in 5 files covered. (71.95%)

53 existing lines in 13 files now uncovered.

230544 of 251776 relevant lines covered (91.57%)

6594884.0 hits per line

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

99.08
/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

1✔
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

1✔
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

1✔
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

1✔
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

1✔
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

1✔
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

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

1✔
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

1✔
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

1✔
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

1✔
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

1✔
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

1✔
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

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

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

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

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

219

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

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

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

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

254

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

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

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

277

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

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

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

303

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

458

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

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

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

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

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

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

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

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

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

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

514

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
665

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

1✔
674
    table.enumerate_string_column(col_str);
2✔
675

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

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

687

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

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

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

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

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

2✔
710
    ttt.add_search_index(col);
4✔
711

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

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

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

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

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

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

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

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

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

758

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

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

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

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

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

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

794
#endif
795

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

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

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

813

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

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

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

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

835

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

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

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

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

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

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

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

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

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

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

890

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

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

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

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

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

1✔
932
    ObjKey resindex;
2✔
933

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

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

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

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

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

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

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

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

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

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

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

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

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

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

977

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

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

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

1✔
995
    size_t cnt;
2✔
996

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

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

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

1012

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

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

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

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

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

1052

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

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

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

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

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

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

2✔
1089
        ObjKey ndx;
4✔
1090

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

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

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

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

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

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

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

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

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

1126

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

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

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

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

1144

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

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

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

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

1✔
1163

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

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

1✔
1169

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

1✔
1175

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

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

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

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

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

1✔
1206

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

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

1✔
1216

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1283
    tv.sync_if_needed();
2✔
1284

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

1✔
1300
    Query q3;
2✔
1301

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

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

1309

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

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

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

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

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

1✔
1336
    Query q2(q1);
2✔
1337

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1,928✔
1463
                    StringData sd;
3,922✔
1464
                    std::string st;
3,922✔
1465

1,928✔
1466
                    if (fastrand(1) == 0) {
3,922✔
1467
                        // null string
972✔
1468
                        sd = realm::null();
1,970✔
1469
                        st = "null";
1,970✔
1470
                    }
1,970✔
1471
                    else {
1,952✔
1472
                        // non-null string
956✔
1473
                        size_t len = static_cast<size_t>(fastrand(3));
1,952✔
1474
                        if (len == 0)
1,952✔
1475
                            len = 0;
507✔
1476
                        else if (len == 1)
1,445✔
1477
                            len = 7;
453✔
1478
                        else if (len == 2)
992✔
1479
                            len = 27;
500✔
1480
                        else
492✔
1481
                            len = 73;
492✔
1482

956✔
1483
                        if (fastrand(1) == 0) {
1,952✔
1484
                            // duplicate string
467✔
1485
                            sd = StringData(buf1, len);
962✔
1486
                            st = std::string(buf1, len);
962✔
1487
                        }
962✔
1488
                        else {
990✔
1489
                            // random string
489✔
1490
                            for (size_t s = 0; s < len; s++) {
26,707✔
1491
                                if (fastrand(100) > 20)
25,717✔
1492
                                    buf2[s] = 0; // zero byte
20,425✔
1493
                                else
5,292✔
1494
                                    buf2[s] = static_cast<char>(fastrand(255)); // random byte
5,292✔
1495
                            }
25,717✔
1496
                            // no generated string can equal "null" (our vector magic value for null) because
489✔
1497
                            // len == 4 is not possible
489✔
1498
                            sd = StringData(buf2, len);
990✔
1499
                            st = std::string(buf2, len);
990✔
1500
                        }
990✔
1501
                    }
1,952✔
1502

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

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

4,000✔
1521

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

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

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

1✔
1548
    TableView t;
2✔
1549

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

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

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

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

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

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

1✔
1577

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1645
    TableView t;
2✔
1646

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

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

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

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

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

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

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

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

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

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

1✔
1694
    TableView t;
2✔
1695

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

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

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

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

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

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

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

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

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

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

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

1756
namespace {
1757

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

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

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

89✔
1782
    return true;
178✔
1783
}
178✔
1784

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1854
    TableView tv;
2✔
1855

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1903

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1996

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

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

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

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

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

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

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

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

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

1✔
2045

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

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

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

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

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

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

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

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

2082

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
2184
    TableView tv;
2✔
2185

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2439

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
2603

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

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

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

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

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

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

2650

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

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

2694
    size_t keys_added = 0;
2695
};
2696

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3✔
2819
    // Maximum.
3✔
2820

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

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

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

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

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

3✔
2848

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

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

3✔
2860

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

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

3✔
2872

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

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

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

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

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

3✔
2898

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

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

3✔
2910

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

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

3✔
2922

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

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

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

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

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

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

3✔
2954

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

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

3✔
2966

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

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

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

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

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

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

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

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

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

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

3✔
3015
    // Binary operators.
3✔
3016

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3157
TEST(Query_DeepLink)
3158
{
2✔
3159

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

1✔
3170
    const int N = 10;
2✔
3171

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2✔
3521

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2✔
3583

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

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

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

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

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

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

2✔
3609

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2✔
3674

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

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

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

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

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

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

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

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

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

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

2✔
3711

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
3819

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
3926

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
4259
    {
2✔
4260
        Query q1 = table.where().equal(col_id, 1);
2✔
4261
        Query q2 = q1 && table.where();
2✔
4262
        CHECK_EQUAL(1, q2.count());
2✔
4263

1✔
4264
        Query q3 = table.where() && q1;
2✔
4265
        CHECK_EQUAL(1, q3.count());
2✔
4266
    }
2✔
4267

1✔
4268
    {
2✔
4269
        Query q1 = table.where().equal(col_id, 1);
2✔
4270
        Query q2 = q1 || table.where();
2✔
4271
        CHECK_EQUAL(1, q2.count());
2✔
4272

1✔
4273
        Query q3 = table.where() || q1;
2✔
4274
        CHECK_EQUAL(1, q3.count());
2✔
4275
    }
2✔
4276
}
2✔
4277

4278
// Check that queries take into account restricting views, but still
4279
// return row index into the underlying table
4280
TEST(Query_AccountForRestrictingViews)
4281
{
2✔
4282
    Table table;
2✔
4283
    auto col_id = table.add_column(type_Int, "id");
2✔
4284

1✔
4285
    table.create_object().set(col_id, 42);
2✔
4286
    table.create_object().set(col_id, 43);
2✔
4287
    table.create_object().set(col_id, 44);
2✔
4288

1✔
4289
    {
2✔
4290
        // Create initial table view
1✔
4291
        TableView results = table.where().equal(col_id, 44).find_all();
2✔
4292
        CHECK_EQUAL(1, results.size());
2✔
4293
        CHECK_EQUAL(44, results[0].get<Int>(col_id));
2✔
4294

1✔
4295
        // Create query based on restricting view
1✔
4296
        Query q = Query(results.get_parent()->where(&results));
2✔
4297
        ObjKey obj_key = q.find();
2✔
4298
        CHECK_EQUAL(obj_key, results.get_key(0));
2✔
4299
    }
2✔
4300
}
2✔
4301

4302
/*
4303

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

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

4317

4318
TEST(Query_UTF8_Contains_Fuzzy)
4319
{
4320
    Table table;
4321
    table.add_column(type_String, "str1");
4322
    table.add_empty_row();
4323

4324
    for (size_t t = 0; t < 10000; t++) {
4325
        char haystack[10];
4326
        char needle[7];
4327

4328
        for (size_t c = 0; c < 10; c++)
4329
            haystack[c] = char(fastrand());
4330

4331
        for (size_t c = 0; c < 7; c++)
4332
            needle[c] = char(fastrand());
4333

4334
        table.set_string(0, 0, StringData(haystack, 10));
4335

4336
        table.column<String>(0).contains(StringData(needle, fastrand(7)), false).count();
4337
        table.column<String>(0).contains(StringData(needle, fastrand(7)), true).count();
4338
    }
4339
}
4340
*/
4341

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

10✔
4350
        TableRef contact = group.add_table("contact");
20✔
4351
        TableRef contact_type = group.add_table("contact_type");
20✔
4352

10✔
4353
        auto col_int = contact_type->add_column(type_Int, "id");
20✔
4354
        auto col_str = contact_type->add_column(type_String, "str");
20✔
4355
        auto col_link = contact->add_column_list(*contact_type, "link");
20✔
4356

10✔
4357
        std::vector<ObjKey> contact_type_keys;
20✔
4358
        std::vector<ObjKey> contact_keys;
20✔
4359
        contact_type->create_objects(10, contact_type_keys);
20✔
4360
        contact->create_objects(10, contact_keys);
20✔
4361

10✔
4362
        Query q1 = (contact->link(col_link).column<Int>(col_int) == 0);
20✔
4363
        Query q2 = contact_type->where().equal(col_int, 0);
20✔
4364
        Query q3 = contact_type->query("id + id == 0");
20✔
4365
        Query q4 = (contact_type->column<Int>(col_int) == 0);
20✔
4366
        Query q5 = (contact_type->column<String>(col_str) == "hejsa");
20✔
4367

10✔
4368
        TableView tv = q1.find_all();
20✔
4369
        TableView tv2 = q2.find_all();
20✔
4370
        TableView tv3 = q3.find_all();
20✔
4371
        TableView tv4 = q4.find_all();
20✔
4372
        TableView tv5 = q5.find_all();
20✔
4373

10✔
4374
        contact->add_column(type_Float, "extra");
20✔
4375
        contact_type->add_column(type_Float, "extra");
20✔
4376

10✔
4377
        for (size_t t = 0; t < REALM_MAX_BPNODE_SIZE + 1; t++) {
20,040✔
4378
            Obj contact_obj = contact->create_object();
20,020✔
4379
            Obj contact_type_obj = contact_type->create_object();
20,020✔
4380
            //  contact_type.get()->set_string(1, t, "hejsa");
10,010✔
4381

10,010✔
4382
            auto ll = contact_obj.get_linklist(col_link);
20,020✔
4383
            ll.add(contact_type_obj.get_key());
20,020✔
4384

10,010✔
4385
            if (t == 0 || t == REALM_MAX_BPNODE_SIZE) {
20,020✔
4386
                tv.sync_if_needed();
40✔
4387
                tv2.sync_if_needed();
40✔
4388
                tv3.sync_if_needed();
40✔
4389
                tv4.sync_if_needed();
40✔
4390
                tv5.sync_if_needed();
40✔
4391
            }
40✔
4392
        }
20,020✔
4393
    }
20✔
4394
}
2✔
4395

4396

4397
TEST(Query_ColumnDeletionSimple)
4398
{
2✔
4399
    Table foo;
2✔
4400
    auto col_int0 = foo.add_column(type_Int, "a");
2✔
4401
    auto col_int1 = foo.add_column(type_Int, "b");
2✔
4402

1✔
4403
    std::vector<ObjKey> keys;
2✔
4404
    foo.create_objects(10, keys);
2✔
4405

1✔
4406
    foo.get_object(keys[3]).set(col_int0, 123);
2✔
4407
    foo.get_object(keys[4]).set(col_int0, 123);
2✔
4408
    foo.get_object(keys[7]).set(col_int0, 123);
2✔
4409
    foo.get_object(keys[2]).set(col_int1, 456);
2✔
4410
    foo.get_object(keys[4]).set(col_int1, 456);
2✔
4411

1✔
4412
    auto q1 = foo.column<Int>(col_int0) == 123;
2✔
4413
    auto q2 = foo.column<Int>(col_int1) == 456;
2✔
4414
    auto q3 = q1 || q2;
2✔
4415
    TableView tv1 = q1.find_all();
2✔
4416
    TableView tv2 = q2.find_all();
2✔
4417
    TableView tv3 = q3.find_all();
2✔
4418
    CHECK_EQUAL(tv1.size(), 3);
2✔
4419
    CHECK_EQUAL(tv2.size(), 2);
2✔
4420
    CHECK_EQUAL(tv3.size(), 4);
2✔
4421

1✔
4422
    foo.remove_column(col_int0);
2✔
4423

1✔
4424
    size_t x = 0;
2✔
4425
    CHECK_LOGIC_ERROR(x = q1.count(), ErrorCodes::InvalidProperty);
2✔
4426
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4427
    CHECK_EQUAL(x, 0);
2✔
4428
    CHECK_EQUAL(tv1.size(), 0);
2✔
4429

1✔
4430
    // This one should succeed in spite the column index is 1 and we
1✔
4431
    x = q2.count();
2✔
4432
    tv2.sync_if_needed();
2✔
4433
    CHECK_EQUAL(x, 2);
2✔
4434
    CHECK_EQUAL(tv2.size(), 2);
2✔
4435

1✔
4436
    x = 0;
2✔
4437
    CHECK_LOGIC_ERROR(x = q3.count(), ErrorCodes::InvalidProperty);
2✔
4438
    CHECK_LOGIC_ERROR(tv3.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4439
    CHECK_EQUAL(x, 0);
2✔
4440
    CHECK_EQUAL(tv3.size(), 0);
2✔
4441
}
2✔
4442

4443

4444
TEST(Query_ColumnDeletionExpression)
4445
{
2✔
4446
    Table foo;
2✔
4447
    auto col_int0 = foo.add_column(type_Int, "a");
2✔
4448
    auto col_int1 = foo.add_column(type_Int, "b");
2✔
4449
    auto col_date2 = foo.add_column(type_Timestamp, "c");
2✔
4450
    auto col_date3 = foo.add_column(type_Timestamp, "d");
2✔
4451
    auto col_str4 = foo.add_column(type_String, "e");
2✔
4452
    auto col_float5 = foo.add_column(type_Float, "f");
2✔
4453
    auto col_bin6 = foo.add_column(type_Binary, "g");
2✔
4454

1✔
4455
    Obj obj0 = foo.create_object();
2✔
4456
    Obj obj1 = foo.create_object();
2✔
4457
    Obj obj2 = foo.create_object();
2✔
4458
    Obj obj3 = foo.create_object();
2✔
4459
    Obj obj4 = foo.create_object();
2✔
4460
    obj0.set(col_int0, 0);
2✔
4461
    obj1.set(col_int0, 1);
2✔
4462
    obj2.set(col_int0, 2);
2✔
4463
    obj3.set(col_int0, 3);
2✔
4464
    obj4.set(col_int0, 4);
2✔
4465
    obj0.set(col_int1, 0);
2✔
4466
    obj1.set(col_int1, 0);
2✔
4467
    obj2.set(col_int1, 3);
2✔
4468
    obj3.set(col_int1, 5);
2✔
4469
    obj4.set(col_int1, 3);
2✔
4470
    obj0.set(col_date2, Timestamp(100, 100));
2✔
4471
    obj0.set(col_date3, Timestamp(200, 100));
2✔
4472
    obj0.set(col_str4, StringData("Hello, world"));
2✔
4473
    obj0.set(col_float5, 3.141592f);
2✔
4474
    obj1.set(col_float5, 1.0f);
2✔
4475
    obj0.set(col_bin6, BinaryData("Binary", 6));
2✔
4476

1✔
4477
    // Expression
1✔
4478
    auto q = foo.query("a == b + 1");
2✔
4479
    // TwoColumnsNode
1✔
4480
    auto q1 = foo.column<Int>(col_int0) == foo.column<Int>(col_int1);
2✔
4481
    TableView tv = q.find_all();
2✔
4482
    TableView tv1 = q1.find_all();
2✔
4483
    CHECK_EQUAL(tv.size(), 2);
2✔
4484
    CHECK_EQUAL(tv1.size(), 1);
2✔
4485

1✔
4486
    foo.remove_column(col_int0);
2✔
4487
    size_t x = 0;
2✔
4488
    CHECK_LOGIC_ERROR(x = q.count(), ErrorCodes::InvalidProperty);
2✔
4489
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4490
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4491
    CHECK_EQUAL(x, 0);
2✔
4492
    CHECK_EQUAL(tv.size(), 0);
2✔
4493

1✔
4494
    q = foo.column<Timestamp>(col_date2) < foo.column<Timestamp>(col_date3);
2✔
4495
    // TimestampNode
1✔
4496
    q1 = foo.column<Timestamp>(col_date3) == Timestamp(200, 100);
2✔
4497
    tv = q.find_all();
2✔
4498
    tv1 = q1.find_all();
2✔
4499
    CHECK_EQUAL(tv.size(), 1);
2✔
4500
    CHECK_EQUAL(tv1.size(), 1);
2✔
4501
    foo.remove_column(col_date3);
2✔
4502
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4503
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4504

1✔
4505
    // StringNodeBase
1✔
4506
    q = foo.column<String>(col_str4) == StringData("Hello, world");
2✔
4507
    q1 = !(foo.column<String>(col_str4) == StringData("Hello, world"));
2✔
4508
    tv = q.find_all();
2✔
4509
    tv1 = q1.find_all();
2✔
4510
    CHECK_EQUAL(tv.size(), 1);
2✔
4511
    CHECK_EQUAL(tv1.size(), 4);
2✔
4512
    foo.remove_column(col_str4);
2✔
4513
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4514
    CHECK_LOGIC_ERROR(tv1.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4515

1✔
4516
    // FloatDoubleNode
1✔
4517
    q = foo.column<Float>(col_float5) > 0.0f;
2✔
4518
    tv = q.find_all();
2✔
4519
    CHECK_EQUAL(tv.size(), 2);
2✔
4520
    foo.remove_column(col_float5);
2✔
4521
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4522

1✔
4523
    // BinaryNode
1✔
4524
    q = foo.column<Binary>(col_bin6) != BinaryData("Binary", 6);
2✔
4525
    tv = q.find_all();
2✔
4526
    CHECK_EQUAL(tv.size(), 4);
2✔
4527
    foo.remove_column(col_bin6);
2✔
4528
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4529
}
2✔
4530

4531

4532
TEST(Query_ColumnDeletionLinks)
4533
{
2✔
4534
    Group g;
2✔
4535
    TableRef foo = g.add_table("foo");
2✔
4536
    TableRef bar = g.add_table("bar");
2✔
4537
    TableRef foobar = g.add_table("foobar");
2✔
4538

1✔
4539
    auto col_int0 = foobar->add_column(type_Int, "int");
2✔
4540

1✔
4541
    auto col_int1 = bar->add_column(type_Int, "int");
2✔
4542
    auto col_link0 = bar->add_column(*foobar, "link");
2✔
4543

1✔
4544
    auto col_link1 = foo->add_column(*bar, "link");
2✔
4545

1✔
4546
    std::vector<ObjKey> foobar_keys;
2✔
4547
    std::vector<ObjKey> bar_keys;
2✔
4548
    std::vector<ObjKey> foo_keys;
2✔
4549
    foobar->create_objects(5, foobar_keys);
2✔
4550
    bar->create_objects(5, bar_keys);
2✔
4551
    foo->create_objects(10, foo_keys);
2✔
4552

1✔
4553
    for (int i = 0; i < 5; i++) {
12✔
4554
        foobar->get_object(foobar_keys[i]).set(col_int0, i);
10✔
4555
        bar->get_object(bar_keys[i]).set(col_int1, i);
10✔
4556
        bar->get_object(bar_keys[i]).set(col_link0, foobar_keys[i]);
10✔
4557
        foo->get_object(foo_keys[i]).set(col_link1, bar_keys[i]);
10✔
4558
    }
10✔
4559
    auto q = foo->link(col_link1).link(col_link0).column<Int>(col_int0) == 2;
2✔
4560
    auto q1 = foo->column<Link>(col_link1).is_null();
2✔
4561
    auto q2 = foo->column<Link>(col_link1) == bar->get_object(bar_keys[2]);
2✔
4562

1✔
4563
    auto tv = q.find_all();
2✔
4564
    auto cnt = q1.count();
2✔
4565
    CHECK_EQUAL(tv.size(), 1);
2✔
4566
    CHECK_EQUAL(cnt, 5);
2✔
4567
    cnt = q2.count();
2✔
4568
    CHECK_EQUAL(cnt, 1);
2✔
4569

1✔
4570
    // remove integer column, should not affect query
1✔
4571
    bar->remove_column(col_int1);
2✔
4572
    tv.sync_if_needed();
2✔
4573
    CHECK_EQUAL(tv.size(), 1);
2✔
4574
    // remove link column, disaster
1✔
4575
    bar->remove_column(col_link0);
2✔
4576
    CHECK_LOGIC_ERROR(bar->check_column(col_link0), ErrorCodes::InvalidProperty);
2✔
4577
    CHECK_LOGIC_ERROR(tv.sync_if_needed(), ErrorCodes::InvalidProperty);
2✔
4578
    foo->remove_column(col_link1);
2✔
4579
    CHECK_LOGIC_ERROR(foo->check_column(col_link1), ErrorCodes::InvalidProperty);
2✔
4580
    CHECK_LOGIC_ERROR(q1.count(), ErrorCodes::InvalidProperty);
2✔
4581
    CHECK_LOGIC_ERROR(q2.count(), ErrorCodes::InvalidProperty);
2✔
4582
}
2✔
4583

4584

4585
TEST(Query_CaseInsensitiveIndexEquality_CommonNumericPrefix)
4586
{
2✔
4587
    Table table;
2✔
4588
    auto col_ndx = table.add_column(type_String, "id");
2✔
4589
    table.add_search_index(col_ndx);
2✔
4590

1✔
4591
    ObjKey key0 = table.create_object().set(col_ndx, "111111111111111111111111").get_key();
2✔
4592
    table.create_object().set(col_ndx, "111111111111111111111112");
2✔
4593

1✔
4594
    Query q = table.where().equal(col_ndx, "111111111111111111111111", false);
2✔
4595
    CHECK_EQUAL(q.count(), 1);
2✔
4596
    TableView tv = q.find_all();
2✔
4597
    CHECK_EQUAL(tv.size(), 1);
2✔
4598
    CHECK_EQUAL(tv[0].get_key(), key0);
2✔
4599
}
2✔
4600

4601

4602
TEST_TYPES(Query_CaseInsensitiveNullable, std::true_type, std::false_type)
4603
{
4✔
4604
    Table table;
4✔
4605
    bool nullable = true;
4✔
4606
    constexpr bool with_index = TEST_TYPE::value;
4✔
4607
    auto col_ndx = table.add_column(type_String, "id", nullable);
4✔
4608
    if (with_index) {
4✔
4609
        table.add_search_index(col_ndx);
2✔
4610
    }
2✔
4611

2✔
4612
    table.create_object().set(col_ndx, "test");
4✔
4613
    table.create_object().set(col_ndx, "words");
4✔
4614
    ObjKey key2 = table.create_object().get_key();
4✔
4615
    ObjKey key3 = table.create_object().get_key();
4✔
4616
    table.create_object().set(col_ndx, "");
4✔
4617
    table.create_object().set(col_ndx, "");
4✔
4618

2✔
4619
    bool case_sensitive = true;
4✔
4620
    StringData null_string;
4✔
4621
    Query q = table.where().equal(col_ndx, null_string, case_sensitive);
4✔
4622
    CHECK_EQUAL(q.count(), 2);
4✔
4623
    TableView tv = q.find_all();
4✔
4624
    CHECK_EQUAL(tv.size(), 2);
4✔
4625
    CHECK_EQUAL(tv.get_key(0), key2);
4✔
4626
    CHECK_EQUAL(tv.get_key(1), key3);
4✔
4627
    Query q2 = table.where().contains(col_ndx, null_string, case_sensitive);
4✔
4628
    CHECK_EQUAL(q2.count(), 6);
4✔
4629
    tv = q2.find_all();
4✔
4630
    CHECK_EQUAL(tv.size(), 6);
4✔
4631

2✔
4632
    case_sensitive = false;
4✔
4633
    q = table.where().equal(col_ndx, null_string, case_sensitive);
4✔
4634
    CHECK_EQUAL(q.count(), 2);
4✔
4635
    tv = q.find_all();
4✔
4636
    CHECK_EQUAL(tv.size(), 2);
4✔
4637
    CHECK_EQUAL(tv.get_key(0), key2);
4✔
4638
    CHECK_EQUAL(tv.get_key(1), key3);
4✔
4639
    q2 = table.where().contains(col_ndx, null_string, case_sensitive);
4✔
4640
    CHECK_EQUAL(q2.count(), 6);
4✔
4641
    tv = q2.find_all();
4✔
4642
    CHECK_EQUAL(tv.size(), 6);
4✔
4643
}
4✔
4644

4645

4646
TEST_TYPES(Query_Rover, std::true_type, std::false_type)
4647
{
4✔
4648
    constexpr bool nullable = TEST_TYPE::value;
4✔
4649

2✔
4650
    Table table;
4✔
4651
    auto col = table.add_column(type_String, "name", nullable);
4✔
4652
    table.add_search_index(col);
4✔
4653

2✔
4654
    table.create_object().set(col, "ROVER");
4✔
4655
    table.create_object().set(col, "Rover");
4✔
4656

2✔
4657
    Query q = table.where().equal(col, "rover", false);
4✔
4658
    CHECK_EQUAL(q.count(), 2);
4✔
4659
    TableView tv = q.find_all();
4✔
4660
    CHECK_EQUAL(tv.size(), 2);
4✔
4661
}
4✔
4662

4663
TEST(Query_StringPrimaryKey)
4664
{
2✔
4665
    Table table;
2✔
4666
    auto col = table.add_column(type_String, "name");
2✔
4667
    table.set_primary_key_column(col);
2✔
4668

1✔
4669
    table.create_object_with_primary_key("RASMUS");
2✔
4670
    table.create_object_with_primary_key("Rasmus");
2✔
4671

1✔
4672
    Query q = table.where().equal(col, "rasmus", false);
2✔
4673
    CHECK_EQUAL(q.count(), 2);
2✔
4674
    TableView tv = q.find_all();
2✔
4675
    CHECK_EQUAL(tv.size(), 2);
2✔
4676
}
2✔
4677

4678
TEST(Query_IntOnly)
4679
{
2✔
4680
    Table table;
2✔
4681
    auto c0 = table.add_column(type_Int, "i1");
2✔
4682
    auto c1 = table.add_column(type_Int, "i2");
2✔
4683

1✔
4684
    table.create_object(ObjKey(7)).set_all(7, 6);
2✔
4685
    table.create_object(ObjKey(19)).set_all(19, 9);
2✔
4686
    table.create_object(ObjKey(5)).set_all(19, 22);
2✔
4687
    table.create_object(ObjKey(21)).set_all(2, 6);
2✔
4688

1✔
4689
    auto q = table.column<Int>(c1) == 6;
2✔
4690
    ObjKey key = q.find();
2✔
4691
    CHECK_EQUAL(key, ObjKey(7));
2✔
4692

1✔
4693
    TableView tv = q.find_all();
2✔
4694
    CHECK_EQUAL(tv.size(), 2);
2✔
4695
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(7));
2✔
4696
    CHECK_EQUAL(tv.get_object(1).get_key(), ObjKey(21));
2✔
4697

1✔
4698
    auto q1 = table.where(&tv).equal(c0, 2);
2✔
4699
    TableView tv1 = q1.find_all();
2✔
4700
    CHECK_EQUAL(tv1.size(), 1);
2✔
4701
    CHECK_EQUAL(tv1.get_object(0).get_key(), ObjKey(21));
2✔
4702

1✔
4703
    q1 = table.where(&tv).greater(c0, 5);
2✔
4704
    tv1 = q1.find_all();
2✔
4705
    CHECK_EQUAL(tv1.size(), 1);
2✔
4706
    CHECK_EQUAL(tv1.get_object(0).get_key(), ObjKey(7));
2✔
4707

1✔
4708
    q = table.column<Int>(c0) == 19 && table.column<Int>(c1) == 9;
2✔
4709
    key = q.find();
2✔
4710
    CHECK_EQUAL(key.value, 19);
2✔
4711

1✔
4712
    tv = q.find_all();
2✔
4713
    CHECK_EQUAL(tv.size(), 1);
2✔
4714
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(19));
2✔
4715

1✔
4716
    // Two column expression
1✔
4717
    q = table.column<Int>(c0) < table.column<Int>(c1);
2✔
4718
    tv = q.find_all();
2✔
4719
    CHECK_EQUAL(tv.size(), 2);
2✔
4720
    CHECK_EQUAL(tv.get_object(0).get_key(), ObjKey(5));
2✔
4721
    CHECK_EQUAL(tv.get_object(1).get_key(), ObjKey(21));
2✔
4722
}
2✔
4723

4724
TEST(Query_LinksTo)
4725
{
2✔
4726
    Query q;
2✔
4727
    ObjKey found_key;
2✔
4728
    Group group;
2✔
4729

1✔
4730
    TableRef source = group.add_table("source");
2✔
4731
    TableRef target = group.add_table("target");
2✔
4732

1✔
4733
    auto col_link = source->add_column(*target, "link");
2✔
4734
    auto col_linklist = source->add_column_list(*target, "linklist");
2✔
4735

1✔
4736
    std::vector<ObjKey> target_keys;
2✔
4737
    target->create_objects(10, target_keys);
2✔
4738

1✔
4739
    std::vector<ObjKey> source_keys;
2✔
4740
    source->create_objects(10, source_keys);
2✔
4741

1✔
4742
    source->get_object(source_keys[2]).set(col_link, target_keys[2]);
2✔
4743
    source->get_object(source_keys[5]).set(col_link, target_keys[5]);
2✔
4744
    source->get_object(source_keys[8]).set(col_link, target_keys[5]);
2✔
4745
    source->get_object(source_keys[9]).set(col_link, target_keys[9]);
2✔
4746

1✔
4747
    q = source->column<Link>(col_link) == target->get_object(target_keys[2]);
2✔
4748
    found_key = q.find();
2✔
4749
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4750

1✔
4751
    q = source->where().equal(col_link, Mixed(target_keys[2]));
2✔
4752
    found_key = q.find();
2✔
4753
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4754

1✔
4755
    q = source->column<Link>(col_link) == target->get_object(target_keys[5]);
2✔
4756
    found_key = q.find();
2✔
4757
    CHECK_EQUAL(found_key, source_keys[5]);
2✔
4758
    q = source->where().equal(col_link, Mixed(target_keys[5]));
2✔
4759
    auto tv = q.find_all();
2✔
4760
    CHECK_EQUAL(tv.size(), 2);
2✔
4761
    q = source->where().not_equal(col_link, Mixed(target_keys[5]));
2✔
4762
    tv = q.find_all();
2✔
4763
    CHECK_EQUAL(tv.size(), 8);
2✔
4764
    q = source->where().equal(col_link, Mixed(ObjLink(source->get_key(), target_keys[5]))); // Wrong table
2✔
4765
    tv = q.find_all();
2✔
4766
    CHECK_EQUAL(tv.size(), 0);
2✔
4767

1✔
4768
    q = source->column<Link>(col_link) == target->get_object(target_keys[9]);
2✔
4769
    found_key = q.find();
2✔
4770
    CHECK_EQUAL(found_key, source_keys[9]);
2✔
4771

1✔
4772
    q = source->column<Link>(col_link) == target->get_object(target_keys[0]);
2✔
4773
    found_key = q.find();
2✔
4774
    CHECK_EQUAL(found_key, null_key);
2✔
4775

1✔
4776
    q = source->column<Link>(col_link).is_null();
2✔
4777
    tv = q.find_all();
2✔
4778
    CHECK_EQUAL(tv.size(), 6);
2✔
4779
    q = source->where().equal(col_link, Mixed()); // Null
2✔
4780
    tv = q.find_all();
2✔
4781
    CHECK_EQUAL(tv.size(), 6);
2✔
4782

1✔
4783
    q = source->column<Link>(col_link) != null();
2✔
4784
    found_key = q.find();
2✔
4785
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4786
    q = source->where().not_equal(col_link, Mixed()); // Null
2✔
4787
    tv = q.find_all();
2✔
4788
    CHECK_EQUAL(tv.size(), 4);
2✔
4789

1✔
4790
    auto linklist = source->get_object(source_keys[1]).get_linklist_ptr(col_linklist);
2✔
4791
    linklist->add(target_keys[6]);
2✔
4792
    linklist = source->get_object(source_keys[2]).get_linklist_ptr(col_linklist);
2✔
4793
    linklist->add(target_keys[0]);
2✔
4794
    linklist->add(target_keys[1]);
2✔
4795
    linklist->add(target_keys[2]);
2✔
4796
    linklist = source->get_object(source_keys[8]).get_linklist_ptr(col_linklist);
2✔
4797
    linklist->add(target_keys[0]);
2✔
4798
    linklist->add(target_keys[5]);
2✔
4799
    linklist->add(target_keys[7]);
2✔
4800

1✔
4801
    q = source->column<Link>(col_linklist) == target->get_object(target_keys[5]);
2✔
4802
    found_key = q.find();
2✔
4803
    CHECK_EQUAL(found_key, source_keys[8]);
2✔
4804

1✔
4805
    q = source->column<Link>(col_linklist) != target->get_object(target_keys[6]);
2✔
4806
    found_key = q.find();
2✔
4807
    CHECK_EQUAL(found_key, source_keys[2]);
2✔
4808

1✔
4809
    q = source->where().equal(col_linklist, Mixed(target_keys[0]));
2✔
4810
    tv = q.find_all();
2✔
4811
    CHECK_EQUAL(tv.size(), 2);
2✔
4812
    q = source->where().not_equal(col_linklist, Mixed(target_keys[6]));
2✔
4813
    tv = q.find_all();
2✔
4814
    CHECK_EQUAL(tv.size(), 2);
2✔
4815

1✔
4816
    q = source->where().equal(col_linklist, Mixed());
2✔
4817
    tv = q.find_all();
2✔
4818
    CHECK_EQUAL(tv.size(), 0); // LinkList never matches null
2✔
4819
    q = source->where().not_equal(col_linklist, Mixed());
2✔
4820
    tv = q.find_all();
2✔
4821
    CHECK_EQUAL(tv.size(), 3);
2✔
4822
}
2✔
4823

4824
TEST(Query_Group_bug)
4825
{
2✔
4826
    // Tests for a bug in queries with OR nodes at different nesting levels
1✔
4827

1✔
4828
    Group g;
2✔
4829
    TableRef service_table = g.add_table("service");
2✔
4830
    TableRef profile_table = g.add_table("profile");
2✔
4831
    TableRef person_table = g.add_table("person");
2✔
4832

1✔
4833
    auto col_service_id = service_table->add_column(type_String, "id");
2✔
4834
    auto col_service_link = service_table->add_column_list(*profile_table, "profiles");
2✔
4835

1✔
4836
    auto col_profile_id = profile_table->add_column(type_String, "role");
2✔
4837
    auto col_profile_link = profile_table->add_column(*service_table, "services");
2✔
4838

1✔
4839
    auto col_person_id = person_table->add_column(type_String, "id");
2✔
4840
    auto col_person_link = person_table->add_column_list(*service_table, "services");
2✔
4841

1✔
4842
    auto sk0 = service_table->create_object().set(col_service_id, "service_1").get_key();
2✔
4843
    auto sk1 = service_table->create_object().set(col_service_id, "service_2").get_key();
2✔
4844

1✔
4845
    auto pk0 = profile_table->create_object().set(col_profile_id, "profile_1").get_key();
2✔
4846
    auto pk1 = profile_table->create_object().set(col_profile_id, "profile_2").get_key();
2✔
4847
    auto pk2 = profile_table->create_object().set(col_profile_id, "profile_3").get_key();
2✔
4848
    auto pk3 = profile_table->create_object().set(col_profile_id, "profile_4").get_key();
2✔
4849
    auto pk4 = profile_table->create_object().set(col_profile_id, "profile_5").get_key();
2✔
4850

1✔
4851
    {
2✔
4852
        auto ll0 = service_table->get_object(sk0).get_linklist(col_service_link);
2✔
4853
        auto ll1 = service_table->get_object(sk1).get_linklist(col_service_link);
2✔
4854
        ll0.add(pk0);
2✔
4855
        ll0.add(pk1);
2✔
4856
        ll1.add(pk2);
2✔
4857
        ll0.add(pk3);
2✔
4858
        ll0.add(pk4);
2✔
4859
    }
2✔
4860

1✔
4861
    profile_table->get_object(pk0).set(col_profile_link, sk0);
2✔
4862
    profile_table->get_object(pk1).set(col_profile_link, sk0);
2✔
4863
    profile_table->get_object(pk2).set(col_profile_link, sk1);
2✔
4864
    profile_table->get_object(pk3).set(col_profile_link, sk0);
2✔
4865
    profile_table->get_object(pk4).set(col_profile_link, sk0);
2✔
4866

1✔
4867
    person_table->create_object().set(col_person_id, "person_1").get_linklist(col_person_link).add(sk0);
2✔
4868
    person_table->create_object().set(col_person_id, "person_2").get_linklist(col_person_link).add(sk0);
2✔
4869
    person_table->create_object().set(col_person_id, "person_3").get_linklist(col_person_link).add(sk1);
2✔
4870
    person_table->create_object().set(col_person_id, "person_4").get_linklist(col_person_link).add(sk0);
2✔
4871
    person_table->create_object().set(col_person_id, "person_5").get_linklist(col_person_link).add(sk0);
2✔
4872

1✔
4873
    realm::Query q0 =
2✔
4874
        person_table->where()
2✔
4875
            .group()
2✔
4876

1✔
4877
            .group()
2✔
4878
            .and_query(person_table->link(col_person_link)
2✔
4879
                           .link(col_service_link)
2✔
4880
                           .column<String>(col_profile_id)
2✔
4881
                           .equal("profile_1"))
2✔
4882
            .Or()
2✔
4883
            .and_query(person_table->link(col_person_link)
2✔
4884
                           .link(col_service_link)
2✔
4885
                           .column<String>(col_profile_id)
2✔
4886
                           .equal("profile_2"))
2✔
4887
            .end_group()
2✔
4888

1✔
4889
            .group()
2✔
4890
            .and_query(person_table->link(col_person_link).column<String>(col_service_id).equal("service_1"))
2✔
4891
            .end_group()
2✔
4892

1✔
4893
            .end_group()
2✔
4894

1✔
4895
            .Or()
2✔
4896

1✔
4897
            .group()
2✔
4898
            .equal(col_person_id, "person_3")
2✔
4899
            .end_group();
2✔
4900

1✔
4901
    CHECK_EQUAL(5, q0.count());
2✔
4902
}
2✔
4903

4904
TEST(Query_TwoColumnUnaligned)
4905
{
2✔
4906
    Group g;
2✔
4907
    TableRef table = g.add_table("table");
2✔
4908
    ColKey a_col_ndx = table->add_column(type_Int, "a");
2✔
4909
    ColKey b_col_ndx = table->add_column(type_Int, "b");
2✔
4910

1✔
4911
    // Adding 1001 rows causes arrays in the 2 columns to be aligned differently
1✔
4912
    // (on a 0 and on an 8 address resp)
1✔
4913
    auto matches = 0;
2✔
4914
    for (int i = 0; i < 1001; ++i) {
2,004✔
4915
        Obj obj = table->create_object();
2,002✔
4916
        obj.set(a_col_ndx, i);
2,002✔
4917
        if (i % 88) {
2,002✔
4918
            obj.set(b_col_ndx, i + 5);
1,978✔
4919
        }
1,978✔
4920
        else {
24✔
4921
            obj.set(b_col_ndx, i);
24✔
4922
            matches++;
24✔
4923
        }
24✔
4924
    }
2,002✔
4925

1✔
4926
    Query q = table->column<Int>(a_col_ndx) == table->column<Int>(b_col_ndx);
2✔
4927
    size_t cnt = q.count();
2✔
4928
    CHECK_EQUAL(cnt, matches);
2✔
4929
}
2✔
4930

4931

4932
TEST(Query_IntOrQueryOptimisation)
4933
{
2✔
4934
    Group g;
2✔
4935
    TableRef table = g.add_table("table");
2✔
4936
    auto col_optype = table->add_column(type_String, "optype");
2✔
4937
    auto col_active = table->add_column(type_Bool, "active");
2✔
4938
    auto col_id = table->add_column(type_Int, "id");
2✔
4939

1✔
4940
    for (int i = 0; i < 100; i++) {
202✔
4941
        auto obj = table->create_object();
200✔
4942
        obj.set<bool>(col_active, (i % 10) != 0);
200✔
4943
        obj.set<int>(col_id, i);
200✔
4944
        if (i == 0) obj.set(col_optype, "CREATE");
200✔
4945
        if (i == 1) obj.set(col_optype, "DELETE");
200✔
4946
        if (i == 2) obj.set(col_optype, "CREATE");
200✔
4947
    }
200✔
4948
    auto optype = table->column<String>(col_optype);
2✔
4949
    auto active = table->column<Bool>(col_active);
2✔
4950
    auto id = table->column<Int>(col_id);
2✔
4951

1✔
4952
    Query q;
2✔
4953
    q = (id == 0 && optype == "CREATE") || id == 1;
2✔
4954
    CHECK_EQUAL(q.count(), 2);
2✔
4955

1✔
4956
    q = id == 1 || (id == 0 && optype == "DELETE");
2✔
4957
    CHECK_EQUAL(q.count(), 1);
2✔
4958

1✔
4959
    q = table->where().equal(col_id, 1).Or().equal(col_id, 0).Or().equal(col_id, 2);
2✔
4960
    CHECK_EQUAL(q.count(), 3);
2✔
4961
}
2✔
4962

4963
TEST_IF(Query_IntOrQueryPerformance, TEST_DURATION > 0)
4964
{
×
4965
    using std::chrono::duration_cast;
×
4966
    using std::chrono::microseconds;
×
4967

4968
    Group g;
×
4969
    TableRef table = g.add_table("table");
×
4970
    auto ints_col_key = table->add_column(type_Int, "ints");
×
4971
    auto nullable_ints_col_key = table->add_column(type_Int, "nullable_ints", true);
×
4972

4973
    const int null_frequency = 1000;
×
4974
    int num_nulls_added = 0;
×
4975
    int limit = 100000;
×
4976
    for (int i = 0; i < limit; ++i) {
×
4977
        if (i % null_frequency == 0) {
×
4978
            auto o = table->create_object().set_all(i);
×
4979
            o.set_null(nullable_ints_col_key);
×
4980
            ++num_nulls_added;
×
4981
        }
×
4982
        else {
×
4983
            table->create_object().set_all(i, i);
×
4984
        }
×
4985
    }
×
4986

4987
    auto run_queries = [&](int num_matches) {
×
4988
        // std::cout << "num_matches: " << num_matches << std::endl;
4989
        Query q_ints = table->column<Int>(ints_col_key) == -1;
×
4990
        Query q_nullables =
×
4991
            (table->column<Int>(nullable_ints_col_key) == -1).Or().equal(nullable_ints_col_key, realm::null());
×
4992
        for (int i = 0; i < num_matches; ++i) {
×
4993
            q_ints = q_ints.Or().equal(ints_col_key, i);
×
4994
            q_nullables = q_nullables.Or().equal(nullable_ints_col_key, i);
×
4995
        }
×
4996

4997
        auto before = std::chrono::steady_clock().now();
×
4998
        size_t ints_count = q_ints.count();
×
4999
        auto after = std::chrono::steady_clock().now();
×
5000
        // std::cout << "ints count: " << duration_cast<microseconds>(after - before).count() << " us" << std::endl;
5001

5002
        before = std::chrono::steady_clock().now();
×
5003
        size_t nullable_ints_count = q_nullables.count();
×
5004
        after = std::chrono::steady_clock().now();
×
5005
        // std::cout << "nullable ints count: " << duration_cast<microseconds>(after - before).count() << " us"
5006
        //           << std::endl;
5007

5008
        size_t expected_nullable_query_count =
×
5009
            num_matches + num_nulls_added - (((num_matches - 1) / null_frequency) + 1);
×
5010
        CHECK_EQUAL(ints_count, num_matches);
×
5011
        CHECK_EQUAL(nullable_ints_count, expected_nullable_query_count);
×
5012
    };
×
5013

5014
    run_queries(2);
×
5015
    run_queries(2048);
×
5016

5017
    table->add_search_index(ints_col_key);
×
5018
    table->add_search_index(nullable_ints_col_key);
×
5019

5020
    run_queries(2);
×
5021
    run_queries(2048);
×
5022
}
×
5023

5024
TEST(Query_IntIndexed)
5025
{
2✔
5026
    Group g;
2✔
5027
    TableRef table = g.add_table("table");
2✔
5028
    auto col_id = table->add_column(type_Int, "id");
2✔
5029

1✔
5030
    for (int i = 0; i < 100; i++) {
202✔
5031
        table->create_object().set_all(i % 10);
200✔
5032
    }
200✔
5033

1✔
5034
    table->add_search_index(col_id);
2✔
5035
    Query q = table->where().equal(col_id, 1);
2✔
5036
    CHECK_EQUAL(q.count(), 10);
2✔
5037
    auto tv = q.find_all();
2✔
5038
    CHECK_EQUAL(tv.size(), 10);
2✔
5039
}
2✔
5040

5041
TEST(Query_IntIndexedRandom)
5042
{
2✔
5043
    Random random(random_int<int>());
2✔
5044

1✔
5045
    Group g;
2✔
5046
    TableRef table = g.add_table("table");
2✔
5047
    auto col_id = table->add_column(type_Int, "id");
2✔
5048
    auto col_val = table->add_column(type_Int, "val");
2✔
5049

1✔
5050
    for (int i = 0; i < 100000; i++) {
200,002✔
5051
        table->create_object().set(col_id, random.draw_int_max(20)).set(col_val, random.draw_int_max(100));
200,000✔
5052
    }
200,000✔
5053

1✔
5054
    for (const char* str : {"id == 1", "id == 1 and val > 50"}) {
4✔
5055
        table->remove_search_index(col_id);
4✔
5056
        Query q = table->query(str);
4✔
5057
        auto before = std::chrono::steady_clock().now();
4✔
5058
        size_t c1 = q.count();
4✔
5059
        auto after = std::chrono::steady_clock().now();
4✔
5060
        auto count_without_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5061
        before = std::chrono::steady_clock().now();
4✔
5062
        auto tv1 = q.find_all();
4✔
5063
        after = std::chrono::steady_clock().now();
4✔
5064
        auto find_all_without_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5065

2✔
5066
        table->add_search_index(col_id);
4✔
5067
        before = std::chrono::steady_clock().now();
4✔
5068
        size_t c2 = q.count();
4✔
5069
        after = std::chrono::steady_clock().now();
4✔
5070
        auto count_with_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5071
        CHECK_EQUAL(c1, c2);
4✔
5072
        before = std::chrono::steady_clock().now();
4✔
5073
        auto tv2 = q.find_all();
4✔
5074
        after = std::chrono::steady_clock().now();
4✔
5075
        auto find_all_with_index = std::chrono::duration_cast<std::chrono::microseconds>(after - before).count();
4✔
5076
        CHECK_EQUAL(tv1.size(), tv2.size());
4✔
5077
        CHECK_EQUAL(tv1.size(), c1);
4✔
5078

2✔
5079
        /*
2✔
5080
        std::cout << "Query: " << str << std::endl;
2✔
5081
        std::cout << "count without index: " << count_without_index << " us" << std::endl;
2✔
5082
        std::cout << "find all without index: " << find_all_without_index << " us" << std::endl;
2✔
5083
        std::cout << "count with index: " << count_with_index << " us" << std::endl;
2✔
5084
        std::cout << "find all with index: " << find_all_with_index << " us" << std::endl;
2✔
5085
         */
2✔
5086
        static_cast<void>(count_without_index);
4✔
5087
        static_cast<void>(find_all_without_index);
4✔
5088
        static_cast<void>(count_with_index);
4✔
5089
        static_cast<void>(find_all_with_index);
4✔
5090
    }
4✔
5091
}
2✔
5092

5093
TEST(Query_IntFindInNextLeaf)
5094
{
2✔
5095
    Group g;
2✔
5096
    TableRef table = g.add_table("table");
2✔
5097
    auto col_id = table->add_column(type_Int, "id");
2✔
5098

1✔
5099
    // num_misses > MAX_BPNODE_SIZE to check results on other leafs
1✔
5100
    constexpr int num_misses = 1000 * 2 + 10;
2✔
5101
    for (int i = 0; i < num_misses; i++) {
4,022✔
5102
        table->create_object().set(col_id, i % 10);
4,020✔
5103
    }
4,020✔
5104
    table->create_object().set(col_id, 20);
2✔
5105

1✔
5106
    auto check_results = [&]() {
4✔
5107
        for (int i = 0; i < 10; ++i) {
44✔
5108
            Query qi = table->where().equal(col_id, i);
40✔
5109
            CHECK_EQUAL(qi.count(), num_misses / 10);
40✔
5110
        }
40✔
5111
        Query q20 = table->where().equal(col_id, 20);
4✔
5112
        CHECK_EQUAL(q20.count(), 1);
4✔
5113
    };
4✔
5114
    check_results();
2✔
5115
    table->add_search_index(col_id);
2✔
5116
    check_results();
2✔
5117
}
2✔
5118

5119
TEST(Query_IntIndexOverLinkViewNotInTableOrder)
5120
{
2✔
5121
    Group g;
2✔
5122

1✔
5123
    TableRef child_table = g.add_table("child");
2✔
5124
    auto col_child_id = child_table->add_column(type_Int, "id");
2✔
5125
    child_table->add_search_index(col_child_id);
2✔
5126

1✔
5127
    auto k0 = child_table->create_object().set(col_child_id, 3).get_key();
2✔
5128
    auto k1 = child_table->create_object().set(col_child_id, 2).get_key();
2✔
5129

1✔
5130
    TableRef parent_table = g.add_table("parent");
2✔
5131
    auto col_parent_children = parent_table->add_column_list(*child_table, "children");
2✔
5132

1✔
5133
    auto parent_obj = parent_table->create_object();
2✔
5134
    auto children = parent_obj.get_linklist(col_parent_children);
2✔
5135
    // Add in reverse order so that the query node sees declining start indices
1✔
5136
    children.add(k1);
2✔
5137
    children.add(k0);
2✔
5138

1✔
5139
    // Query via linkview
1✔
5140
    Query q = child_table->where(children).equal(col_child_id, 3);
2✔
5141
    // Call find() twice. This caused a memory lead at some point. Must pass a memory leak test.
1✔
5142
    CHECK_EQUAL(k0, q.find());
2✔
5143
    CHECK_EQUAL(k0, q.find());
2✔
5144
    CHECK_EQUAL(k1, child_table->where(children).equal(col_child_id, 2).find());
2✔
5145

1✔
5146
    // Query directly
1✔
5147
    CHECK_EQUAL(k0, child_table->where().equal(col_child_id, 3).find());
2✔
5148
    CHECK_EQUAL(k1, child_table->where().equal(col_child_id, 2).find());
2✔
5149
}
2✔
5150

5151
TEST(Query_MixedTypeQuery)
5152
{
2✔
5153
    Group g;
2✔
5154
    auto table = g.add_table("Foo");
2✔
5155
    auto col_int = table->add_column(type_Int, "int");
2✔
5156
    auto col_double = table->add_column(type_Double, "double");
2✔
5157
    for (int64_t i = 0; i < 100; i++) {
202✔
5158
        table->create_object().set(col_int, i).set(col_double, 100. - i);
200✔
5159
    }
200✔
5160

1✔
5161
    auto tv = (table->column<Int>(col_int) > 9.5).find_all();
2✔
5162
    CHECK_EQUAL(tv.size(), 90);
2✔
5163
    auto tv1 = (table->column<Int>(col_int) > table->column<Double>(col_double)).find_all();
2✔
5164
    CHECK_EQUAL(tv1.size(), 49);
2✔
5165
}
2✔
5166

5167
TEST(Query_LinkListIntPastOneIsNull)
5168
{
2✔
5169
    Group g;
2✔
5170
    auto table_foo = g.add_table("Foo");
2✔
5171
    auto table_bar = g.add_table("Bar");
2✔
5172
    auto col_int = table_foo->add_column(type_Int, "int", true);
2✔
5173
    auto col_list = table_bar->add_column_list(*table_foo, "foo_link");
2✔
5174
    std::vector<util::Optional<int64_t>> values = {{0}, {1}, {2}, {util::none}};
2✔
5175
    auto bar_obj = table_bar->create_object();
2✔
5176
    auto list = bar_obj.get_linklist(col_list);
2✔
5177

1✔
5178
    for (size_t i = 0; i < values.size(); i++) {
10✔
5179
        auto obj = table_foo->create_object();
8✔
5180
        obj.set(col_int, values[i]);
8✔
5181
        list.add(obj.get_key());
8✔
5182
    }
8✔
5183

1✔
5184
    Query q = table_bar->link(col_list).column<Int>(col_int) == realm::null();
2✔
5185

1✔
5186
    CHECK_EQUAL(q.count(), 1);
2✔
5187
}
2✔
5188

5189
TEST(Query_LinkView_StrIndex)
5190
{
2✔
5191
    Group g;
2✔
5192
    auto table_foo = g.add_table_with_primary_key("class_Foo", type_String, "id");
2✔
5193
    auto col_id = table_foo->get_column_key("id");
2✔
5194

1✔
5195
    auto table_bar = g.add_table("class_Bar");
2✔
5196
    auto col_list = table_bar->add_column_list(*table_foo, "link");
2✔
5197

1✔
5198
    auto foo = table_foo->create_object_with_primary_key("97625fdc-d3f8-4c45-9a4d-dc8c83c657a1");
2✔
5199
    auto bar = table_bar->create_object();
2✔
5200
    auto ll = bar.get_linklist(col_list);
2✔
5201
    ll.add(foo.get_key());
2✔
5202

1✔
5203
    auto q = table_foo->where(ll).equal(col_id, "97625fdc-d3f8-4c45-9a4d-dc8c83c657a1");
2✔
5204
    CHECK_EQUAL(q.count(), 1);
2✔
5205
}
2✔
5206

5207
TEST(Query_StringOrShortStrings)
5208
{
2✔
5209
    Group g;
2✔
5210
    TableRef table = g.add_table("table");
2✔
5211
    auto col_value = table->add_column(type_String, "value");
2✔
5212

1✔
5213
    std::string strings[] = {"0", "1", "2"};
2✔
5214
    for (auto& str : strings) {
6✔
5215
        table->create_object().set(col_value, str);
6✔
5216
    }
6✔
5217

1✔
5218
    for (auto& str : strings) {
6✔
5219
        Query q = table->where()
6✔
5220
                      .group()
6✔
5221
                      .equal(col_value, StringData(str))
6✔
5222
                      .Or()
6✔
5223
                      .equal(col_value, StringData("not present"))
6✔
5224
                      .end_group();
6✔
5225
        CHECK_EQUAL(q.count(), 1);
6✔
5226
    }
6✔
5227
}
2✔
5228

5229
TEST(Query_StringOrMediumStrings)
5230
{
2✔
5231
    Group g;
2✔
5232
    TableRef table = g.add_table("table");
2✔
5233
    auto col_value = table->add_column(type_String, "value");
2✔
5234

1✔
5235
    std::string strings[] = {"0", "1", "2"};
2✔
5236
    for (auto& str : strings) {
6✔
5237
        str.resize(16, str[0]); // Make the strings long enough to require ArrayStringLong
6✔
5238
        table->create_object().set(col_value, str);
6✔
5239
    }
6✔
5240

1✔
5241
    for (auto& str : strings) {
6✔
5242
        Query q = table->where()
6✔
5243
                      .group()
6✔
5244
                      .equal(col_value, StringData(str))
6✔
5245
                      .Or()
6✔
5246
                      .equal(col_value, StringData("not present"))
6✔
5247
                      .end_group();
6✔
5248
        CHECK_EQUAL(q.count(), 1);
6✔
5249
    }
6✔
5250
}
2✔
5251

5252
TEST(Query_StringOrLongStrings)
5253
{
2✔
5254
    Group g;
2✔
5255
    TableRef table = g.add_table("table");
2✔
5256
    auto col_value = table->add_column(type_String, "value");
2✔
5257

1✔
5258
    std::string strings[] = {"0", "1", "2"};
2✔
5259
    for (auto& str : strings) {
6✔
5260
        str.resize(64, str[0]); // Make the strings long enough to require ArrayBigBlobs
6✔
5261
        table->create_object().set(col_value, str);
6✔
5262
    }
6✔
5263

1✔
5264
    for (auto& str : strings) {
6✔
5265
        Query q = table->where()
6✔
5266
                      .group()
6✔
5267
                      .equal(col_value, StringData(str))
6✔
5268
                      .Or()
6✔
5269
                      .equal(col_value, StringData("not present"))
6✔
5270
                      .end_group();
6✔
5271
        CHECK_EQUAL(q.count(), 1);
6✔
5272
    }
6✔
5273
}
2✔
5274

5275
TEST(Query_LinkViewAnd)
5276
{
2✔
5277
    Group g;
2✔
5278

1✔
5279
    TableRef child_table = g.add_table("child");
2✔
5280
    auto col_child_id = child_table->add_column(type_Int, "id");
2✔
5281
    auto col_child_name = child_table->add_column(type_String, "name");
2✔
5282

1✔
5283
    auto k0 = child_table->create_object().set(col_child_id, 3).set(col_child_name, "Adam").get_key();
2✔
5284
    auto k1 = child_table->create_object().set(col_child_id, 2).set(col_child_name, "Jeff").get_key();
2✔
5285

1✔
5286
    TableRef parent_table = g.add_table("parent");
2✔
5287
    auto col_parent_children = parent_table->add_column_list(*child_table, "children");
2✔
5288

1✔
5289
    auto parent_obj = parent_table->create_object();
2✔
5290
    auto children = parent_obj.get_linklist(col_parent_children);
2✔
5291
    children.add(k0);
2✔
5292
    children.add(k1);
2✔
5293

1✔
5294
    Query q1 = child_table->where(children).equal(col_child_id, 3);
2✔
5295
    Query q2 = child_table->where(children).equal(col_child_name, "Jeff");
2✔
5296
    CHECK_EQUAL(k0, q1.find());
2✔
5297
    CHECK_EQUAL(k1, q2.find());
2✔
5298
    q1.and_query(q2);
2✔
5299
    CHECK_NOT(q1.find());
2✔
5300
}
2✔
5301

5302
TEST(Query_LinksWithIndex)
5303
{
2✔
5304
    Group g;
2✔
5305

1✔
5306
    TableRef target = g.add_table("target");
2✔
5307
    auto col_value = target->add_column(type_String, "value");
2✔
5308
    auto col_date = target->add_column(type_Timestamp, "date");
2✔
5309
    target->add_search_index(col_value);
2✔
5310
    target->add_search_index(col_date);
2✔
5311

1✔
5312
    TableRef foo = g.add_table("foo");
2✔
5313
    auto col_foo = foo->add_column_list(*target, "linklist");
2✔
5314
    auto col_location = foo->add_column(type_String, "location");
2✔
5315
    auto col_score = foo->add_column(type_Int, "score");
2✔
5316
    foo->add_search_index(col_location);
2✔
5317
    foo->add_search_index(col_score);
2✔
5318

1✔
5319
    TableRef middle = g.add_table("middle");
2✔
5320
    auto col_link = middle->add_column(*target, "link");
2✔
5321

1✔
5322
    TableRef origin = g.add_table("origin");
2✔
5323
    auto col_linklist = origin->add_column_list(*middle, "linklist");
2✔
5324

1✔
5325
    std::vector<StringData> strings{"Copenhagen", "Aarhus", "Odense", "Aalborg", "Faaborg"};
2✔
5326
    auto now = std::chrono::system_clock::now();
2✔
5327
    std::chrono::seconds d{0};
2✔
5328
    for (auto& str : strings) {
10✔
5329
        target->create_object().set(col_value, str).set(col_date, Timestamp(now + d));
10✔
5330
        d = d + std::chrono::seconds{1};
10✔
5331
    }
10✔
5332

1✔
5333
    auto m0 = middle->create_object().set(col_link, target->find_first(col_value, strings[0])).get_key();
2✔
5334
    auto m1 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5335
    auto m2 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5336
    auto m3 = middle->create_object().set(col_link, target->find_first(col_value, strings[2])).get_key();
2✔
5337
    auto m4 = middle->create_object().set(col_link, target->find_first(col_value, strings[3])).get_key();
2✔
5338

1✔
5339
    auto obj0 = origin->create_object();
2✔
5340
    obj0.get_linklist(col_linklist).add(m3);
2✔
5341

1✔
5342
    auto obj1 = origin->create_object();
2✔
5343
    auto ll1 = obj1.get_linklist(col_linklist);
2✔
5344
    ll1.add(m1);
2✔
5345
    ll1.add(m2);
2✔
5346

1✔
5347
    origin->create_object().get_linklist(col_linklist).add(m4);
2✔
5348
    origin->create_object().get_linklist(col_linklist).add(m3);
2✔
5349
    auto obj4 = origin->create_object();
2✔
5350
    obj4.get_linklist(col_linklist).add(m0);
2✔
5351

1✔
5352
    Query q = origin->link(col_linklist).link(col_link).column<String>(col_value) == "Odense";
2✔
5353
    CHECK_EQUAL(q.find(), obj0.get_key());
2✔
5354
    auto tv = q.find_all();
2✔
5355
    CHECK_EQUAL(tv.size(), 3);
2✔
5356

1✔
5357
    auto ll = foo->create_object().set(col_location, "Fyn").set(col_score, 5).get_linklist(col_foo);
2✔
5358
    ll.add(target->find_first(col_value, strings[2]));
2✔
5359
    ll.add(target->find_first(col_value, strings[4]));
2✔
5360

1✔
5361
    Query q1 =
2✔
5362
        origin->link(col_linklist).link(col_link).backlink(*foo, col_foo).column<String>(col_location) == "Fyn";
2✔
5363
    CHECK_EQUAL(q1.find(),  obj0.get_key());
2✔
5364
    Query q2 = origin->link(col_linklist).link(col_link).backlink(*foo, col_foo).column<Int>(col_score) == 5;
2✔
5365
    CHECK_EQUAL(q2.find(),  obj0.get_key());
2✔
5366

1✔
5367
    // Make sure that changes in the table are reflected in the query result
1✔
5368
    middle->get_object(m3).set(col_link, target->find_first(col_value, strings[1]));
2✔
5369
    CHECK_EQUAL(q.find(), obj1.get_key());
2✔
5370

1✔
5371
    q = origin->link(col_linklist).link(col_link).column<Timestamp>(col_date) == Timestamp(now);
2✔
5372
    CHECK_EQUAL(q.find(), obj4.get_key());
2✔
5373
}
2✔
5374

5375
TEST(Query_NotImmediatelyBeforeKnownRange)
5376
{
2✔
5377
    Group g;
2✔
5378
    TableRef parent = g.add_table("parent");
2✔
5379
    TableRef child = g.add_table("child");
2✔
5380
    auto col_link = parent->add_column_list(*child, "list");
2✔
5381
    auto col_str = child->add_column(type_String, "value");
2✔
5382
    child->add_search_index(col_str);
2✔
5383

1✔
5384
    Obj obj = parent->create_object();
2✔
5385
    auto k0 = child->create_object().set(col_str, "a").get_key();
2✔
5386
    auto k1 = child->create_object().set(col_str, "b").get_key();
2✔
5387
    auto list = obj.get_linklist(col_link);
2✔
5388
    list.insert(0, k0);
2✔
5389
    list.insert(0, k1);
2✔
5390

1✔
5391
    Query q = child->where(list).Not().equal(col_str, "a");
2✔
5392
    CHECK_EQUAL(q.count(), 1);
2✔
5393
}
2✔
5394

5395
TEST_TYPES(Query_PrimaryKeySearchForNull, Prop<String>, Prop<Int>, Prop<ObjectId>, Nullable<String>, Nullable<Int>,
5396
           Nullable<ObjectId>)
5397
{
12✔
5398
    using type = typename TEST_TYPE::type;
12✔
5399
    using underlying_type = typename TEST_TYPE::underlying_type;
12✔
5400
    Table table;
12✔
5401
    TestValueGenerator gen;
12✔
5402
    auto col = table.add_column(TEST_TYPE::data_type, "property", TEST_TYPE::is_nullable);
12✔
5403
    table.set_primary_key_column(col);
12✔
5404
    underlying_type v0 = gen.convert_for_test<underlying_type>(42);
12✔
5405
    underlying_type v1 = gen.convert_for_test<underlying_type>(43);
12✔
5406
    Mixed mixed_null;
12✔
5407
    auto obj0 = table.create_object_with_primary_key(v0);
12✔
5408
    auto obj1 = table.create_object_with_primary_key(v1);
12✔
5409

6✔
5410
    auto verify_result_count = [&](Query& q, size_t expected_count) {
24✔
5411
        CHECK_EQUAL(q.count(), expected_count);
24✔
5412
        TableView tv = q.find_all();
24✔
5413
        CHECK_EQUAL(tv.size(), expected_count);
24✔
5414
    };
24✔
5415
    Query q = table.where().equal(col, v0);
12✔
5416
    verify_result_count(q, 1);
12✔
5417
    q = table.where().equal(col, v1);
12✔
5418
    verify_result_count(q, 1);
12✔
5419

6✔
5420
    CHECK_EQUAL(table.find_first(col, v0), obj0.get_key());
12✔
5421
    CHECK_EQUAL(table.find_first(col, v1), obj1.get_key());
12✔
5422
    CHECK_NOT(table.find_first(col, type{}));
12✔
5423
}
12✔
5424

5425
TEST_TYPES(Query_Mixed, std::true_type, std::false_type)
5426
{
4✔
5427
    bool has_index = TEST_TYPE::value;
4✔
5428
    constexpr bool exact_match = true;
4✔
5429
    constexpr bool insensitive_match = false;
4✔
5430
    Group g;
4✔
5431
    auto table = g.add_table("Foo");
4✔
5432
    auto origin = g.add_table("Origin");
4✔
5433
    auto col_any = table->add_column(type_Mixed, "any");
4✔
5434
    auto col_int = table->add_column(type_Int, "int");
4✔
5435
    auto col_link = origin->add_column(*table, "link");
4✔
5436
    auto col_mixed = origin->add_column(type_Mixed, "mixed");
4✔
5437
    auto col_links = origin->add_column_list(*table, "links");
4✔
5438

2✔
5439
    if (has_index)
4✔
5440
        table->add_search_index(col_any);
2✔
5441

2✔
5442
    size_t int_over_50 = 0;
4✔
5443
    size_t nb_strings = 0;
4✔
5444
    for (int64_t i = 0; i < 100; i++) {
404✔
5445
        if (i % 4) {
400✔
5446
            if (i > 50)
300✔
5447
                int_over_50++;
148✔
5448
            table->create_object().set(col_any, Mixed(i)).set(col_int, i);
300✔
5449
        }
300✔
5450
        else {
100✔
5451
            std::string str = "String" + util::to_string(i);
100✔
5452
            table->create_object().set(col_any, Mixed(str)).set(col_int, i);
100✔
5453
            nb_strings++;
100✔
5454
        }
100✔
5455
    }
400✔
5456
    std::string str2bin("String2Binary");
4✔
5457
    table->get_object(15).set(col_any, Mixed());
4✔
5458
    table->get_object(75).set(col_any, Mixed(75.));
4✔
5459
    table->get_object(28).set(col_any, Mixed(BinaryData(str2bin)));
4✔
5460
    table->get_object(25).set(col_any, Mixed(3.));
4✔
5461
    table->get_object(35).set(col_any, Mixed(Decimal128("3")));
4✔
5462
    table->get_object(80).set(col_any, Mixed("abcdefgh"));
4✔
5463
    table->get_object(81).set(col_any, Mixed(int64_t(0x6867666564636261)));
4✔
5464

2✔
5465
    auto it = table->begin();
4✔
5466
    for (int64_t i = 0; i < 10; i++) {
44✔
5467
        auto obj = origin->create_object();
40✔
5468
        auto ll = obj.get_linklist(col_links);
40✔
5469

20✔
5470
        obj.set(col_link, it->get_key());
40✔
5471
        if (i % 3) {
40✔
5472
            obj.set(col_mixed, Mixed(i));
24✔
5473
        }
24✔
5474
        else {
16✔
5475
            obj.set(col_mixed, Mixed(table->begin()->get_link()));
16✔
5476
        }
16✔
5477
        for (int64_t j = 0; j < 10; j++) {
440✔
5478
            ll.add(it->get_key());
400✔
5479
            ++it;
400✔
5480
        }
400✔
5481
    }
40✔
5482

2✔
5483
    // g.to_json(std::cout);
2✔
5484
    auto tv = (table->column<Mixed>(col_any) > Mixed(50)).find_all();
4✔
5485
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5486
    tv = (table->column<Mixed>(col_any) > 50).find_all();
4✔
5487
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5488
    tv = (table->column<Mixed>(col_any) == 37).find_all();
4✔
5489
    CHECK_EQUAL(tv.size(), 1);
4✔
5490
    tv = table->where().equal(col_any, Mixed(37)).find_all();
4✔
5491
    CHECK_EQUAL(tv.size(), 1);
4✔
5492
    tv = (table->column<Mixed>(col_any) >= 50).find_all();
4✔
5493
    CHECK_EQUAL(tv.size(), int_over_50 + 1);
4✔
5494
    tv = (table->column<Mixed>(col_any) <= 50).find_all();
4✔
5495
    CHECK_EQUAL(tv.size(), 100 - int_over_50 - nb_strings - 1);
4✔
5496
    tv = (table->column<Mixed>(col_any) < 50).find_all();
4✔
5497
    CHECK_EQUAL(tv.size(), 100 - int_over_50 - nb_strings - 2);
4✔
5498
    tv = (table->column<Mixed>(col_any) < 50 || table->column<Mixed>(col_any) > 50).find_all();
4✔
5499
    CHECK_EQUAL(tv.size(), 100 - nb_strings - 2);
4✔
5500
    tv = (table->column<Mixed>(col_any) != 50).find_all();
4✔
5501
    CHECK_EQUAL(tv.size(), 99);
4✔
5502

2✔
5503
    tv = table->where().greater(col_any, Mixed(50)).find_all();
4✔
5504
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5505
    tv = table->where().greater(col_any, 50).find_all();
4✔
5506
    CHECK_EQUAL(tv.size(), int_over_50);
4✔
5507

2✔
5508
    tv = table->where().equal(col_any, null()).find_all();
4✔
5509
    CHECK_EQUAL(tv.size(), 1);
4✔
5510
    tv = table->where().not_equal(col_any, null()).find_all();
4✔
5511
    CHECK_EQUAL(tv.size(), 99);
4✔
5512

2✔
5513
    tv = table->where().begins_with(col_any, StringData("String2")).find_all(); // 20, 24, 28
4✔
5514
    CHECK_EQUAL(tv.size(), 3);
4✔
5515
    tv = table->where().begins_with(col_any, BinaryData("String2", 7)).find_all(); // 20, 24, 28
4✔
5516
    CHECK_EQUAL(tv.size(), 3);
4✔
5517
    tv = table->where().begins_with(col_any, Mixed(75.), exact_match).find_all();
4✔
5518
    CHECK_EQUAL(tv.size(), 0);
4✔
5519
    tv = table->where().begins_with(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5520
    CHECK_EQUAL(tv.size(), 0);
4✔
5521

2✔
5522
    tv = table->where().contains(col_any, StringData("TRIN"), insensitive_match).find_all();
4✔
5523
    CHECK_EQUAL(tv.size(), 24);
4✔
5524
    tv = table->where().contains(col_any, Mixed("TRIN"), insensitive_match).find_all();
4✔
5525
    CHECK_EQUAL(tv.size(), 24);
4✔
5526
    tv = table->where().contains(col_any, Mixed("TRIN"), exact_match).find_all();
4✔
5527
    CHECK_EQUAL(tv.size(), 0);
4✔
5528
    tv = table->where().contains(col_any, Mixed(75.), exact_match).find_all();
4✔
5529
    CHECK_EQUAL(tv.size(), 0);
4✔
5530
    tv = table->where().contains(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5531
    CHECK_EQUAL(tv.size(), 0);
4✔
5532

2✔
5533
    tv = table->where().like(col_any, StringData("Strin*")).find_all();
4✔
5534
    CHECK_EQUAL(tv.size(), 24);
4✔
5535
    tv = table->where().like(col_any, Mixed(75.), exact_match).find_all();
4✔
5536
    CHECK_EQUAL(tv.size(), 0);
4✔
5537
    tv = table->where().like(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5538
    CHECK_EQUAL(tv.size(), 0);
4✔
5539

2✔
5540
    tv = table->where().ends_with(col_any, StringData("4")).find_all(); // 4, 24, 44, 64, 84
4✔
5541
    CHECK_EQUAL(tv.size(), 5);
4✔
5542
    char bin[1] = {0x34};
4✔
5543
    tv = table->where().ends_with(col_any, BinaryData(bin)).find_all(); // 4, 24, 44, 64, 84
4✔
5544
    CHECK_EQUAL(tv.size(), 5);
4✔
5545
    tv = table->where().ends_with(col_any, Mixed(75.), exact_match).find_all();
4✔
5546
    CHECK_EQUAL(tv.size(), 0);
4✔
5547
    tv = table->where().ends_with(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5548
    CHECK_EQUAL(tv.size(), 0);
4✔
5549

2✔
5550
    tv = table->where().equal(col_any, "String2Binary", exact_match).find_all();
4✔
5551
    CHECK_EQUAL(tv.size(), 1);
4✔
5552
    tv = table->where().equal(col_any, "string2binary", exact_match).find_all();
4✔
5553
    CHECK_EQUAL(tv.size(), 0);
4✔
5554
    tv = table->where().equal(col_any, "string2binary", insensitive_match).find_all();
4✔
5555
    CHECK_EQUAL(tv.size(), 1);
4✔
5556
    tv = table->where().not_equal(col_any, "string2binary", insensitive_match).find_all();
4✔
5557
    CHECK_EQUAL(tv.size(), 99);
4✔
5558

2✔
5559
    tv = table->where().equal(col_any, StringData(), insensitive_match).find_all();
4✔
5560
    CHECK_EQUAL(tv.size(), 1);
4✔
5561
    tv = table->where().equal(col_any, StringData(), exact_match).find_all();
4✔
5562
    CHECK_EQUAL(tv.size(), 1);
4✔
5563

2✔
5564
    tv = table->where().equal(col_any, Mixed(), insensitive_match).find_all();
4✔
5565
    CHECK_EQUAL(tv.size(), 1);
4✔
5566
    tv = table->where().equal(col_any, Mixed(), exact_match).find_all();
4✔
5567
    CHECK_EQUAL(tv.size(), 1);
4✔
5568

2✔
5569
    tv = table->where().equal(col_any, Mixed(75.), insensitive_match).find_all();
4✔
5570
    CHECK_EQUAL(tv.size(), 1);
4✔
5571
    tv = table->where().equal(col_any, Mixed(75.), exact_match).find_all();
4✔
5572
    CHECK_EQUAL(tv.size(), 1);
4✔
5573

2✔
5574
    tv = (table->column<Mixed>(col_any) == StringData("String48")).find_all();
4✔
5575
    CHECK_EQUAL(tv.size(), 1);
4✔
5576
    tv = (table->column<Mixed>(col_any) == 3.).find_all();
4✔
5577
    CHECK_EQUAL(tv.size(), 3);
4✔
5578
    tv = (table->column<Mixed>(col_any) == table->column<Int>(col_int)).find_all();
4✔
5579
    CHECK_EQUAL(tv.size(), 71);
4✔
5580

2✔
5581
    tv = (table->column<Mixed>(col_any) == StringData("abcdefgh")).find_all();
4✔
5582
    CHECK_EQUAL(tv.size(), 1);
4✔
5583
    tv = (table->column<Mixed>(col_any) == StringData("ABCDEFGH")).find_all();
4✔
5584
    CHECK_EQUAL(tv.size(), 0);
4✔
5585

2✔
5586
    ObjLink link_to_first = table->begin()->get_link();
4✔
5587
    tv = (origin->column<Mixed>(col_mixed) == Mixed(link_to_first)).find_all();
4✔
5588
    CHECK_EQUAL(tv.size(), 4);
4✔
5589
    tv = (origin->where().links_to(col_mixed, link_to_first)).find_all();
4✔
5590
    CHECK_EQUAL(tv.size(), 4);
4✔
5591
    tv = (origin->where().equal(col_link, Mixed(link_to_first))).find_all();
4✔
5592
    CHECK_EQUAL(tv.size(), 1);
4✔
5593
    tv = (origin->where().equal(col_links, Mixed(link_to_first))).find_all();
4✔
5594
    CHECK_EQUAL(tv.size(), 1);
4✔
5595
    auto q = origin->where().not_equal(col_links, Mixed(link_to_first));
4✔
5596
    auto d = q.get_description();
4✔
5597
    tv = q.find_all();
4✔
5598
    CHECK_EQUAL(tv.size(), 10);
4✔
5599
    q = origin->query(d);
4✔
5600
    tv = q.find_all();
4✔
5601
    CHECK_EQUAL(tv.size(), 10);
4✔
5602
    tv = (origin->link(col_links).column<Mixed>(col_any) > 50).find_all();
4✔
5603
    CHECK_EQUAL(tv.size(), 5);
4✔
5604
    tv = (origin->link(col_link).column<Mixed>(col_any) > 50).find_all();
4✔
5605
    CHECK_EQUAL(tv.size(), 2);
4✔
5606
    tv = (origin->link(col_links).column<Mixed>(col_any).contains("string2bin", insensitive_match)).find_all();
4✔
5607
    CHECK_EQUAL(tv.size(), 1);
4✔
5608
    tv = (origin->link(col_links).column<Mixed>(col_any).like("*ring*", insensitive_match)).find_all();
4✔
5609
    CHECK_EQUAL(tv.size(), 10);
4✔
5610
    tv = (origin->link(col_links).column<Mixed>(col_any).begins_with("String", exact_match)).find_all();
4✔
5611
    CHECK_EQUAL(tv.size(), 10);
4✔
5612
    tv = (origin->link(col_links).column<Mixed>(col_any).ends_with("g40", exact_match)).find_all();
4✔
5613
    CHECK_EQUAL(tv.size(), 1);
4✔
5614
}
4✔
5615

5616
TEST(Query_ListOfMixed)
5617
{
2✔
5618
    Group g;
2✔
5619
    auto table = g.add_table("Foo");
2✔
5620
    auto origin = g.add_table("Origin");
2✔
5621
    auto col_any = table->add_column_list(type_Mixed, "any");
2✔
5622
    auto col_int = origin->add_column(type_Int, "int");
2✔
5623
    auto col_link = origin->add_column(*table, "link");
2✔
5624
    auto col_links = origin->add_column_list(*table, "links");
2✔
5625
    size_t expected = 0;
2✔
5626

1✔
5627
    for (int64_t i = 0; i < 100; i++) {
202✔
5628
        auto obj = table->create_object();
200✔
5629
        auto list = obj.get_list<Mixed>(col_any);
200✔
5630
        if (i % 4) {
200✔
5631
            list.add(i);
150✔
5632
            if (i > 50)
150✔
5633
                expected++;
74✔
5634
        }
150✔
5635
        else if ((i % 10) == 0) {
50✔
5636
            list.add(100.);
10✔
5637
            expected++;
10✔
5638
        }
10✔
5639
        if (i % 3) {
200✔
5640
            std::string str = "String" + util::to_string(i);
132✔
5641
            list.add(str);
132✔
5642
        }
132✔
5643
    }
200✔
5644
    auto it = table->begin();
2✔
5645
    for (int64_t i = 0; i < 10; i++) {
22✔
5646
        auto obj = origin->create_object();
20✔
5647
        obj.set(col_int, 100);
20✔
5648
        auto ll = obj.get_linklist(col_links);
20✔
5649

10✔
5650
        obj.set(col_link, it->get_key());
20✔
5651
        for (int64_t j = 0; j < 10; j++) {
220✔
5652
            ll.add(it->get_key());
200✔
5653
            ++it;
200✔
5654
        }
200✔
5655
    }
20✔
5656

1✔
5657
    // g.to_json(std::cout, 2);
1✔
5658
    auto tv = (table->column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5659
    CHECK_EQUAL(tv.size(), expected);
2✔
5660
    tv = (origin->link(col_links).column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5661
    CHECK_EQUAL(tv.size(), 8);
2✔
5662
    tv = (origin->link(col_link).column<Lst<Mixed>>(col_any) > 50).find_all();
2✔
5663
    CHECK_EQUAL(tv.size(), 7);
2✔
5664
    tv = (origin->link(col_links).column<Lst<Mixed>>(col_any) == origin->column<Int>(col_int)).find_all();
2✔
5665
    CHECK_EQUAL(tv.size(), 5);
2✔
5666
}
2✔
5667

5668
TEST(Query_Dictionary)
5669
{
2✔
5670
    Group g;
2✔
5671
    auto foo = g.add_table("foo");
2✔
5672
    auto origin = g.add_table("origin");
2✔
5673
    auto col_dict = foo->add_column_dictionary(type_Mixed, "dict");
2✔
5674
    auto col_link = origin->add_column(*foo, "link");
2✔
5675
    auto col_links = origin->add_column_list(*foo, "links");
2✔
5676
    size_t expected = 0;
2✔
5677

1✔
5678
    for (int64_t i = 0; i < 100; i++) {
202✔
5679
        auto obj = foo->create_object();
200✔
5680
        Dictionary dict = obj.get_dictionary(col_dict);
200✔
5681
        bool incr = false;
200✔
5682
        if (i % 4) {
200✔
5683
            dict.insert("Value", i);
150✔
5684
            if (i > 50)
150✔
5685
                incr = true;
74✔
5686
        }
150✔
5687
        else if ((i % 10) == 0) {
50✔
5688
            dict.insert("Foo", "Bar");
10✔
5689
            dict.insert("Value", 100.);
10✔
5690
            incr = true;
10✔
5691
        }
10✔
5692
        if (i % 3) {
200✔
5693
            std::string str = "String" + util::to_string(i);
132✔
5694
            dict.insert("Value", str);
132✔
5695
            incr = false;
132✔
5696
        }
132✔
5697
        dict.insert("Dummy", i);
200✔
5698
        if (incr) {
200✔
5699
            expected++;
30✔
5700
        }
30✔
5701
    }
200✔
5702

1✔
5703
    auto it = foo->begin();
2✔
5704
    for (int64_t i = 0; i < 10; i++) {
22✔
5705
        auto obj = origin->create_object();
20✔
5706

10✔
5707
        obj.set(col_link, it->get_key());
20✔
5708

10✔
5709
        auto ll = obj.get_linklist(col_links);
20✔
5710
        for (int64_t j = 0; j < 10; j++) {
220✔
5711
            ll.add(it->get_key());
200✔
5712
            ++it;
200✔
5713
        }
200✔
5714
    }
20✔
5715

1✔
5716
    // g.to_json(std::cout);
1✔
5717
    auto tv = (foo->column<Dictionary>(col_dict).key("Value") > Mixed(50)).find_all();
2✔
5718
    CHECK_EQUAL(tv.size(), expected);
2✔
5719
    tv = (foo->column<Dictionary>(col_dict) > 50).find_all(); // Any key will do
2✔
5720
    CHECK_EQUAL(tv.size(), 50);                               // 0 and 51..99
2✔
5721

1✔
5722
    tv = (origin->link(col_link).column<Dictionary>(col_dict).key("Value") > 50).find_all();
2✔
5723
    CHECK_EQUAL(tv.size(), 3);
2✔
5724
    tv = (origin->link(col_links).column<Dictionary>(col_dict).key("Value") > 50).find_all();
2✔
5725
    CHECK_EQUAL(tv.size(), 6);
2✔
5726
    tv = (origin->link(col_links).column<Dictionary>(col_dict) > 50).find_all();
2✔
5727
    CHECK_EQUAL(tv.size(), 6);
2✔
5728
    tv = (origin->link(col_links).column<Dictionary>(col_dict).key("Value") == null()).find_all();
2✔
5729
    CHECK_EQUAL(tv.size(), 7);
2✔
5730

1✔
5731
    tv = (foo->column<Dictionary>(col_dict).keys().begins_with("F")).find_all();
2✔
5732
    CHECK_EQUAL(tv.size(), 5);
2✔
5733
    tv = (origin->link(col_link).column<Dictionary>(col_dict).keys() == "Foo").find_all();
2✔
5734
    CHECK_EQUAL(tv.size(), 5);
2✔
5735
}
2✔
5736

5737
TEST(Query_DictionaryTypedLinks)
5738
{
2✔
5739
    Group g;
2✔
5740
    auto dog = g.add_table("dog");
2✔
5741
    auto cat = g.add_table("cat");
2✔
5742
    auto person = g.add_table("person");
2✔
5743
    auto col_data = person->add_column_dictionary(type_Mixed, "data");
2✔
5744
    auto col_dog_name = dog->add_column(type_String, "Name");
2✔
5745
    auto col_dog_parent = dog->add_column(*dog, "Parent");
2✔
5746
    auto col_cat_name = cat->add_column(type_String, "Name");
2✔
5747

1✔
5748
    auto fido = dog->create_object().set(col_dog_name, "Fido");
2✔
5749
    auto pluto = dog->create_object().set(col_dog_name, "Pluto");
2✔
5750
    pluto.set(col_dog_parent, fido.get_key());
2✔
5751
    dog->create_object().set(col_dog_name, "Vaks");
2✔
5752
    auto marie = cat->create_object().set(col_cat_name, "Marie");
2✔
5753
    cat->create_object().set(col_cat_name, "Berlioz");
2✔
5754
    cat->create_object().set(col_cat_name, "Toulouse");
2✔
5755

1✔
5756
    auto john = person->create_object().get_dictionary(col_data);
2✔
5757
    auto paul = person->create_object().get_dictionary(col_data);
2✔
5758

1✔
5759
    john.insert("Name", "John");
2✔
5760
    john.insert("Pet", pluto);
2✔
5761

1✔
5762
    paul.insert("Name", "Paul");
2✔
5763
    paul.insert("Pet", marie);
2✔
5764

1✔
5765
    // g.to_json(std::cout, 5);
1✔
5766

1✔
5767
    auto cnt = (person->column<Dictionary>(col_data).key("Pet").property("Name") == StringData("Pluto")).count();
2✔
5768
    CHECK_EQUAL(cnt, 1);
2✔
5769
    cnt = (person->column<Dictionary>(col_data).key("Pet").property("Name") == StringData("Marie")).count();
2✔
5770
    CHECK_EQUAL(cnt, 1);
2✔
5771
    cnt = (person->column<Dictionary>(col_data).key("Pet").property("Parent").property("Name") == StringData("Fido"))
2✔
5772
              .count();
2✔
5773
    CHECK_EQUAL(cnt, 1);
2✔
5774
}
2✔
5775

5776
TEST(Query_TypeOfValue)
5777
{
2✔
5778
    Group g;
2✔
5779
    auto table = g.add_table("Foo");
2✔
5780
    auto origin = g.add_table("Origin");
2✔
5781
    auto col_any = table->add_column(type_Mixed, "mixed");
2✔
5782
    auto col_int = table->add_column(type_Int, "int");
2✔
5783
    auto col_primitive_list = table->add_column_list(type_Mixed, "list");
2✔
5784
    auto col_link = origin->add_column(*table, "link");
2✔
5785
    auto col_links = origin->add_column_list(*table, "links");
2✔
5786
    size_t nb_ints = 0;
2✔
5787
    size_t nb_strings = 0;
2✔
5788
    for (int64_t i = 0; i < 100; i++) {
202✔
5789
        if (i % 4) {
200✔
5790
            nb_ints++;
150✔
5791
            table->create_object().set(col_any, Mixed(i)).set(col_int, i);
150✔
5792
        }
150✔
5793
        else {
50✔
5794
            std::string str = "String" + util::to_string(i);
50✔
5795
            table->create_object().set(col_any, Mixed(str)).set(col_int, i);
50✔
5796
            nb_strings++;
50✔
5797
        }
50✔
5798
    }
200✔
5799
    std::string bin_data("String2Binary");
2✔
5800
    table->get_object(15).set(col_any, Mixed());
2✔
5801
    nb_ints--;
2✔
5802
    table->get_object(75).set(col_any, Mixed(75.));
2✔
5803
    nb_ints--;
2✔
5804
    table->get_object(28).set(col_any, Mixed(BinaryData(bin_data)));
2✔
5805
    nb_strings--;
2✔
5806
    table->get_object(25).set(col_any, Mixed(3.));
2✔
5807
    nb_ints--;
2✔
5808
    table->get_object(35).set(col_any, Mixed(Decimal128("3")));
2✔
5809
    nb_ints--;
2✔
5810

1✔
5811
    auto list_0 = table->get_object(0).get_list<Mixed>(col_primitive_list);
2✔
5812
    list_0.add(Mixed{1});
2✔
5813
    list_0.add(Mixed{Decimal128(10)});
2✔
5814
    list_0.add(Mixed{Double{100}});
2✔
5815
    auto list_1 = table->get_object(1).get_list<Mixed>(col_primitive_list);
2✔
5816
    list_1.add(Mixed{std::string("hello")});
2✔
5817
    list_1.add(Mixed{1000});
2✔
5818

1✔
5819
    auto it = table->begin();
2✔
5820
    for (int64_t i = 0; i < 10; i++) {
22✔
5821
        auto obj = origin->create_object();
20✔
5822
        auto ll = obj.get_linklist(col_links);
20✔
5823

10✔
5824
        obj.set(col_link, it->get_key());
20✔
5825
        for (int64_t j = 0; j < 10; j++) {
220✔
5826
            ll.add(it->get_key());
200✔
5827
            ++it;
200✔
5828
        }
200✔
5829
    }
20✔
5830

1✔
5831
    auto tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string("string"))).find_all();
2✔
5832
    CHECK_EQUAL(tv.size(), nb_strings);
2✔
5833
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string("double"))).find_all();
2✔
5834
    CHECK_EQUAL(tv.size(), 2);
2✔
5835
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(std::string("Decimal128"))).find_all();
2✔
5836
    CHECK_EQUAL(tv.size(), 1);
2✔
5837
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(BinaryData(bin_data))).find_all();
2✔
5838
    CHECK_EQUAL(tv.size(), 1);
2✔
5839
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(util::none)).find_all();
2✔
5840
    CHECK_EQUAL(tv.size(), 1);
2✔
5841
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(type_String)).find_all();
2✔
5842
    CHECK_EQUAL(tv.size(), nb_strings);
2✔
5843
    tv = (table->column<Mixed>(col_any).type_of_value() == TypeOfValue(col_int)).find_all();
2✔
5844
    CHECK_EQUAL(tv.size(), nb_ints);
2✔
5845
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(col_int)).find_all();
2✔
5846
    CHECK_EQUAL(tv.size(), 2);
2✔
5847
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(type_Decimal)).find_all();
2✔
5848
    CHECK_EQUAL(tv.size(), 1);
2✔
5849
    tv = (table->column<Lst<Mixed>>(col_primitive_list).type_of_value() == TypeOfValue(type_Int)).find_all();
2✔
5850
    CHECK_EQUAL(tv.size(), 2);
2✔
5851
    tv = (table->column<Lst<Mixed>>(col_primitive_list, ExpressionComparisonType::All).type_of_value() ==
2✔
5852
              TypeOfValue(TypeOfValue::Attribute::Numeric) &&
2✔
5853
          table->column<Lst<Mixed>>(col_primitive_list).size() > 0)
2✔
5854
             .find_all();
2✔
5855
    CHECK_EQUAL(tv.size(), 1);
2✔
5856
}
2✔
5857

5858
TEST(Query_links_to_with_bpnode_split)
5859
{
2✔
5860
    // The bug here is that LinksToNode would read a LinkList as a simple Array
1✔
5861
    // instead of a BPTree. So this only worked when the number of items < REALM_MAX_BPNODE_SIZE
1✔
5862
    Group g;
2✔
5863
    auto table = g.add_table("Foo");
2✔
5864
    auto origin = g.add_table("Origin");
2✔
5865
    auto col_int = table->add_column(type_Int, "int");
2✔
5866
    auto col_link = origin->add_column(*table, "link");
2✔
5867
    auto col_links = origin->add_column_list(*table, "links");
2✔
5868
    constexpr size_t num_items = REALM_MAX_BPNODE_SIZE + 1;
2✔
5869
    for (size_t i = 0; i < num_items; ++i) {
2,004✔
5870
        table->create_object().set(col_int, int64_t(i));
2,002✔
5871
    }
2,002✔
5872
    for (size_t i = 0; i < num_items; ++i) {
2,004✔
5873
        auto obj = origin->create_object();
2,002✔
5874
        auto it_i = table->begin();
2,002✔
5875
        it_i.go(i);
2,002✔
5876
        obj.set(col_link, it_i->get_key());
2,002✔
5877
        auto list = obj.get_linklist(col_links);
2,002✔
5878
        for (auto it = table->begin(); it != table->end(); ++it) {
2,006,004✔
5879
            list.add(it->get_key());
2,004,002✔
5880
        }
2,004,002✔
5881
    }
2,002✔
5882

1✔
5883
    for (auto it = table->begin(); it != table->end(); ++it) {
2,004✔
5884
        Query q = origin->where().links_to(col_links, it->get_key());
2,002✔
5885
        CHECK_EQUAL(q.count(), num_items);
2,002✔
5886
        Query q2 = origin->where().links_to(col_link, it->get_key());
2,002✔
5887
        CHECK_EQUAL(q2.count(), 1);
2,002✔
5888
    }
2,002✔
5889
}
2✔
5890

5891
TEST(Query_FullText)
5892
{
2✔
5893
    Group g;
2✔
5894
    auto table = g.add_table("table");
2✔
5895
    auto col = table->add_column(type_String, "text");
2✔
5896

1✔
5897
    // Add before index creation
1✔
5898
    table->create_object().set(col, " This is a test, with  spaces!");
2✔
5899
    Obj obj2 = table->create_object().set(col, "Ål, ø og 你好世界Æbler"); // "Hello world" should be filtered out
2✔
5900
    Obj obj3 = table->create_object().set(
2✔
5901
        col,
2✔
5902
        "An object database (also object-oriented database management system) is a database management system in "
2✔
5903
        "which information is represented in the form of objects as used in object-oriented programming. Object "
2✔
5904
        "databases are different from relational databases which are table-oriented. Object-relational databases "
2✔
5905
        "are a hybrid of both approaches.");
2✔
5906
    table->create_object().set(
2✔
5907
        col,
2✔
5908
        "Object database management systems grew out of research during the early to mid-1970s into having "
2✔
5909
        "intrinsic database management support for graph-structured objects. The term 'object-oriented database "
2✔
5910
        "system' first appeared around 1985.[4] Notable research projects included Encore-Ob/Server (Brown "
2✔
5911
        "University), EXODUS (University of Wisconsin–Madison), IRIS (Hewlett-Packard), ODE (Bell Labs), ORION "
2✔
5912
        "(Microelectronics and Computer Technology Corporation or MCC), Vodak (GMD-IPSI), and Zeitgeist (Texas "
2✔
5913
        "Instruments). The ORION project had more published papers than any of the other efforts. Won Kim of MCC "
2✔
5914
        "compiled the best of those papers in a book published by The MIT Press.");
2✔
5915
    table->create_object().set(
2✔
5916
        col, "Lilleø er i mange år blevet anvendt til græsning af Askø-bøndernes kreaturer. I 1788 blev en del af "
2✔
5917
             "Askøs gårde flyttet til Lilleø, og tre gårde eksisterer fortsat på øen. Hovederhvervet på Lilleø er i "
2✔
5918
             "dag frugtavl, og der dyrkes især æbler, pærer og blommer.");
2✔
5919

1✔
5920
    // Create the fulltext index
1✔
5921
    table->add_fulltext_index(col);
2✔
5922
    CHECK_EQUAL(table->search_index_type(col), IndexType::Fulltext);
2✔
5923

1✔
5924
    table->create_object().set(col, "Alle elsker John");
2✔
5925
    table->create_object().set(col, "Johns ven kender John godt");
2✔
5926
    table->create_object().set(col, "Ich wohne in Großarl");
2✔
5927
    table->create_object().set(col, "A short story about a dog running after two cats");
2✔
5928

1✔
5929
    auto tv = table->where().fulltext(col, "object").find_all();
2✔
5930
    CHECK_EQUAL(2, tv.size());
2✔
5931

1✔
5932
    // Add after index creation
1✔
5933
    auto k5 =
2✔
5934
        table->create_object()
2✔
5935
            .set(
2✔
5936
                col,
2✔
5937
                "Early commercial products included Gemstone (Servio Logic, name changed to GemStone Systems), Gbase "
2✔
5938
                "(Graphael), and Vbase (Ontologic). The early to mid-1990s saw additional commercial products enter "
2✔
5939
                "the market. These included ITASCA (Itasca Systems), Jasmine (Fujitsu, marketed by Computer "
2✔
5940
                "Associates), Matisse (Matisse Software), Objectivity/DB (Objectivity, Inc.), ObjectStore (Progress "
2✔
5941
                "Software, acquired from eXcelon which was originally Object Design), ONTOS (Ontos, Inc., name "
2✔
5942
                "changed from Ontologic), O2[6] (O2 Technology, merged with several companies, acquired by Informix, "
2✔
5943
                "which was in turn acquired by IBM), POET (now FastObjects from Versant which acquired Poet Software)"
2✔
5944
                ", Versant Object Database (Versant Corporation), VOSS (Logic Arts) and JADE (Jade Software "
2✔
5945
                "Corporation). Some of these products remain on the market and have been joined by new open source "
2✔
5946
                "and commercial products such as InterSystems Caché.")
2✔
5947
            .get_key();
2✔
5948

1✔
5949
    tv.sync_if_needed();
2✔
5950
    CHECK_EQUAL(3, tv.size());
2✔
5951

1✔
5952
    // Add another
1✔
5953
    table->create_object().set(
2✔
5954
        col, "As the usage of web-based technology increases with the implementation of Intranets and extranets, "
2✔
5955
             "companies have a vested interest in OODBMSs to display their complex data. Using a DBMS that has been "
2✔
5956
             "specifically designed to store data as objects gives an advantage to those companies that are geared "
2✔
5957
             "towards multimedia presentation or organizations that utilize computer-aided design (CAD).[3]");
2✔
5958

1✔
5959
    tv.sync_if_needed();
2✔
5960
    CHECK_EQUAL(3, tv.size());
2✔
5961

1✔
5962
    // Delete one
1✔
5963
    table->remove_object(k5);
2✔
5964
    tv.sync_if_needed();
2✔
5965
    CHECK_EQUAL(2, tv.size());
2✔
5966

1✔
5967
    tv = table->where().fulltext(col, "hybrid").find_all();
2✔
5968
    CHECK_EQUAL(1, tv.size());
2✔
5969

1✔
5970
    // Change value in place
1✔
5971
    obj3.set(
2✔
5972
        col,
2✔
5973
        "Object database management systems added the concept of persistence to object programming languages. The "
2✔
5974
        "early commercial products were integrated with various languages: GemStone (Smalltalk), Gbase (LISP), Vbase "
2✔
5975
        "(COP) and VOSS (Virtual Object Storage System for Smalltalk). For much of the 1990s, C++ dominated the "
2✔
5976
        "commercial object database management market. Vendors added Java in the late 1990s and more recently, C#.");
2✔
5977

1✔
5978
    tv = table->where().fulltext(col, "hybrid").find_all();
2✔
5979
    CHECK_EQUAL(0, tv.size());
2✔
5980

1✔
5981
    tv = table->where().fulltext(col, "Gemstone").find_all();
2✔
5982
    CHECK_EQUAL(1, tv.size());
2✔
5983

1✔
5984
    tv = table->where().fulltext(col, "æbler").find_all();
2✔
5985
    CHECK_EQUAL(2, tv.size());
2✔
5986

1✔
5987
    table->create_object().set(
2✔
5988
        col, "The song \"Supercalifragilisticexpialidocious\" is from the 1964 Disney musical film \"Mary Poppins\"");
2✔
5989

1✔
5990
    tv = table->where().fulltext(col, "supercalifragilisticexpialidocious mary").find_all();
2✔
5991
    CHECK_EQUAL(1, tv.size());
2✔
5992

1✔
5993
    obj2.remove();
2✔
5994
    tv.sync_if_needed();
2✔
5995
    CHECK_EQUAL(1, tv.size());
2✔
5996

1✔
5997
    tv = table->where().fulltext(col, "Johns").find_all();
2✔
5998
    CHECK_EQUAL(1, tv.size());
2✔
5999
    tv = table->where().fulltext(col, "John").find_all();
2✔
6000
    CHECK_EQUAL(2, tv.size());
2✔
6001
    tv = table->where().fulltext(col, "Großarl").find_all();
2✔
6002
    CHECK_EQUAL(1, tv.size());
2✔
6003
    tv = table->where().fulltext(col, "catssadasdsa").find_all();
2✔
6004
    CHECK_EQUAL(0, tv.size());
2✔
6005

1✔
6006
    table->clear();
2✔
6007
    CHECK(table->get_search_index(col)->is_empty());
2✔
6008
}
2✔
6009

6010
TEST(Query_FullTextMulti)
6011
{
2✔
6012
    Group g;
2✔
6013
    auto table = g.add_table("table");
2✔
6014
    auto origin = g.add_table_with_primary_key("origin", type_Int, "id");
2✔
6015
    auto col_link = origin->add_column_list(*table, "link");
2✔
6016
    auto col = table->add_column(type_String, "text");
2✔
6017
    table->add_fulltext_index(col);
2✔
6018

1✔
6019
    table->create_object().set(
2✔
6020
        col,
2✔
6021
        "An object database (also object-oriented database management system) is a database management system in "
2✔
6022
        "which information is represented in the form of objects as used in object-oriented programming. Object "
2✔
6023
        "databases are different from relational databases which are table-oriented. Object-relational databases "
2✔
6024
        "are a hybrid of both approaches.");
2✔
6025
    table->create_object().set(
2✔
6026
        col,
2✔
6027
        "Object database management systems grew out of research during the early to mid-1970s into having "
2✔
6028
        "intrinsic database management support for graph-structured objects. The term 'object-oriented database "
2✔
6029
        "system' first appeared around 1985.[4] Notable research projects included Encore-Ob/Server (Brown "
2✔
6030
        "University), EXODUS (University of Wisconsin–Madison), IRIS (Hewlett-Packard), ODE (Bell Labs), ORION "
2✔
6031
        "(Microelectronics and Computer Technology Corporation or MCC), Vodak (GMD-IPSI), and Zeitgeist (Texas "
2✔
6032
        "Instruments). The ORION project had more published papers than any of the other efforts. Won Kim of MCC "
2✔
6033
        "compiled the best of those papers in a book published by The MIT Press.");
2✔
6034
    table->create_object().set(
2✔
6035
        col,
2✔
6036
        "Early commercial products included Gemstone (Servio Logic, name changed to GemStone Systems), Gbase "
2✔
6037
        "(Graphael), and Vbase (Ontologic). The early to mid-1990s saw additional commercial products enter the "
2✔
6038
        "market. These included ITASCA (Itasca Systems), Jasmine (Fujitsu, marketed by Computer Associates), Matisse "
2✔
6039
        "(Matisse Software), Objectivity/DB (Objectivity, Inc.), ObjectStore (Progress Software, acquired from "
2✔
6040
        "eXcelon which was originally Object Design), ONTOS (Ontos, Inc., name changed from Ontologic), O2[6] (O2 "
2✔
6041
        "Technology, merged with several companies, acquired by Informix, which was in turn acquired by IBM), POET "
2✔
6042
        "(now FastObjects from Versant which acquired Poet Software), Versant Object Database (Versant Corporation), "
2✔
6043
        "VOSS (Logic Arts) and JADE (Jade Software Corporation). Some of these products remain on the market and "
2✔
6044
        "have been joined by new open source and commercial products such as InterSystems Caché.");
2✔
6045
    table->create_object().set(
2✔
6046
        col, "As the usage of web-based technology increases with the implementation of Intranets and extranets, "
2✔
6047
             "companies have a vested interest in OODBMSs to display their complex data. Using a DBMS that has been "
2✔
6048
             "specifically designed to store data as objects gives an advantage to those companies that are geared "
2✔
6049
             "towards multimedia presentation or organizations that utilize computer-aided design (CAD).[3]");
2✔
6050
    table->create_object().set(
2✔
6051
        col,
2✔
6052
        "Object database management systems added the concept of persistence to object programming languages. The "
2✔
6053
        "early commercial products were integrated with various languages: GemStone (Smalltalk), Gbase (LISP), Vbase "
2✔
6054
        "(COP) and VOSS (Virtual Object Storage System for Smalltalk). For much of the 1990s, C++ dominated the "
2✔
6055
        "commercial object database management market. Vendors added Java in the late 1990s and more recently, C#.");
2✔
6056

1✔
6057
    table->create_object().set(
2✔
6058
        col, "L’archive ouverte pluridisciplinaire HAL, est destinée au dépôt et à la diffusion de documents "
2✔
6059
             "scientifiques de niveau recherche, publiés ou non, émanant des établissements d’enseignement et de "
2✔
6060
             "recherche français ou étrangers, des laboratoires publics ou privés.");
2✔
6061
    table->create_object().set(col, "object object object object object duplicates");
2✔
6062
    table->create_object().set(col, "one two three");
2✔
6063
    table->create_object().set(col, "three two one");
2✔
6064
    table->create_object().set(col, "two one");
2✔
6065

1✔
6066
    // object:              0, 1, 2, 4, 6
1✔
6067
    // objects:             0, 1, 3
1✔
6068
    // 'object-oriented':   0, 1
1✔
6069
    // 'table-oriented':    0
1✔
6070
    // oriented:            0, 1
1✔
6071
    // gemstone:            2, 4
1✔
6072
    // data:                3
1✔
6073
    // depot:               5
1✔
6074
    // emanant:             5
1✔
6075
    // database:            0, 1, 2, 4
1✔
6076
    // databases:           0
1✔
6077
    // duplicates:          6
1✔
6078

1✔
6079
    int64_t id = 1000;
2✔
6080
    for (auto& o : *table) {
20✔
6081
        auto ll = origin->create_object_with_primary_key(id++).get_linklist(col_link);
20✔
6082
        ll.add(o.get_key());
20✔
6083
    }
20✔
6084

1✔
6085
    typedef std::vector<int64_t> Keys;
2✔
6086
    auto get_keys = [&](const TableView& tv) -> Keys {
54✔
6087
        std::vector<int64_t> keys(tv.size());
54✔
6088
        for (size_t i = 0; i < tv.size(); ++i)
164✔
6089
            keys[i] = tv.get_key(i).value;
110✔
6090
        return keys;
54✔
6091
    };
54✔
6092
    auto do_fulltext_find = [&](StringData term) -> Keys {
64✔
6093
        return get_keys(table->where().fulltext(col, term).find_all());
64✔
6094
    };
64✔
6095
    auto do_query_find = [&](const TableRef& table, StringData query) -> Keys {
4✔
6096
        return get_keys(table->query(query).find_all());
4✔
6097
    };
4✔
6098

1✔
6099
    CHECK_THROW_ANY(do_fulltext_find(""));
2✔
6100

1✔
6101
    // search with multiple terms
1✔
6102
    CHECK_EQUAL(do_fulltext_find("ONE THREE"), Keys({7, 8}));
2✔
6103
    CHECK_EQUAL(do_fulltext_find("three one"), Keys({7, 8}));
2✔
6104
    CHECK_EQUAL(do_fulltext_find("1990s"), Keys({2, 4}));
2✔
6105
    CHECK_EQUAL(do_fulltext_find("1990s c++"), Keys({4}));
2✔
6106
    CHECK_EQUAL(do_fulltext_find("object gemstone"), Keys({2, 4}));
2✔
6107

1✔
6108
    // over links
1✔
6109
    CHECK_EQUAL(do_query_find(origin, "link.text TEXT 'object gemstone'"), Keys({2, 4}));
2✔
6110
    auto tv = origin->link(col_link).column<String>(col).fulltext("object gemstone").find_all();
2✔
6111
    CHECK_EQUAL(get_keys(tv), Keys({2, 4}));
2✔
6112

1✔
6113
    // through LnkLst
1✔
6114
    auto obj = tv.get_object(0);
2✔
6115
    auto ll = obj.get_linklist(col_link);
2✔
6116
    tv = table->where(ll).fulltext(col, "object gemstone").find_all();
2✔
6117
    CHECK_EQUAL(get_keys(tv), Keys({2}));
2✔
6118

1✔
6119
    // Diacritics ignorant
1✔
6120
    CHECK_EQUAL(do_fulltext_find("depot emanant archive"), Keys({5}));
2✔
6121

1✔
6122
    // search for combination that is not present
1✔
6123
    CHECK_EQUAL(do_fulltext_find("object data"), Keys());
2✔
6124

1✔
6125
    // Prefix
1✔
6126
    CHECK_EQUAL(do_fulltext_find("manage*"), Keys({0, 1, 4}));
2✔
6127
    CHECK_EQUAL(do_fulltext_find("manage* virtu*"), Keys({4}));
2✔
6128

1✔
6129
    // exclude words
1✔
6130
    CHECK_EQUAL(do_fulltext_find("-three one"), Keys({9}));
2✔
6131
    CHECK_EQUAL(do_fulltext_find("one -three"), Keys({9}));
2✔
6132
    CHECK_EQUAL(do_fulltext_find("object -databases"), Keys({1, 2, 4, 6}));
2✔
6133
    CHECK_EQUAL(do_fulltext_find("-databases object -duplicates"), Keys({1, 2, 4}));
2✔
6134
    CHECK_EQUAL(do_fulltext_find("object -objects"), Keys({2, 4, 6}));
2✔
6135
    CHECK_EQUAL(do_fulltext_find("-object objects"), Keys({3}));
2✔
6136
    CHECK_EQUAL(do_fulltext_find("databases -database"), Keys({}));
2✔
6137
    CHECK_EQUAL(do_fulltext_find("-database databases"), Keys({}));
2✔
6138
    CHECK_EQUAL(do_fulltext_find("database -databases"), Keys({1, 2, 4}));
2✔
6139
    CHECK_EQUAL(do_fulltext_find("-databases database"), Keys({1, 2, 4}));
2✔
6140
    CHECK_EQUAL(do_fulltext_find("-database"), Keys({3, 5, 6, 7, 8, 9}));
2✔
6141
    CHECK_EQUAL(do_fulltext_find("-object"), Keys({3, 5, 7, 8, 9}));
2✔
6142
    CHECK_EQUAL(do_fulltext_find("-object -objects"), Keys({5, 7, 8, 9}));
2✔
6143

1✔
6144
    // Don't include and exclude same token
1✔
6145
    CHECK_THROW_ANY(do_fulltext_find("C# -c++")); // Will both end up as 'c'
2✔
6146
    CHECK_THROW_ANY(do_fulltext_find("-object object"));
2✔
6147
    CHECK_THROW_ANY(do_fulltext_find("object -object"));
2✔
6148
    CHECK_THROW_ANY(do_fulltext_find("objects -object object"));
2✔
6149
    CHECK_THROW_ANY(do_fulltext_find("object -object object"));
2✔
6150
    CHECK_THROW_ANY(do_fulltext_find("database -database"));
2✔
6151

1✔
6152
    // many terms
1✔
6153
    CHECK_EQUAL(do_fulltext_find("object database management brown"), Keys({1}));
2✔
6154
    CHECK_EQUAL(do_query_find(table, "text TEXT 'object database management brown'"), Keys({1}));
2✔
6155

1✔
6156
    // non alphanum characters not allowed inside seach token
1✔
6157
    CHECK_THROW_ANY(do_fulltext_find("object-oriented -database"));
2✔
6158
    CHECK_THROW_ANY(do_fulltext_find("object-oriented -table-oriented"));
2✔
6159

1✔
6160
    while (table->size() > 0) {
22✔
6161
        table->begin()->remove();
20✔
6162
    }
20✔
6163

1✔
6164
    CHECK(table->get_search_index(col)->is_empty());
2✔
6165
}
2✔
6166

6167
TEST(Query_FullTextPrefix)
6168
{
2✔
6169
    Group g;
2✔
6170
    auto table = g.add_table("table");
2✔
6171
    auto col = table->add_column(type_String, "text");
2✔
6172
    table->add_fulltext_index(col);
2✔
6173

1✔
6174
    table->create_object().set(col, "Abby Abba Ada Adalee Baylee Bellamy Blaire Adalyn");
2✔
6175
    table->create_object().set(col, "Abigail Abba Barbara Beatrice Bella Blair Blake");
2✔
6176
    table->create_object().set(col, "Adaline Bellamy Blakely");
2✔
6177

1✔
6178
    // table->get_search_index(col)->do_dump_node_structure(std::cout, 0);
1✔
6179

1✔
6180
    auto q = table->query("text TEXT 'ab*'");
2✔
6181
    CHECK_EQUAL(q.count(), 2);
2✔
6182
    q = table->query("text TEXT 'Bel*'");
2✔
6183
    CHECK_EQUAL(q.count(), 3);
2✔
6184
    q = table->query("text TEXT 'Blak*'");
2✔
6185
    CHECK_EQUAL(q.count(), 2);
2✔
6186
    q = table->query("text TEXT 'Bellam*'");
2✔
6187
    CHECK_EQUAL(q.count(), 2);
2✔
6188
    q = table->query("text TEXT 'Bel* Abba -Ada'");
2✔
6189
    CHECK_EQUAL(q.count(), 1);
2✔
6190
}
2✔
6191
#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