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

realm / realm-core / 1669

13 Sep 2023 06:44PM UTC coverage: 91.193% (-0.07%) from 91.258%
1669

push

Evergreen

GitHub
Update History Command tool to work with realms with file format version 23 (#6970)

95936 of 175880 branches covered (0.0%)

233596 of 256155 relevant lines covered (91.19%)

6735051.02 hits per line

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

90.03
/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
{
332,592✔
46
    int L = jd + 68569;
332,592✔
47
    int n = (4 * L) / 146097;
332,592✔
48
    int i, j;
332,592✔
49

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

60
// Confirmed to work for all val < 16389
61
static void out_dec(char** buffer, unsigned val, int width)
62
{
1,995,552✔
63
    int w = width;
1,995,552✔
64
    char* p = *buffer;
1,995,552✔
65
    while (width > 0) {
6,651,840✔
66
        width--;
4,656,288✔
67
        unsigned div10 = (val * 6554) >> 16;
4,656,288✔
68
        p[width] = char(val - div10 * 10) + '0';
4,656,288✔
69
        val = div10;
4,656,288✔
70
    }
4,656,288✔
71
    *buffer += w;
1,995,552✔
72
}
1,995,552✔
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(char* buffer) const
78
{
332,592✔
79
    if (is_null()) {
332,592✔
80
        return "null";
×
81
    }
×
82
    int64_t seconds = get_seconds();
332,592✔
83
    int32_t nano = get_nanoseconds();
332,592✔
84
    if (nano < 0) {
332,592✔
85
        nano += Timestamp::nanoseconds_per_second;
6✔
86
        seconds--;
6✔
87
    }
6✔
88

166,296✔
89
    int64_t days = seconds / seconds_in_a_day;
332,592✔
90
    int julian_days = int(days + epoc_julian_days);
332,592✔
91

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

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

166,296✔
106
    julian_to_date(julian_days, &year, &month, &day);
332,592✔
107

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

131
namespace util {
132
namespace serializer {
133

134
template <>
135
std::string print_value<>(BinaryData data)
136
{
3,294✔
137
    if (data.is_null()) {
3,294✔
138
        return "NULL";
102✔
139
    }
102✔
140
    return print_value<StringData>(StringData(data.data(), data.size()));
3,192✔
141
}
3,192✔
142

143
template <>
144
std::string print_value<>(bool b)
145
{
37,752✔
146
    if (b) {
37,752✔
147
        return "true";
24,648✔
148
    }
24,648✔
149
    return "false";
13,104✔
150
}
13,104✔
151

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

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

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

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

187
static bool contains_invalids(StringData data)
188
{
150,210✔
189
    // the custom whitelist is different from std::isprint because it doesn't include quotations
74,145✔
190
    const static std::string whitelist = " {|}~:;<=>?@!#$%&()*+,-./[]^_`";
150,210✔
191
    const char* ptr = data.data();
150,210✔
192
    const char* end = ptr + data.size();
150,210✔
193
    while (ptr < end) {
2,422,206✔
194
        using unsigned_char_t = unsigned char;
2,274,138✔
195
        char c = *ptr;
2,274,138✔
196
        size_t len = sequence_length(c);
2,274,138✔
197
        if (len == 1) {
2,274,138✔
198
            // std::isalnum takes an int, but is undefined for negative values so we must pass an unsigned char
1,119,996✔
199
            if (!std::isalnum(unsigned_char_t(c)) && whitelist.find(c) == std::string::npos) {
2,273,340✔
200
                return true;
1,398✔
201
            }
1,398✔
202
        }
798✔
203
        else {
798✔
204
            // multibyte utf8, check if subsequent bytes have the upper bit set
399✔
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;
2,273,067✔
213
    }
2,271,996✔
214
    return false;
149,139✔
215
}
150,210✔
216

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

74,145✔
227
    if (contains_invalids(data)) {
150,210✔
228
        std::string encode_buffer;
2,142✔
229
        encode_buffer.resize(util::base64_encoded_size(len));
2,142✔
230
        util::base64_encode(start, len, encode_buffer.data(), encode_buffer.size());
2,142✔
231
        out = "B64\"" + encode_buffer + "\"";
2,142✔
232
    }
2,142✔
233
    else {
148,068✔
234
        out.reserve(len + 2);
148,068✔
235
        out += '"';
148,068✔
236
        for (const char* i = start; i != start + len; ++i) {
2,419,854✔
237
            out += *i;
2,271,786✔
238
        }
2,271,786✔
239
        out += '"';
148,068✔
240
    }
148,068✔
241
    return out;
150,210✔
242
}
150,210✔
243

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

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

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

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

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

306
template <>
307
std::string print_value<>(realm::TypeOfValue type)
308
{
378✔
309
    return '"' + type.to_string() + '"';
378✔
310
}
378✔
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
{
240✔
327
    std::string guess_prefix = "$";
240✔
328
    const char start_char = 'x';
240✔
329
    char add_char = start_char;
240✔
330

120✔
331
    auto next_guess = [&]() {
213✔
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

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

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

180✔
368
        return format("@links.%1.%2", source_table_name, source_col_name);
360✔
369
    }
360✔
370
    else if (col_key != ColKey()) {
298,833✔
371
        std::string col_name = table->get_column_name(col_key);
298,833✔
372
        size_t pos = col_name.find_first_of(" \t\r\n");
298,833✔
373
        while (pos != std::string::npos) {
299,157✔
374
            switch (col_name[pos]) {
324✔
375
                case ' ':
114✔
376
                    // space is unchanged
57✔
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;
298,833✔
393
    }
×
394
    return "";
×
395
}
×
396

397
std::string SerialisationState::describe_column(ConstTableRef table, ColKey col_key)
398
{
271,083✔
399
    if (table && col_key) {
271,083✔
400
        std::string desc;
271,083✔
401
        if (!subquery_prefix_list.empty()) {
271,083✔
402
            desc += subquery_prefix_list.back() + value_separator;
66✔
403
        }
66✔
404
        desc += get_column_name(table, col_key);
271,083✔
405
        return desc;
271,083✔
406
    }
271,083✔
407
    return "";
×
408
}
×
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 == col_type_LinkList, 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
{
19,434✔
422
    std::string desc;
19,434✔
423
    if (!subquery_prefix_list.empty()) {
19,434✔
424
        desc += subquery_prefix_list.back();
180✔
425
    }
180✔
426
    if (link_map.links_exist()) {
19,434✔
427
        if (!desc.empty()) {
7,596✔
428
            desc += util::serializer::value_separator;
120✔
429
        }
120✔
430
        desc += link_map.description(*this);
7,596✔
431
    }
7,596✔
432
    ConstTableRef target = link_map.get_target_table();
19,434✔
433
    if (target && target_col_key) {
19,434✔
434
        if (!desc.empty()) {
17,976✔
435
            desc += util::serializer::value_separator;
6,198✔
436
        }
6,198✔
437
        desc += get_column_name(target, target_col_key);
17,976✔
438
    }
17,976✔
439
    return desc;
19,434✔
440
}
19,434✔
441

442
std::string SerialisationState::describe_expression_type(util::Optional<ExpressionComparisonType> type)
443
{
19,224✔
444
    if (type) {
19,224✔
445
        switch (*type) {
6,312✔
446
            case ExpressionComparisonType::Any:
2,730✔
447
                return "ANY ";
2,730✔
448
            case ExpressionComparisonType::All:
1,842✔
449
                return "ALL ";
1,842✔
450
            case ExpressionComparisonType::None:
1,740✔
451
                return "NONE ";
1,740✔
452
        }
12,912✔
453
    }
12,912✔
454
    return "";
12,912✔
455
}
12,912✔
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