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

realm / realm-core / github_pull_request_275914

25 Sep 2023 03:10PM UTC coverage: 92.915% (+1.7%) from 91.215%
github_pull_request_275914

Pull #6073

Evergreen

jedelbo
Merge tag 'v13.21.0' into next-major

"Feature/Bugfix release"
Pull Request #6073: Merge next-major

96928 of 177706 branches covered (0.0%)

8324 of 8714 new or added lines in 122 files covered. (95.52%)

181 existing lines in 28 files now uncovered.

247505 of 266379 relevant lines covered (92.91%)

7164945.17 hits per line

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

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

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

22
#include <algorithm>
23
#include <limits>
24
#include <string>
25
#include <fstream>
26
#include <ostream>
27
#include <chrono>
28

29
#include <realm.hpp>
30
#include <external/json/json.hpp>
31

32
#include "util/misc.hpp"
33

34
#include "test.hpp"
35

36
using namespace realm;
37
using namespace realm::util;
38
using namespace realm::test_util;
39

40

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

70
namespace {
71

72
const bool generate_all = false;
73

74
// After modifying json methods in core, set above generate_all = true to
75
// make the unit tests output their results to files. Then inspect the
76
// files manually to see if the json is correct.
77
//
78
// Finally set generate_all = false and commit them to github which will
79
// make all successive runs compare their produced json with these files
80
//
81
// All produced json is automatically checked for syntax regardless of
82
// the setting of generate_all. This is done using the 'nlohmann::json' parser.
83

84
void setup_multi_table(Table& table, size_t rows)
85
{
6✔
86
    // Create table with all column types
3✔
87
    table.add_column(type_Int, "int");                                     //  0
6✔
88
    table.add_column(type_Bool, "bool");                                   //  1
6✔
89
    table.add_column(type_Timestamp, "date");                              //  2
6✔
90
    table.add_column(type_Float, "float");                                 //  3
6✔
91
    table.add_column(type_Double, "double");                               //  4
6✔
92
    table.add_column(type_String, "string");                               //  5
6✔
93
    table.add_column(type_String, "string_long");                          //  6
6✔
94
    ColKey col_string_big = table.add_column(type_String, "string_big_blobs"); //  7
6✔
95
    ColKey col_string_enum = table.add_column(type_String, "string_enum");     //  8 - becomes StringEnumColumn
6✔
96
    ColKey col_binary = table.add_column(type_Binary, "binary");               //  9
6✔
97
    ColKey col_oid = table.add_column(type_ObjectId, "oid");                   //  10
6✔
98
    ColKey col_decimal = table.add_column(type_Decimal, "decimal");            //  11
6✔
99
    ColKey col_int_list = table.add_column_list(type_Int, "integers");         //  12
6✔
100
    ColKey col_string_list = table.add_column_list(type_String, "strings");    //  13
6✔
101
    ColKey col_dict = table.add_column_dictionary(type_Int, "dictionary");     //  14
6✔
102
    ColKey col_set = table.add_column_set(type_Int, "set");                    //  15
6✔
103
    ColKey col_uuid = table.add_column(type_UUID, "uuid");                     //  16
6✔
104

3✔
105

3✔
106
    std::vector<std::string> strings;
6✔
107
    for (size_t i = 0; i < rows; ++i) {
96✔
108
        std::stringstream out;
90✔
109
        out << "string" << i;
90✔
110
        strings.push_back(out.str());
90✔
111
    }
90✔
112
    for (size_t i = 0; i < rows; ++i) {
96✔
113
        int64_t sign = (i % 2 == 0) ? 1 : -1;
69✔
114
        std::string long_str = strings[i] + " very long string.........";
90✔
115
        auto obj = table.create_object().set_all(int64_t(i * sign), (i % 2 ? true : false), Timestamp{12345, 0},
69✔
116
                                                 123.456f * sign, 9876.54321 * sign, strings[i], long_str);
90✔
117
        switch (i % 2) {
90✔
118
            case 0: {
48✔
119
                std::string s = strings[i];
48✔
120
                s += " very long string.........";
48✔
121
                for (int j = 0; j != 4; ++j)
240✔
122
                    s += " big blobs big blobs big blobs"; // +30
192✔
123
                obj.set(col_string_big, s);
48✔
124
                break;
48✔
125
            }
×
126
            case 1:
42✔
127
                obj.set(col_string_big, "");
42✔
128
                break;
42✔
129
        }
90✔
130
        switch (i % 3) {
90✔
131
            case 0:
30✔
132
                obj.set(col_string_enum, "enum1");
30✔
133
                break;
30✔
134
            case 1:
30✔
135
                obj.set(col_string_enum, "enum2");
30✔
136
                break;
30✔
137
            case 2:
30✔
138
                obj.set(col_string_enum, "enum3");
30✔
139
                break;
30✔
140
        }
90✔
141
        obj.set(col_binary, BinaryData("binary", 7));
90✔
142
        obj.set(col_oid, ObjectId());
90✔
143
        obj.set(col_decimal, Decimal128("1.2345"));
90✔
144
        obj.set(col_uuid, UUID());
90✔
145
        auto int_list = obj.get_list<Int>(col_int_list);
90✔
146
        auto str_list = obj.get_list<String>(col_string_list);
90✔
147
        for (size_t n = 0; n < i % 5; n++) {
270✔
148
            int64_t val = -123 + i * n * 1234 * sign;
180✔
149
            std::string str = "sub_" + util::to_string(val);
180✔
150
            int_list.add(val);
180✔
151
            str_list.add(str);
180✔
152
        }
180✔
153

45✔
154
        auto dict = obj.get_dictionary(col_dict);
90✔
155
        dict.insert("a", 2);
90✔
156

45✔
157
        auto set = obj.get_set<Int>(col_set);
90✔
158
        set.insert(123);
90✔
159
    }
90✔
160

3✔
161
    // We also want a StringEnumColumn
3✔
162
    table.enumerate_string_column(col_string_enum);
6✔
163
}
6✔
164

165
bool json_test(std::string json, std::string expected_file, bool generate)
166
{
102✔
167
    // std::cout << json << std::endl;
51✔
168
    std::string file_name = get_test_resource_path();
102✔
169
    file_name += expected_file + ".json";
102✔
170

51✔
171
    auto j = nlohmann::json::parse(json);
102✔
172

51✔
173
    if (generate) {
102✔
174
        // Generate the testdata to compare. After doing this,
175
        // verify that the output is correct with a json validator:
176
        // http://jsonformatter.curiousconcept.com/
177
        std::ofstream test_file(file_name.c_str(), std::ios::out | std::ios::binary);
×
178
        test_file << j;
×
179
        std::cerr << "\n----------------------------------------\n";
×
180
        std::cerr << "Generated " << expected_file << ":\n";
×
181
        std::cerr << json << "\n----------------------------------------\n";
×
182
        return true;
×
183
    }
×
184
    else {
102✔
185
        nlohmann::json expected;
102✔
186
        std::ifstream test_file(file_name.c_str(), std::ios::in | std::ios::binary);
102✔
187

51✔
188
        // fixme, find a way to use CHECK from a function
51✔
189
        if (!test_file.good())
102✔
190
            return false;
×
191
        if (test_file.fail())
102✔
192
            return false;
×
193
        test_file >> expected;
102✔
194
        if (j != expected) {
102✔
NEW
195
            std::cout << json << std::endl;
×
NEW
196
            std::cout << expected << std::endl;
×
NEW
197
            std::string file_name = get_test_resource_path();
×
NEW
198
            std::string path = file_name + "bad_" + expected_file + ".json";
×
199
            std::string pathOld = "bad_" + file_name;
×
200
            File out(path, File::mode_Write);
×
201
            out.write(json);
×
202
            std::cerr << "\n error result in '" << std::string(path) << "'\n";
×
203
            return false;
×
204
        }
×
205
        return true;
51✔
206
    }
51✔
207
}
102✔
208

51✔
209

51✔
210
std::map<std::string, std::string> no_renames;
211

212
TEST(Json_NoLinks)
213
{
1✔
214
    Table table;
1✔
215
    setup_multi_table(table, 15);
2✔
216

2✔
217
    std::stringstream ss;
2✔
218
    table.to_json(ss, 0, no_renames);
1✔
219
    CHECK(json_test(ss.str(), "expect_json", generate_all));
2✔
220
    return;
2✔
221
}
2✔
222

1✔
223
TEST(Xjson_NoLinks)
1✔
224
{
1✔
225
    Table table;
1✔
226
    setup_multi_table(table, 15);
2✔
227

2✔
228
    std::stringstream ss;
2✔
229
    table.to_json(ss, 0, no_renames, output_mode_xjson);
1✔
230

2✔
231
    CHECK(json_test(ss.str(), "expect_xjson", generate_all));
2✔
232
    return;
1✔
233
}
2✔
234

1✔
235
TEST(Xjson_Plus_NoLinks)
1✔
236
{
1✔
237
    Table table;
1✔
238
    setup_multi_table(table, 15);
2✔
239

2✔
240
    std::stringstream ss;
2✔
241
    table.to_json(ss, 0, no_renames, output_mode_xjson_plus);
1✔
242

2✔
243
    CHECK(json_test(ss.str(), "expect_xjson_plus", generate_all));
2✔
244
    return;
1✔
245
}
2✔
246

1✔
247
/*
1✔
248
For tables with links, the link_depth argument in to_json() means following:
249

250
link_depth = -1:
251
    Follow links to infinite depth, but only follow each link exactly once. Not suitable if cycles exist because they
252
    make it complex to find out what link is being followed for a table that has multiple outgoing links
253

254
link_depth >= 0:
255
    Follow all possible permitations of link paths that are at most link_depth links deep. A link can be taken any
256
    number if times.
257

258
A link which isn't followed (bottom of link_depth has been met, or link has already been followed with
259
    link_depth = -1) is printed as a simple sequence of integers of row indexes in the link column.
260
*/
261
TEST(Json_LinkList1)
262
{
1✔
263
    // Basic non-cyclic LinkList test that also tests column and table renaming
1✔
264
    Group group;
2✔
265

1✔
266
    TableRef table1 = group.add_table("table1");
2✔
267
    TableRef table2 = group.add_table("table2");
1✔
268
    TableRef table3 = group.add_table("table3");
2✔
269

2✔
270
    // add some more columns to table1 and table2
2✔
271
    table1->add_column(type_Int, "col1");
1✔
272
    table1->add_column(type_String, "str1");
1✔
273

2✔
274
    table2->add_column(type_Int, "col1");
2✔
275
    table2->add_column(type_String, "str2");
1✔
276

2✔
277
    table3->add_column(type_Int, "col1");
2✔
278
    table3->add_column(type_String, "str2");
1✔
279

2✔
280
    // add some rows
2✔
281
    auto obj0 = table1->create_object().set_all(100, "foo");
1✔
282
    auto obj1 = table1->create_object().set_all(200, "!");
1✔
283
    table1->create_object().set_all(300, "bar");
2✔
284

2✔
285
    table2->create_object().set_all(400, "hello");
2✔
286
    auto k21 = table2->create_object().set_all(500, "world").get_key();
1✔
287
    auto k22 = table2->create_object().set_all(600, "!").get_key();
2✔
288

2✔
289
    auto k30 = table3->create_object().set_all(700, "baz").get_key();
2✔
290
    table3->create_object().set_all(800, "test");
1✔
291
    auto k32 = table3->create_object().set_all(900, "hi").get_key();
2✔
292

2✔
293
    ColKey col_link2 = table1->add_column_list(*table2, "linkA");
2✔
294
    ColKey col_link3 = table1->add_column_list(*table3, "linkB");
1✔
295

2✔
296
    // set some links
2✔
297
    auto ll0 = obj0.get_linklist(col_link2); // Links to table 2
1✔
298
    ll0.add(k21);
1✔
299

2✔
300
    auto ll1 = obj1.get_linklist(col_link2); // Links to table 2
2✔
301
    ll1.add(k21);
1✔
302
    ll1.add(k22);
2✔
303

2✔
304
    auto ll2 = obj0.get_linklist(col_link3); // Links to table 3
2✔
305
    ll2.add(k30);
1✔
306
    ll2.add(k32);
2✔
307

2✔
308
    std::stringstream ss;
2✔
309

1✔
310
    // Now try different link_depth arguments
2✔
311
    table1->to_json(ss, 0, no_renames);
1✔
312
    CHECK(json_test(ss.str(), "expected_json_linklist1_1", generate_all));
1✔
313

2✔
314
    ss.str("");
2✔
315
    table1->to_json(ss, -1, no_renames);
1✔
316
    CHECK(json_test(ss.str(), "expected_json_linklist1_2", generate_all));
2✔
317

2✔
318
    ss.str("");
2✔
319
    table1->to_json(ss, 0, no_renames);
1✔
320
    CHECK(json_test(ss.str(), "expected_json_linklist1_3", generate_all));
2✔
321

2✔
322
    ss.str("");
2✔
323
    table1->to_json(ss, 1, no_renames);
1✔
324
    CHECK(json_test(ss.str(), "expected_json_linklist1_4", generate_all));
2✔
325

2✔
326
    ss.str("");
2✔
327
    table1->to_json(ss, 2, no_renames);
1✔
328
    CHECK(json_test(ss.str(), "expected_json_linklist1_5", generate_all));
2✔
329

2✔
330
    // Column and table renaming
2✔
331
    std::map<std::string, std::string> m;
1✔
332
    m["str1"] = "STR1";
1✔
333
    m["linkA"] = "LINKA";
2✔
334
    m["table1"] = "TABLE1";
2✔
335
    ss.str("");
2✔
336
    table1->to_json(ss, 2, m);
2✔
337
    CHECK(json_test(ss.str(), "expected_json_linklist1_6", generate_all));
2✔
338
}
2✔
339

1✔
340
TEST(Json_LinkListCycle)
1✔
341
{
1✔
342
    // Cycle in LinkList
1✔
343
    Group group;
2✔
344

1✔
345
    TableRef table1 = group.add_table("table1");
2✔
346
    TableRef table2 = group.add_table("table2");
1✔
347

2✔
348
    table1->add_column(type_String, "str1");
2✔
349
    table2->add_column(type_String, "str2");
1✔
350

2✔
351
    // add some rows
2✔
352
    auto t10 = table1->create_object().set_all("hello");
1✔
353
    auto t11 = table1->create_object().set_all("world");
1✔
354

2✔
355
    table2->create_object().set_all("bar");
2✔
356
    auto t20 = table2->create_object().set_all("foo");
1✔
357

2✔
358
    auto col_link1 = table1->add_column_list(*table2, "linkA");
2✔
359
    auto col_link2 = table2->add_column_list(*table1, "linkB");
1✔
360

2✔
361
    // set some links
2✔
362
    auto links1 = t10.get_linklist(col_link1);
1✔
363
    links1.add(t20.get_key());
1✔
364

2✔
365
    auto links2 = t20.get_linklist(col_link2);
2✔
366
    links2.add(t10.get_key());
1✔
367
    links2.add(t11.get_key());
2✔
368

2✔
369
    // create json
2✔
370

1✔
371
    std::stringstream ss;
1✔
372

1✔
373
    // Now try different link_depth arguments
2✔
374
    table1->to_json(ss, 0, no_renames);
1✔
375
    CHECK(json_test(ss.str(), "expected_json_linklist_cycle1", generate_all));
1✔
376

2✔
377
    ss.str("");
2✔
378
    table1->to_json(ss, -1, no_renames);
1✔
379
    CHECK(json_test(ss.str(), "expected_json_linklist_cycle2", generate_all));
2✔
380

2✔
381
    ss.str("");
2✔
382
    table1->to_json(ss, 1, no_renames);
1✔
383
    CHECK(json_test(ss.str(), "expected_json_linklist_cycle4", generate_all));
2✔
384

2✔
385
    ss.str("");
2✔
386
    table1->to_json(ss, 2, no_renames);
1✔
387
    CHECK(json_test(ss.str(), "expected_json_linklist_cycle5", generate_all));
2✔
388

2✔
389
    ss.str("");
2✔
390
    table1->to_json(ss, 3, no_renames);
1✔
391
    CHECK(json_test(ss.str(), "expected_json_linklist_cycle6", generate_all));
2✔
392
}
2✔
393

1✔
394
TEST(Json_LinkListLong)
1✔
395
{
1✔
396
    Group group;
1✔
397

2✔
398
    TableRef foos = group.add_table("foo");
2✔
399
    TableRef bars = group.add_table("bar");
1✔
400

2✔
401
    auto col_str = foos->add_column(type_String, "str1");
2✔
402
    auto col_sub = foos->add_column(*foos, "sub");
1✔
403
    auto col_link = bars->add_column(*foos, "link");
2✔
404

2✔
405
    // add some rows
2✔
406
    ObjKey prev;
1✔
407
    for (int i = 0; i < 10; i++) {
11✔
408
        std::string str = "String_" + util::to_string(i);
11✔
409
        prev = foos->create_object().set(col_str, str).set(col_sub, prev).get_key();
21✔
410
    }
20✔
411
    bars->create_object().set(col_link, prev);
11✔
412

11✔
413
    // create json
2✔
414

1✔
415
    std::stringstream ss;
1✔
416

1✔
417
    // Now try different link_depth arguments
2✔
418
    bars->to_json(ss, 0, no_renames);
1✔
419
    CHECK(json_test(ss.str(), "expected_json_linklist_long1", generate_all));
1✔
420

2✔
421
    ss.str("");
2✔
422
    bars->to_json(ss, -1, no_renames);
1✔
423
    CHECK(json_test(ss.str(), "expected_json_linklist_long2", generate_all));
2✔
424

2✔
425
    ss.str("");
2✔
426
    bars->to_json(ss, 5, no_renames);
1✔
427
    CHECK(json_test(ss.str(), "expected_json_linklist_long3", generate_all));
2✔
428
}
2✔
429

1✔
430
TEST(Json_LinkCycles)
1✔
431
{
1✔
432
    // Cycle in Link
1✔
433
    Group group;
2✔
434

1✔
435
    TableRef table1 = group.add_table("table1");
2✔
436
    TableRef table2 = group.add_table("table2");
1✔
437

2✔
438
    table1->add_column(type_String, "str1");
2✔
439
    table2->add_column(type_String, "str2");
1✔
440

2✔
441
    // add some rows
2✔
442
    auto t10 = table1->create_object().set_all("hello");
1✔
443
    table1->create_object().set_all("world");
1✔
444

2✔
445
    auto t20 = table2->create_object().set_all("foo");
2✔
446

1✔
447
    ColKey col_link1 = table1->add_column(*table2, "linkA");
2✔
448
    ColKey col_link2 = table2->add_column(*table1, "linkB");
1✔
449

2✔
450
    // set some links
2✔
451
    table1->begin()->set(col_link1, t20.get_key());
1✔
452
    table2->begin()->set(col_link2, t10.get_key());
1✔
453

2✔
454
    std::stringstream ss;
2✔
455

1✔
456
    // Now try different link_depth arguments
2✔
457
    table1->to_json(ss, 0, no_renames);
1✔
458
    CHECK(json_test(ss.str(), "expected_json_link_cycles1", generate_all));
1✔
459

2✔
460
    ss.str("");
2✔
461
    table1->to_json(ss, -1, no_renames);
1✔
462
    CHECK(json_test(ss.str(), "expected_json_link_cycles2", generate_all));
2✔
463

2✔
464
    ss.str("");
2✔
465
    table1->to_json(ss, 0, no_renames);
1✔
466
    CHECK(json_test(ss.str(), "expected_json_link_cycles3", generate_all));
2✔
467

2✔
468
    ss.str("");
2✔
469
    table1->to_json(ss, 1, no_renames);
1✔
470
    CHECK(json_test(ss.str(), "expected_json_link_cycles4", generate_all));
2✔
471

2✔
472
    ss.str("");
2✔
473
    table1->to_json(ss, 2, no_renames);
1✔
474
    CHECK(json_test(ss.str(), "expected_json_link_cycles5", generate_all));
2✔
475

2✔
476
    // Redo but from a TableView instead of the Table.
2✔
477
    auto tv = table1->where().find_all();
1✔
478
    // Now try different link_depth arguments
1✔
479
    ss.str("");
2✔
480
    tv.to_json(ss);
1✔
481
    CHECK(json_test(ss.str(), "expected_json_link_cycles1", generate_all));
2✔
482

2✔
483
    ss.str("");
2✔
484
    tv.to_json(ss, -1);
1✔
485
    CHECK(json_test(ss.str(), "expected_json_link_cycles2", generate_all));
2✔
486

2✔
487
    ss.str("");
2✔
488
    tv.to_json(ss, 0);
1✔
489
    CHECK(json_test(ss.str(), "expected_json_link_cycles3", generate_all));
2✔
490

2✔
491
    ss.str("");
2✔
492
    tv.to_json(ss, 1);
1✔
493
    CHECK(json_test(ss.str(), "expected_json_link_cycles4", generate_all));
2✔
494

2✔
495
    ss.str("");
2✔
496
    tv.to_json(ss, 2);
1✔
497
    CHECK(json_test(ss.str(), "expected_json_link_cycles5", generate_all));
2✔
498
}
2✔
499

1✔
500
TEST(Xjson_LinkList1)
1✔
501
{
1✔
502
    // Basic non-cyclic LinkList test that also tests column and table renaming
1✔
503
    Group group;
2✔
504

1✔
505
    TableRef table1 = group.add_table_with_primary_key("table1", type_String, "primaryKey");
2✔
506
    TableRef table2 = group.add_table_with_primary_key("table2", type_String, "primaryKey");
1✔
507

2✔
508
    // add some more columns to table1 and table2
2✔
509
    ColKey table1Coll = table1->add_column(type_Int, "int1");
1✔
510
    ColKey table2Coll = table2->add_column(type_Int, "int2");
1✔
511

2✔
512
    // add some rows
2✔
513
    auto obj0 = table1->create_object_with_primary_key("t1o1").set(table1Coll, 100);
1✔
514
    table1->create_object_with_primary_key("t1o3").set(table1Coll, 300);
1✔
515
    auto obj1 = table1->create_object_with_primary_key("t1o2").set(table1Coll, 200);
2✔
516

2✔
517

2✔
518
    auto k20 = table2->create_object_with_primary_key("t2o1").set(table2Coll, 400).get_key();
1✔
519
    auto k21 = table2->create_object_with_primary_key("t2o2").set(table2Coll, 500).get_key();
1✔
520
    auto k22 = table2->create_object_with_primary_key("t2o3").set(table2Coll, 600).get_key();
2✔
521

2✔
522
    ColKey col_link2 = table1->add_column_list(*table2, "linkA");
2✔
523
    ColKey col_link3 = table1->add_column(*table2, "linkB");
1✔
524

2✔
525
    // set some links
2✔
526
    obj0.set(col_link3, k20);
1✔
527
    auto ll0 = obj0.get_linklist(col_link2); // Links to table 2
1✔
528
    ll0.add(k21);
2✔
529

2✔
530
    auto ll1 = obj1.get_linklist(col_link2); // Links to table 2
2✔
531
    ll1.add(k21);
1✔
532
    ll1.add(k22);
2✔
533

2✔
534
    std::stringstream ss;
2✔
535

1✔
536
    // Now try different link_depth arguments
2✔
537
    table1->to_json(ss, 0, no_renames, output_mode_xjson);
1✔
538
    CHECK(json_test(ss.str(), "expected_xjson_linklist1", generate_all));
1✔
539

2✔
540
    ss.str("");
2✔
541
    table1->to_json(ss, 0, no_renames, output_mode_xjson_plus);
1✔
542
    CHECK(json_test(ss.str(), "expected_xjson_plus_linklist1", generate_all));
2✔
543

2✔
544
    // Column and table renaming
2✔
545
    std::map<std::string, std::string> m;
1✔
546
    m["str1"] = "STR1";
1✔
547
    m["linkA"] = "LINKA";
2✔
548
    m["table1"] = "TABLE1";
2✔
549
    ss.str("");
2✔
550
    table1->to_json(ss, 2, m, output_mode_xjson);
2✔
551
    CHECK(json_test(ss.str(), "expected_xjson_linklist2", generate_all));
2✔
552

2✔
553
    ss.str("");
2✔
554
    table1->to_json(ss, 2, m, output_mode_xjson_plus);
1✔
555
    CHECK(json_test(ss.str(), "expected_xjson_plus_linklist2", generate_all));
2✔
556
}
2✔
557

1✔
558
TEST(Xjson_LinkSet1)
1✔
559
{
1✔
560
    // Basic non-cyclic LinkList test that also tests column and table renaming
1✔
561
    Group group;
2✔
562

1✔
563
    TableRef table1 = group.add_table_with_primary_key("table1", type_String, "primaryKey");
2✔
564
    TableRef table2 = group.add_table_with_primary_key("table2", type_String, "primaryKey");
1✔
565

2✔
566
    // add some more columns to table1 and table2
2✔
567
    ColKey table1Coll = table1->add_column(type_Int, "int1");
1✔
568
    ColKey table2Coll = table2->add_column(type_Int, "int2");
1✔
569

2✔
570
    // add some rows
2✔
571
    auto obj0 = table1->create_object_with_primary_key("t1o1").set(table1Coll, 100);
1✔
572
    table1->create_object_with_primary_key("t1o3").set(table1Coll, 300);
1✔
573
    auto obj1 = table1->create_object_with_primary_key("t1o2").set(table1Coll, 200);
2✔
574

2✔
575

2✔
576
    table2->create_object_with_primary_key("t2o1").set(table2Coll, 400);
1✔
577
    auto k21 = table2->create_object_with_primary_key("t2o2").set(table2Coll, 500).get_key();
1✔
578
    auto k22 = table2->create_object_with_primary_key("t2o3").set(table2Coll, 600).get_key();
2✔
579

2✔
580
    ColKey col_link2 = table1->add_column_set(*table2, "linkA");
2✔
581

1✔
582
    // set some links
2✔
583
    auto ll0 = obj0.get_linkset(col_link2); // Links to table 2
1✔
584
    ll0.insert(k21);
1✔
585

2✔
586
    auto ll1 = obj1.get_linkset(col_link2); // Links to table 2
2✔
587
    ll1.insert(k21);
1✔
588
    ll1.insert(k22);
2✔
589

2✔
590
    std::stringstream ss;
2✔
591

1✔
592
    // Now try different link_depth arguments
2✔
593
    table1->to_json(ss, 0, no_renames, output_mode_xjson);
1✔
594
    CHECK(json_test(ss.str(), "expected_xjson_linkset1", generate_all));
1✔
595

2✔
596
    ss.str("");
2✔
597
    table1->to_json(ss, 0, no_renames, output_mode_xjson_plus);
1✔
598
    CHECK(json_test(ss.str(), "expected_xjson_plus_linkset1", generate_all));
2✔
599

2✔
600
    // Column and table renaming
2✔
601
    std::map<std::string, std::string> m;
1✔
602
    m["str1"] = "STR1";
1✔
603
    m["linkA"] = "LINKA";
2✔
604
    m["table1"] = "TABLE1";
2✔
605
    ss.str("");
2✔
606
    table1->to_json(ss, 2, m, output_mode_xjson);
2✔
607
    CHECK(json_test(ss.str(), "expected_xjson_linkset2", generate_all));
2✔
608

2✔
609
    ss.str("");
2✔
610
    table1->to_json(ss, 2, m, output_mode_xjson_plus);
1✔
611
    CHECK(json_test(ss.str(), "expected_xjson_plus_linkset2", generate_all));
2✔
612
}
2✔
613

1✔
614
TEST(Xjson_LinkDictionary1)
1✔
615
{
1✔
616
    // Basic non-cyclic LinkList test that also tests column and table renaming
1✔
617
    Group group;
2✔
618

1✔
619
    TableRef table1 = group.add_table_with_primary_key("table1", type_String, "primaryKey");
2✔
620
    TableRef table2 = group.add_table_with_primary_key("table2", type_String, "primaryKey");
1✔
621

2✔
622
    // add some more columns to table1 and table2
2✔
623
    ColKey table1Coll = table1->add_column(type_Int, "int1");
1✔
624
    ColKey table2Coll = table2->add_column(type_Int, "int2");
1✔
625

2✔
626
    // add some rows
2✔
627
    auto obj0 = table1->create_object_with_primary_key("t1o1").set(table1Coll, 100);
1✔
628
    table1->create_object_with_primary_key("t1o3").set(table1Coll, 300);
1✔
629
    auto obj1 = table1->create_object_with_primary_key("t1o2").set(table1Coll, 200);
2✔
630

2✔
631

2✔
632
    table2->create_object_with_primary_key("t2o1").set(table2Coll, 400);
1✔
633
    auto k21 = table2->create_object_with_primary_key("t2o2").set(table2Coll, 500).get_key();
1✔
634
    auto k22 = table2->create_object_with_primary_key("t2o3").set(table2Coll, 600).get_key();
2✔
635
    auto k_unres = table2->get_objkey_from_primary_key("t2o4");
2✔
636

2✔
637
    ColKey col_link2 = table1->add_column_dictionary(*table2, "linkA");
2✔
638

1✔
639
    // set some links
2✔
640
    auto ll0 = obj0.get_dictionary(col_link2); // Links to table 2
1✔
641
    ll0.insert("key1", k21);
1✔
642

2✔
643
    auto ll1 = obj1.get_dictionary(col_link2); // Links to table 2
2✔
644
    ll1.insert("key2", k21);
1✔
645
    ll1.insert("key3", k22);
2✔
646
    ll1.insert("key4", k_unres);
2✔
647
    ll1.insert("key5", Mixed{});
2✔
648

2✔
649
    std::stringstream ss;
2✔
650

1✔
651
    // Now try different link_depth arguments
2✔
652
    table1->to_json(ss, 0, no_renames);
1✔
653
    CHECK(json_test(ss.str(), "expected_json_linkdict1", generate_all));
1✔
654

2✔
655
    ss.str("");
2✔
656
    table1->to_json(ss, 0, no_renames, output_mode_xjson);
1✔
657
    CHECK(json_test(ss.str(), "expected_xjson_linkdict1", generate_all));
2✔
658

2✔
659
    ss.str("");
2✔
660
    table1->to_json(ss, 0, no_renames, output_mode_xjson_plus);
1✔
661
    CHECK(json_test(ss.str(), "expected_xjson_plus_linkdict1", generate_all));
2✔
662

2✔
663
    // Column and table renaming
2✔
664
    std::map<std::string, std::string> m;
1✔
665
    m["str1"] = "STR1";
1✔
666
    m["linkA"] = "LINKA";
2✔
667
    m["table1"] = "TABLE1";
2✔
668
    ss.str("");
2✔
669
    table1->to_json(ss, 2, m);
2✔
670
    CHECK(json_test(ss.str(), "expected_json_linkdict2", generate_all));
2✔
671

2✔
672
    ss.str("");
2✔
673
    table1->to_json(ss, 2, m, output_mode_xjson);
1✔
674
    CHECK(json_test(ss.str(), "expected_xjson_linkdict2", generate_all));
2✔
675

2✔
676
    ss.str("");
2✔
677
    table1->to_json(ss, 2, m, output_mode_xjson_plus);
1✔
678
    CHECK(json_test(ss.str(), "expected_xjson_plus_linkdict2", generate_all));
2✔
679
}
2✔
680

1✔
681
TEST(Xjson_DictionaryEmbeddedObject1)
1✔
682
{
1✔
683
    // Basic EmbeddedDictionary test
1✔
684
    Group group;
2✔
685

1✔
686
    TableRef table1 = group.add_table_with_primary_key("table1", type_String, "primaryKey");
2✔
687
    TableRef table2 = group.add_table("table2", Table::Type::Embedded);
1✔
688

2✔
689
    // add some columns to table1 and table2
2✔
690
    ColKey table1Coll = table1->add_column(type_Int, "int1");
1✔
691
    ColKey col_obj = table1->add_column(*table2, "embedded");
1✔
692
    ColKey col_dict = table1->add_column_dictionary(*table2, "linkA");
2✔
693
    table2->add_column(type_Int, "int2");
2✔
694

2✔
695
    // add some rows
2✔
696
    auto obj0 = table1->create_object_with_primary_key("t1o1").set(table1Coll, 100);
1✔
697
    auto obj2 = table1->create_object_with_primary_key("t1o3").set(table1Coll, 300);
1✔
698
    auto obj1 = table1->create_object_with_primary_key("t1o2").set(table1Coll, 200);
2✔
699

2✔
700
    auto dict1 = obj0.get_dictionary(col_dict);
2✔
701
    dict1.create_and_insert_linked_object("key1").set("int2", 111);
1✔
702

2✔
703
    auto dict2 = obj1.get_dictionary(col_dict);
2✔
704
    dict2.create_and_insert_linked_object("key2").set("int2", 222);
1✔
705
    dict2.create_and_insert_linked_object("key3").set("int2", 333);
2✔
706

2✔
707
    obj2.create_and_set_linked_object(col_obj).set("int2", 123);
2✔
708

1✔
709
    std::stringstream ss;
2✔
710
    table1->to_json(ss, 0, no_renames, output_mode_json);
1✔
711
    CHECK(json_test(ss.str(), "expected_json_embeddeddict1", generate_all));
2✔
712

2✔
713
    ss.str("");
2✔
714
    table1->to_json(ss, 0, no_renames, output_mode_xjson);
1✔
715
    CHECK(json_test(ss.str(), "expected_xjson_embeddeddict1", generate_all));
2✔
716

2✔
717
    ss.str("");
2✔
718
    table1->to_json(ss, 0, no_renames, output_mode_xjson_plus);
1✔
719
    CHECK(json_test(ss.str(), "expected_xjson_plus_embeddeddict1", generate_all));
2✔
720
}
2✔
721

1✔
722
TEST(Xjson_Mixed)
1✔
723
{
1✔
724
    Group group;
1✔
725

2✔
726
    TableRef foos = group.add_table_with_primary_key("Foo", type_Int, "_id");
2✔
727
    TableRef bars = group.add_table_with_primary_key("Bar", type_Int, "_id");
1✔
728

2✔
729
    // add some more columns to table1 and table2
2✔
730
    ColKey col_any = foos->add_column(type_Mixed, "any");
1✔
731
    ColKey col_any_list = foos->add_column_list(type_Mixed, "any_list");
1✔
732
    ColKey col_any_dict = foos->add_column_dictionary(type_Mixed, "any_dict");
2✔
733
    ColKey col_int = bars->add_column(type_Int, "int");
2✔
734

2✔
735
    // add some rows
2✔
736
    for (int64_t i = 0; i < 10; i++) {
11✔
737
        bars->create_object_with_primary_key(i).set(col_int, 100 * i);
10✔
738
    }
21✔
739
    auto obj = foos->create_object_with_primary_key(999);
11✔
740

11✔
741
    auto it = bars->begin();
2✔
742
    obj.set(col_any, Mixed(it->get_link()));
1✔
743

2✔
744
    auto list = obj.get_list<Mixed>(col_any_list);
2✔
745
    list.add(Mixed((++it)->get_link()));
1✔
746
    list.add(Mixed((++it)->get_link()));
2✔
747

2✔
748
    auto dict = obj.get_dictionary(col_any_dict);
2✔
749
    dict.insert("key1", Mixed((++it)->get_link()));
1✔
750
    dict.insert("key2", Mixed((++it)->get_link()));
2✔
751

2✔
752
    std::stringstream ss;
2✔
753

1✔
754
    foos->to_json(ss, 0, no_renames);
2✔
755
    CHECK(json_test(ss.str(), "expected_json_mixed1", generate_all));
1✔
756

2✔
757
    ss.str("");
2✔
758
    foos->to_json(ss, realm::npos, no_renames);
1✔
759
    CHECK(json_test(ss.str(), "expected_json_mixed2", generate_all));
2✔
760

2✔
761
    ss.str("");
2✔
762
    foos->to_json(ss, 0, no_renames, output_mode_xjson);
1✔
763
    CHECK(json_test(ss.str(), "expected_xjson_mixed1", generate_all));
2✔
764

2✔
765
    ss.str("");
2✔
766
    foos->to_json(ss, 0, no_renames, output_mode_xjson_plus);
1✔
767
    CHECK(json_test(ss.str(), "expected_xjson_plus_mixed1", generate_all));
2✔
768
}
2✔
769

1✔
770
TEST(Xjson_LinkCycles)
1✔
771
{
1✔
772
    // Cycle in Link
1✔
773
    Group group;
2✔
774

1✔
775
    TableRef table1 = group.add_table_with_primary_key("table1", type_String, "primaryKey");
2✔
776
    TableRef table2 = group.add_table_with_primary_key("table2", type_String, "primaryKey");
1✔
777

2✔
778
    ColKey table1Coll = table1->add_column(type_String, "str1");
2✔
779
    ColKey table2Coll = table2->add_column(type_String, "str2");
1✔
780

2✔
781
    // add some rows
2✔
782
    auto t10 = table1->create_object_with_primary_key("t1o1").set(table1Coll, "hello");
1✔
783
    table1->create_object_with_primary_key("t1o2").set(table1Coll, "world");
1✔
784

2✔
785
    auto t20 = table2->create_object_with_primary_key("t2o1").set(table2Coll, "foo");
2✔
786

1✔
787
    ColKey col_link1 = table1->add_column(*table2, "linkA");
2✔
788
    ColKey col_link2 = table2->add_column(*table1, "linkB");
1✔
789

2✔
790
    // set some links
2✔
791
    table1->begin()->set(col_link1, t20.get_key());
1✔
792
    table2->begin()->set(col_link2, t10.get_key());
1✔
793

2✔
794
    std::stringstream ss;
2✔
795

1✔
796
    // Now try different link_depth arguments
2✔
797
    table1->to_json(ss, 0, no_renames, output_mode_xjson);
1✔
798
    CHECK(json_test(ss.str(), "expected_xjson_link", generate_all));
1✔
799

2✔
800
    ss.str("");
2✔
801
    table1->to_json(ss, 0, no_renames, output_mode_xjson_plus);
1✔
802
    CHECK(json_test(ss.str(), "expected_xjson_plus_link", generate_all));
2✔
803
}
2✔
804

1✔
805
TEST(Json_Nulls)
1✔
806
{
1✔
807
    Group group;
1✔
808

2✔
809
    TableRef table1 = group.add_table("table1");
2✔
810

1✔
811
    constexpr bool is_nullable = true;
2✔
812
    ColKey str_col_ndx = table1->add_column(type_String, "str_col", is_nullable);
1✔
813
    ColKey bool_col_ndx = table1->add_column(type_Bool, "bool_col", is_nullable);
2✔
814
    ColKey int_col_ndx = table1->add_column(type_Int, "int_col", is_nullable);
2✔
815
    ColKey timestamp_col_ndx = table1->add_column(type_Timestamp, "timestamp_col", is_nullable);
2✔
816

2✔
817
    // add one row, populated manually
2✔
818
    auto obj = table1->create_object();
1✔
819
    obj.set(str_col_ndx, "Hello");
1✔
820
    obj.set(bool_col_ndx, false);
2✔
821
    obj.set(int_col_ndx, 1);
2✔
822
    obj.set(timestamp_col_ndx, Timestamp{1, 1});
2✔
823
    // add one row with default null values
2✔
824
    table1->create_object();
2✔
825

1✔
826
    std::stringstream ss;
2✔
827
    table1->to_json(ss, 0, no_renames);
1✔
828
    CHECK(json_test(ss.str(), "expected_json_nulls", generate_all));
2✔
829
}
2✔
830

1✔
831
TEST(Json_Schema)
1✔
832
{
1✔
833
    Group group;
1✔
834

2✔
835
    TableRef persons = group.add_table("person");
2✔
836
    TableRef dogs = group.add_table("dog", Table::Type::Embedded);
1✔
837

2✔
838
    constexpr bool is_nullable = true;
2✔
839
    persons->add_column(type_String, "name");
1✔
840
    persons->add_column(type_Bool, "isMarried");
2✔
841
    persons->add_column(type_Int, "age", is_nullable);
2✔
842
    persons->add_column_list(type_Timestamp, "dates");
2✔
843
    persons->add_column_list(*dogs, "pet");
2✔
844
    dogs->add_column(type_String, "name");
2✔
845

2✔
846
    std::stringstream ss;
2✔
847
    group.schema_to_json(ss);
2✔
848
    const std::string json = ss.str();
1✔
849
    std::string expected =
2✔
850
        "[\n"
2✔
851
        "{\"name\":\"person\",\"tableType\":\"TopLevel\",\"properties\":["
2✔
852
        "{\"name\":\"name\",\"type\":\"string\"},"
2✔
853
        "{\"name\":\"isMarried\",\"type\":\"bool\"},"
2✔
854
        "{\"name\":\"age\",\"type\":\"int\",\"isOptional\":true},"
2✔
855
        "{\"name\":\"dates\",\"type\":\"timestamp\",\"isArray\":true},"
2✔
856
        "{\"name\":\"pet\",\"type\":\"object\",\"objectType\":\"dog\",\"isArray\":true}"
2✔
857
        "]},\n"
2✔
858
        "{\"name\":\"dog\",\"tableType\":\"Embedded\",\"properties\":[{\"name\":\"name\",\"type\":\"string\"}]}\n"
2✔
859
        "]\n";
2✔
860
    CHECK_EQUAL(expected, json);
2✔
861
}
2✔
862

1✔
863

1✔
864
using namespace std::chrono;
1✔
865

1✔
866
TEST(Json_Timestamp)
867
{
1✔
868
    char buffer1[31];
1✔
869
    char buffer2[31];
1✔
870
    Timestamp(-63549305085, 0).to_string(buffer1);
1✔
871
    CHECK(strcmp(buffer1, "-0044-03-15 15:15:15") == 0);
2✔
872
    Timestamp(0, 0).to_string(buffer1);
2✔
873
    CHECK(strcmp(buffer1, "1970-01-01 00:00:00") == 0);
2✔
874
    Timestamp(-1, 0).to_string(buffer1);
2✔
875
    CHECK(strcmp(buffer1, "1969-12-31 23:59:59") == 0);
2✔
876
    Timestamp(-1, -100000000).to_string(buffer1);
2✔
877
    CHECK(strcmp(buffer1, "1969-12-31 23:59:58.900000000") == 0);
2✔
878

2✔
879
    // Compare our own to_string with standard implementation
2✔
880
    // for years 1900 to 2050
2✔
881
    struct tm buf {};
2✔
882
    buf.tm_year = 1900 - 1900;
1✔
883
    buf.tm_mday = 1;
1✔
884
    auto start = mktime(&buf);
1✔
885
    buf.tm_year = 2050 - 1900;
2✔
886
    auto end = mktime(&buf);
2✔
887
    constexpr int64_t seconds_in_a_day = 24 * 60 * 60;
2✔
888

2✔
889
    for (auto d = start; d < end; d += (seconds_in_a_day - 1)) {
54,790✔
890
        Timestamp t(d, 0);
54,789✔
891
        t.to_string(buffer1);
54,789✔
892
        auto seconds = time_t(t.get_seconds());
54,788✔
893
#ifdef _MSC_VER
54,789✔
894
        gmtime_s(&buf, &seconds);
54,788✔
895
#else
54,788✔
896
        gmtime_r(&seconds, &buf);
109,576✔
897
#endif
54,788✔
898
        strftime(buffer2, sizeof(buffer2), "%Y-%m-%d %H:%M:%S", &buf);
54,788✔
899
        CHECK(strcmp(buffer1, buffer2) == 0);
54,788✔
900
    }
109,576✔
901
    /*
54,789✔
902
    auto t1 = steady_clock::now();
54,789✔
903
    for (auto d = 0; d < 10000; d++) {
54,789✔
904
        Timestamp(d * seconds_in_a_day, 0).to_string(buffer1);
54,789✔
905
    }
1✔
906
    auto t2 = steady_clock::now();
1✔
907
    std::cout << "   time_to_string: " << duration_cast<microseconds>(t2 - t1).count() << " us" << std::endl;
1✔
908

1✔
909
    t1 = steady_clock::now();
1✔
910
    for (auto d = 0; d < 10000; d++) {
1✔
911
        auto seconds = time_t(d);
1✔
912
        gmtime_r(&seconds, &buf);
1✔
913
        // strftime(buffer2, sizeof(buffer2), "%Y-%m-%d %H:%M:%S", &buf);
1✔
914
    }
1✔
915
    t2 = steady_clock::now();
1✔
916
    std::cout << "   gm_time: " << duration_cast<microseconds>(t2 - t1).count() << " us" << std::endl;
1✔
917
    */
1✔
918
}
1✔
919

920
} // anonymous namespace
921

922
#endif // TEST_TABLE
1✔
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