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

realm / realm-core / thomas.goyne_491

09 Aug 2024 04:34PM UTC coverage: 89.577% (-1.5%) from 91.087%
thomas.goyne_491

Pull #7967

Evergreen

tgoyne
Actually check for unuplaoded changes in no_pending_local_changes()

We can have local changesets stored which have already been uploaded and
acknoledged by the server, so checking all of the changesets is incorrect. We
need to instead only check changesets for versions after the current position
of the upload cursor.
Pull Request #7967: RCORE-2232 Actually check for unuploaded changes in no_pending_local_changes()

90956 of 164876 branches covered (55.17%)

37 of 38 new or added lines in 2 files covered. (97.37%)

42 existing lines in 8 files now uncovered.

145956 of 162940 relevant lines covered (89.58%)

8094301.68 hits per line

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

90.43
/src/realm/util/serializer.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 <realm/util/serializer.hpp>
20

21
#include <realm/binary_data.hpp>
22
#include <realm/geospatial.hpp>
23
#include <realm/keys.hpp>
24
#include <realm/null.hpp>
25
#include <realm/query_expression.hpp>
26
#include <realm/string_data.hpp>
27
#include <realm/table.hpp>
28
#include <realm/timestamp.hpp>
29
#include <realm/util/base64.hpp>
30

31
#include <cctype>
32
#include <cmath>
33
#include <iomanip>
34

35
namespace realm {
36

37
/* Uses Fliegel & Van Flandern algorithm */
38
static constexpr long date_to_julian(int y, int m, int d)
39
{
×
40
    return (1461 * (y + 4800 + (m - 14) / 12)) / 4 + (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 -
×
41
           (3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075;
×
42
}
×
43

44
static void julian_to_date(int jd, int* y, int* m, int* d)
45
{
329,814✔
46
    uint64_t L = jd + 68569;
329,814✔
47
    uint64_t n = (4 * L) / 146097;
329,814✔
48
    uint64_t i, j;
329,814✔
49

50
    L = L - (146097 * n + 3) / 4;
329,814✔
51
    i = (4000 * (L + 1)) / 1461001;
329,814✔
52
    L = L - (1461 * i) / 4 + 31;
329,814✔
53
    j = (80 * L) / 2447;
329,814✔
54
    *d = static_cast<int>(L - (2447 * j) / 80);
329,814✔
55
    L = j / 11;
329,814✔
56
    *m = static_cast<int>(j + 2 - (12 * L));
329,814✔
57
    *y = static_cast<int>(100 * (n - 49) + i + L);
329,814✔
58
}
329,814✔
59

60
// Confirmed to work for all val < 16389
61
static void out_dec(char** buffer, unsigned val, int width)
62
{
1,978,884✔
63
    int w = width;
1,978,884✔
64
    char* p = *buffer;
1,978,884✔
65
    while (width > 0) {
6,596,280✔
66
        width--;
4,617,396✔
67
        unsigned div10 = (val * 6554) >> 16;
4,617,396✔
68
        p[width] = char(val - div10 * 10) + '0';
4,617,396✔
69
        val = div10;
4,617,396✔
70
    }
4,617,396✔
71
    *buffer += w;
1,978,884✔
72
}
1,978,884✔
73

74
static constexpr long epoc_julian_days = date_to_julian(1970, 1, 1); // 2440588
75
static constexpr int seconds_in_a_day = 24 * 60 * 60;
76

77
const char* Timestamp::to_string(std::array<char, 32>& buffer) const
78
{
329,814✔
79
    if (is_null()) {
329,814✔
80
        return "null";
×
81
    }
×
82
    int64_t seconds = get_seconds();
329,814✔
83
    int32_t nano = get_nanoseconds();
329,814✔
84
    if (nano < 0) {
329,814✔
85
        nano += Timestamp::nanoseconds_per_second;
6✔
86
        seconds--;
6✔
87
    }
6✔
88

89
    int64_t days = seconds / seconds_in_a_day;
329,814✔
90
    int julian_days = int(days + epoc_julian_days);
329,814✔
91

92
    int seconds_in_day = int(seconds - days * seconds_in_a_day);
329,814✔
93
    if (seconds_in_day < 0) {
329,814✔
94
        seconds_in_day += seconds_in_a_day;
153,420✔
95
        julian_days--;
153,420✔
96
    }
153,420✔
97

98
    int year;
329,814✔
99
    int month;
329,814✔
100
    int day;
329,814✔
101
    int hours = seconds_in_day / 3600;
329,814✔
102
    int remainingSeconds = seconds_in_day - hours * 3600;
329,814✔
103
    int minutes = remainingSeconds / 60;
329,814✔
104
    int secs = remainingSeconds - minutes * 60;
329,814✔
105

106
    julian_to_date(julian_days, &year, &month, &day);
329,814✔
107

108
    char* p = buffer.data();
329,814✔
109
    if (year < 0) {
329,814✔
110
        *p++ = '-';
6✔
111
        year = -year;
6✔
112
    }
6✔
113
    out_dec(&p, year, 4);
329,814✔
114
    *p++ = '-';
329,814✔
115
    out_dec(&p, month, 2);
329,814✔
116
    *p++ = '-';
329,814✔
117
    out_dec(&p, day, 2);
329,814✔
118
    *p++ = ' ';
329,814✔
119
    out_dec(&p, hours, 2);
329,814✔
120
    *p++ = ':';
329,814✔
121
    out_dec(&p, minutes, 2);
329,814✔
122
    *p++ = ':';
329,814✔
123
    out_dec(&p, secs, 2);
329,814✔
124
    *p = '\0';
329,814✔
125
    if (nano) {
329,814✔
126
        snprintf(p, 32 - (p - buffer.data()), ".%09d", nano);
972✔
127
    }
972✔
128
    return buffer.data();
329,814✔
129
}
329,814✔
130

131
namespace util {
132
namespace serializer {
133

134
template <>
135
std::string print_value<>(BinaryData data)
136
{
3,738✔
137
    if (data.is_null()) {
3,738✔
138
        return "NULL";
114✔
139
    }
114✔
140
    return util::format("binary(%1)", print_value<StringData>(StringData(data.data(), data.size())));
3,624✔
141
}
3,738✔
142

143
template <>
144
std::string print_value<>(bool b)
145
{
864✔
146
    if (b) {
864✔
147
        return "true";
306✔
148
    }
306✔
149
    return "false";
558✔
150
}
864✔
151

152
template <typename T>
153
inline std::string print_with_nan_check(T val)
154
{
2,298✔
155
    // we standardize NaN because some implementations (windows) will
156
    // print the different types of NaN such as "nan(ind)" to indicate "indefinite"
157
    if (std::isnan(val)) {
2,298✔
158
        // preserving the sign of nan is not strictly required but is good etiquette
159
        if (std::signbit(val)) {
48✔
160
            return "-nan";
×
161
        }
×
162
        return "nan";
48✔
163
    }
48✔
164
    std::stringstream ss;
2,250✔
165
    ss << std::setprecision(std::numeric_limits<T>::max_digits10) << val;
2,250✔
166
    return ss.str();
2,250✔
167
}
2,298✔
168

169
template <>
170
std::string print_value<>(float val)
171
{
696✔
172
    return print_with_nan_check(val);
696✔
173
}
696✔
174

175
template <>
176
std::string print_value<>(double val)
177
{
1,602✔
178
    return print_with_nan_check(val);
1,602✔
179
}
1,602✔
180

181
template <>
182
std::string print_value<>(realm::null)
183
{
×
184
    return "NULL";
×
185
}
×
186

187
static bool contains_invalids(StringData data)
188
{
17,454✔
189
    // the custom whitelist is different from std::isprint because it doesn't include quotations
190
    const static std::string whitelist = " {|}~:;<=>?@!#$%&()*+,-./[]^_`";
17,454✔
191
    const char* ptr = data.data();
17,454✔
192
    const char* end = ptr + data.size();
17,454✔
193
    while (ptr < end) {
135,570✔
194
        using unsigned_char_t = unsigned char;
120,258✔
195
        char c = *ptr;
120,258✔
196
        size_t len = sequence_length(c);
120,258✔
197
        if (len == 1) {
120,258✔
198
            // std::isalnum takes an int, but is undefined for negative values so we must pass an unsigned char
199
            if (!std::isalnum(unsigned_char_t(c)) && whitelist.find(c) == std::string::npos) {
119,460✔
200
                return true;
1,398✔
201
            }
1,398✔
202
        }
119,460✔
203
        else {
798✔
204
            // multibyte utf8, check if subsequent bytes have the upper bit set
205
            for (size_t i = 1; i < len; i++) {
888✔
206
                ++ptr;
834✔
207
                if (ptr == end || (*ptr & 0x80) == 0) {
834✔
208
                    return true;
744✔
209
                }
744✔
210
            }
834✔
211
        }
798✔
212
        ++ptr;
118,116✔
213
    }
118,116✔
214
    return false;
15,312✔
215
}
17,454✔
216

217
template <>
218
std::string print_value<>(StringData data)
219
{
17,598✔
220
    if (data.is_null()) {
17,598✔
221
        return "NULL";
144✔
222
    }
144✔
223
    std::string out;
17,454✔
224
    const char* start = data.data();
17,454✔
225
    const size_t len = data.size();
17,454✔
226

227
    if (contains_invalids(data)) {
17,454✔
228
        std::string encode_buffer;
2,142✔
229
        encode_buffer.resize(util::base64_encoded_size(len));
2,142✔
230
        util::base64_encode(data, encode_buffer);
2,142✔
231
        out = "B64\"" + encode_buffer + "\"";
2,142✔
232
    }
2,142✔
233
    else {
15,312✔
234
        out.reserve(len + 2);
15,312✔
235
        out += '"';
15,312✔
236
        for (const char* i = start; i != start + len; ++i) {
133,218✔
237
            out += *i;
117,906✔
238
        }
117,906✔
239
        out += '"';
15,312✔
240
    }
15,312✔
241
    return out;
17,454✔
242
}
17,598✔
243

244
template <>
245
std::string print_value<>(realm::Timestamp t)
246
{
2,304✔
247
    if (t.is_null()) {
2,304✔
248
        return "NULL";
6✔
249
    }
6✔
250
    std::stringstream ss;
2,298✔
251
    if (t.is_null()) {
2,298✔
252
        ss << "null";
×
253
    }
×
254
    else {
2,298✔
255
        ss << "T" << t.get_seconds() << ":" << t.get_nanoseconds();
2,298✔
256
    }
2,298✔
257
    return ss.str();
2,298✔
258
}
2,304✔
259

260
template <>
261
std::string print_value<>(realm::ObjectId oid)
262
{
809,067✔
263
    return "oid(" + oid.to_string() + ")";
809,067✔
264
}
809,067✔
265

266
template <>
267
std::string print_value<>(realm::ObjKey k)
268
{
90✔
269
    std::stringstream ss;
90✔
270
    if (!k) {
90✔
271
        ss << "NULL";
6✔
272
    }
6✔
273
    else {
84✔
274
        ss << "O" << k.value;
84✔
275
    }
84✔
276
    return ss.str();
90✔
277
}
90✔
278

279
std::string print_value(realm::ObjLink link, Group* g)
280
{
348✔
281
    if (!link) {
348✔
282
        return "NULL";
138✔
283
    }
138✔
284
    else {
210✔
285
        TableRef target_table = g ? g->get_table(link.get_table_key()) : TableRef();
210✔
286
        if (ColKey pk_col = target_table ? target_table->get_primary_key_column() : ColKey{}) {
210✔
287
            if (auto obj = target_table->try_get_object(link.get_obj_key())) {
132✔
288
                auto pk_val = obj.get_any(pk_col);
132✔
289
                std::ostringstream ostr;
132✔
290
                ostr << "obj(" << util::serializer::print_value(target_table->get_name()) << "," << pk_val << ')';
132✔
291
                return ostr.str();
132✔
292
            }
132✔
293
        }
132✔
294
    }
210✔
295
    std::stringstream ss;
78✔
296
    ss << "L" << link.get_table_key().value << ":" << link.get_obj_key().value;
78✔
297
    return ss.str();
78✔
298
}
348✔
299

300
template <>
301
std::string print_value<>(realm::UUID uuid)
302
{
2,412✔
303
    return "uuid(" + uuid.to_string() + ")";
2,412✔
304
}
2,412✔
305

306
template <>
307
std::string print_value<>(realm::TypeOfValue type)
308
{
606✔
309
    return '"' + type.to_string() + '"';
606✔
310
}
606✔
311

312
#if REALM_ENABLE_GEOSPATIAL
313
template <>
314
std::string print_value<>(const realm::Geospatial& geo)
315
{
×
316
    return geo.to_string();
×
317
}
×
318
#endif
319

320
// The variable name must be unique with respect to the already chosen variables at
321
// this level of subquery nesting and with respect to the names of the columns in the table.
322
// This assumes that columns can start with '$' and that we might one day want to support
323
// referencing the parent table columns in the subquery. This is currently disabled by an assertion in the
324
// core SubQuery constructor.
325
std::string SerialisationState::get_variable_name(ConstTableRef table)
326
{
228✔
327
    std::string guess_prefix = "$";
228✔
328
    const char start_char = 'x';
228✔
329
    char add_char = start_char;
228✔
330

331
    auto next_guess = [&]() {
228✔
332
        add_char = (((add_char + 1) - 'a') % ('z' - 'a' + 1)) + 'a';
186✔
333
        if (add_char == start_char) {
186✔
334
            guess_prefix += add_char;
6✔
335
        }
6✔
336
    };
186✔
337

338
    while (true) {
414✔
339
        std::string guess = guess_prefix + add_char;
414✔
340
        bool found_duplicate = false;
414✔
341
        for (size_t i = 0; i < subquery_prefix_list.size(); ++i) {
1,104✔
342
            if (guess == subquery_prefix_list[i]) {
720✔
343
                found_duplicate = true;
30✔
344
                break;
30✔
345
            }
30✔
346
        }
720✔
347
        if (found_duplicate) {
414✔
348
            next_guess();
30✔
349
            continue;
30✔
350
        }
30✔
351
        if (table->get_column_key(guess) != ColKey()) {
384✔
352
            next_guess();
156✔
353
            continue;
156✔
354
        }
156✔
355
        return guess;
228✔
356
    }
384✔
357
}
228✔
358

359
std::string SerialisationState::get_column_name(ConstTableRef table, ColKey col_key)
360
{
58,947✔
361
    ColumnType col_type = table->get_real_column_type(col_key);
58,947✔
362
    if (col_type == col_type_BackLink) {
58,947✔
363
        const Table::BacklinkOrigin origin = table->find_backlink_origin(col_key);
354✔
364
        REALM_ASSERT(origin);
354✔
365
        StringData source_table_name = origin->first->get_class_name();
354✔
366
        std::string source_col_name = get_column_name(origin->first, origin->second);
354✔
367

368
        return format("@links.%1.%2", source_table_name, source_col_name);
354✔
369
    }
354✔
370
    else if (col_key != ColKey()) {
58,593✔
371
        std::string col_name = table->get_column_name(col_key);
58,593✔
372
        size_t pos = col_name.find_first_of(" \t\r\n");
58,593✔
373
        while (pos != std::string::npos) {
58,917✔
374
            switch (col_name[pos]) {
324✔
375
                case ' ':
114✔
376
                    // space is unchanged
377
                    break;
114✔
378
                case '\t':
210✔
379
                    col_name[pos] = 't';
210✔
380
                    break;
210✔
381
                case '\r':
✔
382
                    col_name[pos] = 'r';
×
383
                    break;
×
384
                case '\n':
✔
385
                    col_name[pos] = 'n';
×
386
                    break;
×
387
            }
324✔
388
            col_name = col_name.substr(0, pos) + "\\" + col_name.substr(pos);
324✔
389
            pos += 2;
324✔
390
            pos = col_name.find_first_of(" \t\r\n", pos);
324✔
391
        }
324✔
392
        return col_name;
58,593✔
393
    }
58,593✔
UNCOV
394
    return "";
×
395
}
58,947✔
396

397
std::string SerialisationState::describe_column(ConstTableRef table, ColKey col_key)
398
{
25,563✔
399
    if (table && col_key) {
25,563✔
400
        std::string desc;
25,563✔
401
        if (!subquery_prefix_list.empty()) {
25,563✔
402
            desc += subquery_prefix_list.back() + value_separator;
54✔
403
        }
54✔
404
        desc += get_column_name(table, col_key);
25,563✔
405
        return desc;
25,563✔
406
    }
25,563✔
407
    return "";
×
408
}
25,563✔
409

410
std::string SerialisationState::get_backlink_column_name(ConstTableRef from, ColKey col_key)
411
{
×
412
    ColumnType col_type = col_key.get_type();
×
413
    REALM_ASSERT_EX(col_type == col_type_Link, col_type);
×
414

415
    auto target_table = from->get_opposite_table(col_key);
×
416
    auto backlink_col = from->get_opposite_column(col_key);
×
417
    return get_column_name(target_table, backlink_col);
×
418
}
×
419

420
std::string SerialisationState::describe_columns(const LinkMap& link_map, ColKey target_col_key)
421
{
23,358✔
422
    std::string desc;
23,358✔
423
    if (!subquery_prefix_list.empty()) {
23,358✔
424
        desc += subquery_prefix_list.back();
180✔
425
    }
180✔
426
    if (link_map.links_exist()) {
23,358✔
427
        if (!desc.empty()) {
8,472✔
428
            desc += util::serializer::value_separator;
120✔
429
        }
120✔
430
        desc += link_map.description(*this);
8,472✔
431
    }
8,472✔
432
    ConstTableRef target = link_map.get_target_table();
23,358✔
433
    if (target && target_col_key) {
23,358✔
434
        if (!desc.empty()) {
21,990✔
435
            desc += util::serializer::value_separator;
7,164✔
436
        }
7,164✔
437
        desc += get_column_name(target, target_col_key);
21,990✔
438
    }
21,990✔
439
    return desc;
23,358✔
440
}
23,358✔
441

442
std::string SerialisationState::describe_expression_type(util::Optional<ExpressionComparisonType> type)
443
{
23,856✔
444
    if (type) {
23,856✔
445
        switch (*type) {
7,842✔
446
            case ExpressionComparisonType::Any:
3,270✔
447
                return "ANY ";
3,270✔
448
            case ExpressionComparisonType::All:
2,334✔
449
                return "ALL ";
2,334✔
450
            case ExpressionComparisonType::None:
2,238✔
451
                return "NONE ";
2,238✔
452
        }
7,842✔
453
    }
7,842✔
454
    return "";
16,014✔
455
}
23,856✔
456

457
} // namespace serializer
458
} // namespace util
459
} // namespace realm
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