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

realm / realm-core / 1850

20 Nov 2023 10:36AM UTC coverage: 91.692% (-0.2%) from 91.907%
1850

push

Evergreen

web-flow
Stop failing when sync is disabled and MacOs debug tests are set to run (#7146)

92316 of 169214 branches covered (0.0%)

231566 of 252547 relevant lines covered (91.69%)

6538009.25 hits per line

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

95.39
/src/realm/object-store/object_store.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2015 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/object-store/object_store.hpp>
20

21
#include <realm/object-store/feature_checks.hpp>
22
#include <realm/object-store/object_schema.hpp>
23
#include <realm/object-store/schema.hpp>
24
#include <realm/object-store/shared_realm.hpp>
25

26
#include <realm/group.hpp>
27
#include <realm/table.hpp>
28
#include <realm/table_view.hpp>
29
#include <realm/util/assert.hpp>
30

31
#if REALM_ENABLE_SYNC
32
#include <realm/sync/instruction_replication.hpp>
33
#endif // REALM_ENABLE_SYNC
34

35
#include <string.h>
36
#include <unordered_set>
37

38
using namespace realm;
39

40
constexpr uint64_t ObjectStore::NotVersioned;
41

42
namespace {
43
const char* const c_metadataTableName = "metadata";
44
const char* const c_versionColumnName = "version";
45

46
const char c_object_table_prefix[] = "class_";
47

48
const char* const c_development_mode_msg =
49
    "If your app is running in development mode, you can delete the realm and restart the app to update your schema.";
50

51
void create_metadata_tables(Group& group)
52
{
43,206✔
53
    // The 'metadata' table is simply ignored by Sync
21,377✔
54
    TableRef metadata_table = group.get_or_add_table(c_metadataTableName);
43,206✔
55

21,377✔
56
    if (metadata_table->get_column_count() == 0) {
43,206✔
57
        metadata_table->add_column(type_Int, c_versionColumnName);
21,260✔
58
        metadata_table->create_object().set(c_versionColumnName, int64_t(ObjectStore::NotVersioned));
21,260✔
59
    }
21,260✔
60
}
43,206✔
61

62
void set_schema_version(Group& group, uint64_t version)
63
{
21,490✔
64
    group.get_table(c_metadataTableName)->get_object(0).set<int64_t>(c_versionColumnName, version);
21,490✔
65
}
21,490✔
66

67
template <typename Group>
68
auto table_for_object_schema(Group& group, ObjectSchema const& object_schema)
69
{
61,621✔
70
    return ObjectStore::table_for_object_type(group, object_schema.name);
61,621✔
71
}
61,621✔
72

73
DataType to_core_type(PropertyType type)
74
{
195,210✔
75
    REALM_ASSERT(type != PropertyType::Object); // Link columns have to be handled differently
195,210✔
76
    switch (type & ~PropertyType::Flags) {
195,210✔
77
        case PropertyType::Int:
48,032✔
78
            return type_Int;
48,032✔
79
        case PropertyType::Bool:
5,472✔
80
            return type_Bool;
5,472✔
81
        case PropertyType::Float:
5,656✔
82
            return type_Float;
5,656✔
83
        case PropertyType::Double:
5,970✔
84
            return type_Double;
5,970✔
85
        case PropertyType::String:
89,074✔
86
            return type_String;
89,074✔
87
        case PropertyType::Date:
6,066✔
88
            return type_Timestamp;
6,066✔
89
        case PropertyType::Data:
5,528✔
90
            return type_Binary;
5,528✔
91
        case PropertyType::ObjectId:
14,100✔
92
            return type_ObjectId;
14,100✔
93
        case PropertyType::Decimal:
5,674✔
94
            return type_Decimal;
5,674✔
95
        case PropertyType::UUID:
5,624✔
96
            return type_UUID;
5,624✔
97
        case PropertyType::Mixed:
4,014✔
98
            return type_Mixed;
4,014✔
99
        default:
✔
100
            REALM_COMPILER_HINT_UNREACHABLE();
×
101
    }
195,210✔
102
}
195,210✔
103

104
ColKey add_column(Group& group, Table& table, Property const& property)
105
{
190,760✔
106
    // Cannot directly insert a LinkingObjects column (a computed property).
94,042✔
107
    // LinkingObjects must be an artifact of an existing link column.
94,042✔
108
    REALM_ASSERT(property.type != PropertyType::LinkingObjects);
190,760✔
109

94,042✔
110
    if (property.is_primary) {
190,760✔
111
        // Primary key columns should have been created when the table was created
5✔
112
        if (auto col = table.get_column_key(property.name)) {
10✔
113
            return col;
×
114
        }
×
115
    }
190,760✔
116
    if (property.type == PropertyType::Object) {
190,760✔
117
        auto target_name = ObjectStore::table_name_for_object_type(property.object_type);
24,213✔
118
        TableRef link_table = group.get_table(target_name);
24,213✔
119
        REALM_ASSERT(link_table);
24,213✔
120
        if (is_array(property.type)) {
24,213✔
121
            return table.add_column_list(*link_table, property.name);
8,631✔
122
        }
8,631✔
123
        else if (is_set(property.type)) {
15,582✔
124
            return table.add_column_set(*link_table, property.name);
3,452✔
125
        }
3,452✔
126
        else if (is_dictionary(property.type)) {
12,130✔
127
            return table.add_column_dictionary(*link_table, property.name);
3,608✔
128
        }
3,608✔
129
        else {
8,522✔
130
            return table.add_column(*link_table, property.name);
8,522✔
131
        }
8,522✔
132
    }
166,547✔
133
    else if (is_array(property.type)) {
166,547✔
134
        auto key = table.add_column_list(to_core_type(property.type & ~PropertyType::Flags), property.name,
22,339✔
135
                                         is_nullable(property.type));
22,339✔
136
        if (property.requires_index())
22,339✔
137
            table.add_search_index(key);
2✔
138
        return key;
22,339✔
139
    }
22,339✔
140
    else if (is_set(property.type)) {
144,208✔
141
        return table.add_column_set(to_core_type(property.type & ~PropertyType::Flags), property.name,
17,227✔
142
                                    is_nullable(property.type));
17,227✔
143
    }
17,227✔
144
    else if (is_dictionary(property.type)) {
126,981✔
145
        return table.add_column_dictionary(to_core_type(property.type & ~PropertyType::Flags), property.name,
13,992✔
146
                                           is_nullable(property.type));
13,992✔
147
    }
13,992✔
148
    else {
112,989✔
149
        auto key = table.add_column(to_core_type(property.type), property.name, is_nullable(property.type));
112,989✔
150
        if (property.requires_index())
112,989✔
151
            table.add_search_index(key);
540✔
152
        if (property.requires_fulltext_index())
112,989✔
153
            table.add_fulltext_index(key);
2✔
154
        return key;
112,989✔
155
    }
112,989✔
156
}
190,760✔
157

158
void replace_column(Group& group, Table& table, Property const& old_property, Property const& new_property)
159
{
16✔
160
    table.remove_column(old_property.column_key);
16✔
161
    add_column(group, table, new_property);
16✔
162
}
16✔
163

164
TableRef create_table(Group& group, ObjectSchema const& object_schema)
165
{
60,189✔
166
    auto name = ObjectStore::table_name_for_object_type(object_schema.name);
60,189✔
167

29,799✔
168
    TableRef table = group.get_table(name);
60,189✔
169
    if (table)
60,189✔
170
        return table;
6✔
171

29,796✔
172
    if (auto* pk_property = object_schema.primary_key_property()) {
60,183✔
173
        auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
28,663✔
174
                              ? Table::Type::TopLevelAsymmetric
14,181✔
175
                              : Table::Type::TopLevel;
28,645✔
176
        table = group.add_table_with_primary_key(name, to_core_type(pk_property->type), pk_property->name,
28,663✔
177
                                                 is_nullable(pk_property->type), table_type);
28,663✔
178
    }
28,663✔
179
    else {
31,520✔
180
        if (object_schema.table_type == ObjectSchema::ObjectType::Embedded) {
31,520✔
181
            table = group.add_table(name, Table::Type::Embedded);
5,463✔
182
        }
5,463✔
183
        else {
26,057✔
184
            auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
26,057✔
185
                                  ? Table::Type::TopLevelAsymmetric
12,955✔
186
                                  : Table::Type::TopLevel;
26,057✔
187
            table = group.get_or_add_table(name, table_type);
26,057✔
188
        }
26,057✔
189
    }
31,520✔
190

29,796✔
191
    return table;
60,183✔
192
}
60,183✔
193

194
void add_initial_columns(Group& group, ObjectSchema const& object_schema)
195
{
60,183✔
196
    auto name = ObjectStore::table_name_for_object_type(object_schema.name);
60,183✔
197
    TableRef table = group.get_table(name);
60,183✔
198

29,796✔
199
    for (auto const& prop : object_schema.persisted_properties) {
219,265✔
200
#if REALM_ENABLE_SYNC
219,265✔
201
        // The sync::create_table* functions create the PK column for us.
108,126✔
202
        if (prop.is_primary)
219,265✔
203
            continue;
28,663✔
204
#endif // REALM_ENABLE_SYNC
190,602✔
205
        add_column(group, *table, prop);
190,602✔
206
    }
190,602✔
207
}
60,183✔
208

209
void make_property_optional(Table& table, Property property)
210
{
8✔
211
    property.type |= PropertyType::Nullable;
8✔
212
    const bool throw_on_null = false;
8✔
213
    property.column_key = table.set_nullability(property.column_key, true, throw_on_null);
8✔
214
}
8✔
215

216
void make_property_required(Group& group, Table& table, Property property)
217
{
8✔
218
    property.type &= ~PropertyType::Nullable;
8✔
219
    table.remove_column(property.column_key);
8✔
220
    property.column_key = add_column(group, table, property);
8✔
221
}
8✔
222

223
void add_search_index(Table& table, Property property, IndexType type)
224
{
24✔
225
    table.add_search_index(table.get_column_key(property.name), type);
24✔
226
}
24✔
227

228
void remove_search_index(Table& table, Property property)
229
{
12✔
230
    table.remove_search_index(table.get_column_key(property.name));
12✔
231
}
12✔
232

233
} // anonymous namespace
234

235
void ObjectStore::set_schema_version(Group& group, uint64_t version)
236
{
21,490✔
237
    ::create_metadata_tables(group);
21,490✔
238
    ::set_schema_version(group, version);
21,490✔
239
}
21,490✔
240

241
uint64_t ObjectStore::get_schema_version(Group const& group)
242
{
67,812✔
243
    ConstTableRef table = group.get_table(c_metadataTableName);
67,812✔
244
    if (!table || table->get_column_count() == 0) {
67,812✔
245
        return ObjectStore::NotVersioned;
21,580✔
246
    }
21,580✔
247
    return table->get_object(0).get<int64_t>(c_versionColumnName);
46,232✔
248
}
46,232✔
249

250
StringData ObjectStore::object_type_for_table_name(StringData table_name)
251
{
330,658✔
252
    if (table_name.begins_with(c_object_table_prefix)) {
330,658✔
253
        return table_name.substr(sizeof(c_object_table_prefix) - 1);
264,840✔
254
    }
264,840✔
255
    return StringData();
65,818✔
256
}
65,818✔
257

258
std::string ObjectStore::table_name_for_object_type(StringData object_type)
259
{
274,238✔
260
    return std::string(c_object_table_prefix) + std::string(object_type);
274,238✔
261
}
274,238✔
262

263
TableRef ObjectStore::table_for_object_type(Group& group, StringData object_type)
264
{
67,402✔
265
    auto name = table_name_for_object_type(object_type);
67,402✔
266
    return group.get_table(name);
67,402✔
267
}
67,402✔
268

269
ConstTableRef ObjectStore::table_for_object_type(Group const& group, StringData object_type)
270
{
61,285✔
271
    auto name = table_name_for_object_type(object_type);
61,285✔
272
    return group.get_table(name);
61,285✔
273
}
61,285✔
274

275
namespace {
276
struct SchemaDifferenceExplainer {
277
    std::vector<ObjectSchemaValidationException> errors;
278

279
    void operator()(schema_change::AddTable op)
280
    {
4✔
281
        errors.emplace_back("Class '%1' has been added.", op.object->name);
4✔
282
    }
4✔
283

284
    void operator()(schema_change::RemoveTable)
285
    {
196✔
286
        // We never do anything for RemoveTable
98✔
287
    }
196✔
288

289
    void operator()(schema_change::ChangeTableType op)
290
    {
12✔
291
        errors.emplace_back("Class '%1' has been changed from %2 to %3.", op.object->name, *op.old_table_type,
12✔
292
                            *op.new_table_type);
12✔
293
    }
12✔
294

295
    void operator()(schema_change::AddInitialProperties)
296
    {
4✔
297
        // Nothing. Always preceded by AddTable.
2✔
298
    }
4✔
299

300
    void operator()(schema_change::AddProperty op)
301
    {
26✔
302
        errors.emplace_back("Property '%1.%2' has been added.", op.object->name, op.property->name);
26✔
303
    }
26✔
304

305
    void operator()(schema_change::RemoveProperty op)
306
    {
16✔
307
        errors.emplace_back("Property '%1.%2' has been removed.", op.object->name, op.property->name);
16✔
308
    }
16✔
309

310
    void operator()(schema_change::ChangePropertyType op)
311
    {
48✔
312
        errors.emplace_back("Property '%1.%2' has been changed from '%3' to '%4'.", op.object->name,
48✔
313
                            op.new_property->name, op.old_property->type_string(), op.new_property->type_string());
48✔
314
    }
48✔
315

316
    void operator()(schema_change::MakePropertyNullable op)
317
    {
12✔
318
        errors.emplace_back("Property '%1.%2' has been made optional.", op.object->name, op.property->name);
12✔
319
    }
12✔
320

321
    void operator()(schema_change::MakePropertyRequired op)
322
    {
10✔
323
        errors.emplace_back("Property '%1.%2' has been made required.", op.object->name, op.property->name);
10✔
324
    }
10✔
325

326
    void operator()(schema_change::ChangePrimaryKey op)
327
    {
26✔
328
        if (op.property && !op.object->primary_key.empty()) {
26✔
329
            errors.emplace_back("Primary Key for class '%1' has changed from '%2' to '%3'.", op.object->name,
4✔
330
                                op.object->primary_key, op.property->name);
4✔
331
        }
4✔
332
        else if (op.property) {
22✔
333
            errors.emplace_back("Primary Key for class '%1' has been added.", op.object->name);
10✔
334
        }
10✔
335
        else {
12✔
336
            errors.emplace_back("Primary Key for class '%1' has been removed.", op.object->name);
12✔
337
        }
12✔
338
    }
26✔
339

340
    void operator()(schema_change::AddIndex op)
341
    {
8✔
342
        errors.emplace_back("Property '%1.%2' has been made indexed.", op.object->name, op.property->name);
8✔
343
    }
8✔
344

345
    void operator()(schema_change::RemoveIndex op)
346
    {
12✔
347
        errors.emplace_back("Property '%1.%2' has been made unindexed.", op.object->name, op.property->name);
12✔
348
    }
12✔
349
};
350

351
class TableHelper {
352
public:
353
    TableHelper(Group& g)
354
        : m_group(g)
355
    {
21,856✔
356
    }
21,856✔
357

358
    Table& operator()(const ObjectSchema* object_schema)
359
    {
370✔
360
        if (object_schema != m_current_object_schema) {
370✔
361
            m_current_table = table_for_object_schema(m_group, *object_schema);
294✔
362
            m_current_object_schema = object_schema;
294✔
363
        }
294✔
364
        REALM_ASSERT(m_current_table);
370✔
365
        return *m_current_table;
370✔
366
    }
370✔
367

368
private:
369
    Group& m_group;
370
    const ObjectSchema* m_current_object_schema = nullptr;
371
    TableRef m_current_table;
372
};
373

374
template <typename ErrorType, typename Verifier>
375
void verify_no_errors(Verifier&& verifier, std::vector<SchemaChange> const& changes)
376
{
8,807✔
377
    for (auto& change : changes) {
39,866✔
378
        change.visit(verifier);
39,866✔
379
    }
39,866✔
380

4,317✔
381
    if (!verifier.errors.empty()) {
8,807✔
382
        throw ErrorType(verifier.errors);
162✔
383
    }
162✔
384
}
8,807✔
385
} // anonymous namespace
386

387
bool ObjectStore::needs_migration(std::vector<SchemaChange> const& changes)
388
{
10✔
389
    using namespace schema_change;
10✔
390
    struct Visitor {
10✔
391
        bool operator()(AddIndex)
10✔
392
        {
7✔
393
            return false;
4✔
394
        }
4✔
395
        bool operator()(AddInitialProperties)
10✔
396
        {
6✔
397
            return false;
2✔
398
        }
2✔
399
        bool operator()(AddProperty)
10✔
400
        {
6✔
401
            return true;
2✔
402
        }
2✔
403
        bool operator()(AddTable)
10✔
404
        {
6✔
405
            return false;
2✔
406
        }
2✔
407
        bool operator()(RemoveTable)
10✔
408
        {
5✔
409
            return false;
×
410
        }
×
411
        bool operator()(ChangeTableType)
10✔
412
        {
5✔
413
            return true;
×
414
        }
×
415
        bool operator()(ChangePrimaryKey)
10✔
416
        {
5✔
417
            return true;
×
418
        }
×
419
        bool operator()(ChangePropertyType)
10✔
420
        {
5✔
421
            return true;
×
422
        }
×
423
        bool operator()(MakePropertyNullable)
10✔
424
        {
5✔
425
            return true;
×
426
        }
×
427
        bool operator()(MakePropertyRequired)
10✔
428
        {
5✔
429
            return true;
×
430
        }
×
431
        bool operator()(RemoveIndex)
10✔
432
        {
6✔
433
            return false;
2✔
434
        }
2✔
435
        bool operator()(RemoveProperty)
10✔
436
        {
5✔
437
            return true;
×
438
        }
×
439
    };
10✔
440

5✔
441
    return std::any_of(begin(changes), end(changes), [](auto&& change) {
12✔
442
        return change.visit(Visitor());
12✔
443
    });
12✔
444
}
10✔
445

446
void ObjectStore::verify_no_changes_required(std::vector<SchemaChange> const& changes)
447
{
86✔
448
    verify_no_errors<SchemaMismatchException>(SchemaDifferenceExplainer(), changes);
86✔
449
}
86✔
450

451
void ObjectStore::verify_no_migration_required(std::vector<SchemaChange> const& changes)
452
{
2✔
453
    using namespace schema_change;
2✔
454
    struct Verifier : SchemaDifferenceExplainer {
2✔
455
        using SchemaDifferenceExplainer::operator();
2✔
456

1✔
457
        // Adding a table or adding/removing indexes can be done automatically.
1✔
458
        // All other changes require migrations.
1✔
459
        void operator()(AddTable) {}
1✔
460
        void operator()(AddInitialProperties) {}
1✔
461
        void operator()(AddIndex) {}
1✔
462
        void operator()(RemoveIndex) {}
1✔
463
    } verifier;
2✔
464
    verify_no_errors<SchemaMismatchException>(verifier, changes);
2✔
465
}
2✔
466

467
bool ObjectStore::verify_valid_additive_changes(std::vector<SchemaChange> const& changes, bool update_indexes)
468
{
7,905✔
469
    using namespace schema_change;
7,905✔
470
    struct Verifier : SchemaDifferenceExplainer {
7,905✔
471
        using SchemaDifferenceExplainer::operator();
7,905✔
472

3,870✔
473
        bool index_changes = false;
7,905✔
474
        bool other_changes = false;
7,905✔
475

3,870✔
476
        // Additive mode allows adding things, extra columns, and adding/removing indexes
3,870✔
477
        void operator()(AddTable)
7,905✔
478
        {
19,012✔
479
            other_changes = true;
19,012✔
480
        }
19,012✔
481
        void operator()(AddInitialProperties)
7,905✔
482
        {
19,012✔
483
            other_changes = true;
19,012✔
484
        }
19,012✔
485
        void operator()(AddProperty)
7,905✔
486
        {
3,903✔
487
            other_changes = true;
66✔
488
        }
66✔
489
        void operator()(RemoveProperty) {}
3,933✔
490
        void operator()(AddIndex)
7,905✔
491
        {
3,879✔
492
            index_changes = true;
18✔
493
        }
18✔
494
        void operator()(RemoveIndex)
7,905✔
495
        {
3,876✔
496
            index_changes = true;
12✔
497
        }
12✔
498
    } verifier;
7,905✔
499
    verify_no_errors<InvalidAdditiveSchemaChangeException>(verifier, changes);
7,905✔
500
    return verifier.other_changes || (verifier.index_changes && update_indexes);
7,905✔
501
}
7,905✔
502

503
void ObjectStore::verify_valid_external_changes(std::vector<SchemaChange> const& changes)
504
{
480✔
505
    using namespace schema_change;
480✔
506
    struct Verifier : SchemaDifferenceExplainer {
480✔
507
        using SchemaDifferenceExplainer::operator();
480✔
508

236✔
509
        // Adding new things is fine
236✔
510
        void operator()(AddTable) {}
244✔
511
        void operator()(AddInitialProperties) {}
244✔
512
        void operator()(AddProperty) {}
252✔
513
        void operator()(AddIndex) {}
1,062✔
514
        void operator()(RemoveIndex) {}
239✔
515
        // Deleting tables is not okay
236✔
516
        void operator()(RemoveTable op)
480✔
517
        {
237✔
518
            errors.emplace_back("Class '%1' has been removed.", op.object->name);
2✔
519
        }
2✔
520
    } verifier;
480✔
521
    verify_no_errors<InvalidExternalSchemaChangeException>(verifier, changes);
480✔
522
}
480✔
523

524
void ObjectStore::verify_compatible_for_immutable_and_readonly(std::vector<SchemaChange> const& changes)
525
{
276✔
526
    using namespace schema_change;
276✔
527
    struct Verifier : SchemaDifferenceExplainer {
276✔
528
        using SchemaDifferenceExplainer::operator();
276✔
529

138✔
530
        void operator()(AddTable) {}
145✔
531
        void operator()(AddInitialProperties) {}
145✔
532
        void operator()(ChangeTableType) {}
140✔
533
        void operator()(RemoveProperty) {}
143✔
534
        void operator()(AddIndex) {}
140✔
535
        void operator()(RemoveIndex) {}
140✔
536
    } verifier;
276✔
537
    verify_no_errors<InvalidReadOnlySchemaChangeException>(verifier, changes);
276✔
538
}
276✔
539

540
static void apply_non_migration_changes(Group& group, std::vector<SchemaChange> const& changes)
541
{
58✔
542
    using namespace schema_change;
58✔
543
    struct Applier : SchemaDifferenceExplainer {
58✔
544
        Applier(Group& group)
58✔
545
            : group{group}
58✔
546
            , table{group}
58✔
547
        {
58✔
548
        }
58✔
549
        Group& group;
58✔
550
        TableHelper table;
58✔
551

29✔
552
        // Produce an exception listing the unsupported schema changes for
29✔
553
        // everything but the explicitly supported ones
29✔
554
        using SchemaDifferenceExplainer::operator();
58✔
555

29✔
556
        void operator()(AddTable op)
58✔
557
        {
41✔
558
            create_table(group, *op.object);
24✔
559
        }
24✔
560
        void operator()(AddInitialProperties op)
58✔
561
        {
41✔
562
            add_initial_columns(group, *op.object);
24✔
563
        }
24✔
564
        void operator()(AddIndex op)
58✔
565
        {
33✔
566
            table(op.object).add_search_index(op.property->column_key, op.type);
8✔
567
        }
8✔
568
        void operator()(RemoveIndex op)
58✔
569
        {
32✔
570
            table(op.object).remove_search_index(op.property->column_key);
6✔
571
        }
6✔
572
    } applier{group};
58✔
573
    verify_no_errors<SchemaMismatchException>(applier, changes);
58✔
574
}
58✔
575

576
static void set_primary_key(Table& table, const Property* property)
577
{
44✔
578
    ColKey col;
44✔
579
    if (property) {
44✔
580
        col = table.get_column_key(property->name);
38✔
581
        REALM_ASSERT(col);
38✔
582
    }
38✔
583
    table.set_primary_key_column(col);
44✔
584
}
44✔
585

586
static void create_initial_tables(Group& group, std::vector<SchemaChange> const& changes)
587
{
13,576✔
588
    using namespace schema_change;
13,576✔
589
    struct Applier {
13,576✔
590
        Applier(Group& group)
13,576✔
591
            : group{group}
13,576✔
592
            , table{group}
13,576✔
593
        {
13,576✔
594
        }
13,576✔
595
        Group& group;
13,576✔
596
        TableHelper table;
13,576✔
597

6,750✔
598
        void operator()(AddTable op)
13,576✔
599
        {
41,152✔
600
            create_table(group, *op.object);
41,152✔
601
        }
41,152✔
602
        void operator()(RemoveTable) {}
6,750✔
603
        void operator()(AddInitialProperties op)
13,576✔
604
        {
41,152✔
605
            add_initial_columns(group, *op.object);
41,152✔
606
        }
41,152✔
607

6,750✔
608
        // Note that in normal operation none of these will be hit, as if we're
6,750✔
609
        // creating the initial tables there shouldn't be anything to update.
6,750✔
610
        // Implementing these makes us better able to handle weird
6,750✔
611
        // not-quite-correct files produced by other things and has no obvious
6,750✔
612
        // downside.
6,750✔
613
        void operator()(ChangeTableType op)
13,576✔
614
        {
6,750✔
615
            table(op.object).set_table_type(static_cast<Table::Type>(*op.new_table_type), false);
×
616
        }
×
617
        void operator()(AddProperty op)
13,576✔
618
        {
6,750✔
619
            add_column(group, table(op.object), *op.property);
×
620
        }
×
621
        void operator()(RemoveProperty op)
13,576✔
622
        {
6,750✔
623
            table(op.object).remove_column(op.property->column_key);
×
624
        }
×
625
        void operator()(MakePropertyNullable op)
13,576✔
626
        {
6,750✔
627
            make_property_optional(table(op.object), *op.property);
×
628
        }
×
629
        void operator()(MakePropertyRequired op)
13,576✔
630
        {
6,750✔
631
            make_property_required(group, table(op.object), *op.property);
×
632
        }
×
633
        void operator()(ChangePrimaryKey op)
13,576✔
634
        {
6,750✔
635
            set_primary_key(table(op.object), op.property);
×
636
        }
×
637
        void operator()(AddIndex op)
13,576✔
638
        {
6,750✔
639
            add_search_index(table(op.object), *op.property, op.type);
×
640
        }
×
641
        void operator()(RemoveIndex op)
13,576✔
642
        {
6,750✔
643
            remove_search_index(table(op.object), *op.property);
×
644
        }
×
645

6,750✔
646
        void operator()(ChangePropertyType op)
13,576✔
647
        {
6,750✔
648
            replace_column(group, table(op.object), *op.old_property, *op.new_property);
×
649
        }
×
650
    } applier{group};
13,576✔
651

6,750✔
652
    for (auto& change : changes) {
82,304✔
653
        change.visit(applier);
82,304✔
654
    }
82,304✔
655
}
13,576✔
656

657
void ObjectStore::apply_additive_changes(Group& group, std::vector<SchemaChange> const& changes, bool update_indexes)
658
{
7,792✔
659
    using namespace schema_change;
7,792✔
660
    struct Applier {
7,792✔
661
        Applier(Group& group, bool update_indexes)
7,792✔
662
            : group{group}
7,792✔
663
            , table{group}
7,792✔
664
            , update_indexes{update_indexes}
7,792✔
665
        {
7,792✔
666
        }
7,792✔
667
        Group& group;
7,792✔
668
        TableHelper table;
7,792✔
669
        bool update_indexes;
7,792✔
670

3,821✔
671
        void operator()(AddTable op)
7,792✔
672
        {
18,991✔
673
            create_table(group, *op.object);
18,991✔
674
        }
18,991✔
675
        void operator()(RemoveTable) {}
3,821✔
676
        void operator()(AddInitialProperties op)
7,792✔
677
        {
18,991✔
678
            add_initial_columns(group, *op.object);
18,991✔
679
        }
18,991✔
680
        void operator()(AddProperty op)
7,792✔
681
        {
3,854✔
682
            add_column(group, table(op.object), *op.property);
66✔
683
        }
66✔
684
        void operator()(AddIndex op)
7,792✔
685
        {
3,826✔
686
            if (update_indexes) {
10✔
687
                add_search_index(table(op.object), *op.property, op.type);
10✔
688
            }
10✔
689
        }
10✔
690
        void operator()(RemoveIndex op)
7,792✔
691
        {
3,823✔
692
            if (update_indexes)
4✔
693
                table(op.object).remove_search_index(op.property->column_key);
4✔
694
        }
4✔
695
        void operator()(RemoveProperty) {}
3,830✔
696

3,821✔
697
        // No need for errors for these, as we've already verified that they aren't present
3,821✔
698
        void operator()(ChangeTableType) {}
3,821✔
699
        void operator()(ChangePrimaryKey) {}
3,821✔
700
        void operator()(ChangePropertyType) {}
3,821✔
701
        void operator()(MakePropertyNullable) {}
3,821✔
702
        void operator()(MakePropertyRequired) {}
3,821✔
703
    } applier{group, update_indexes};
7,792✔
704

3,821✔
705
    for (auto& change : changes) {
38,080✔
706
        change.visit(applier);
38,080✔
707
    }
38,080✔
708
}
7,792✔
709

710
static void apply_pre_migration_changes(Group& group, std::vector<SchemaChange> const& changes)
711
{
228✔
712
    using namespace schema_change;
228✔
713
    struct Applier {
228✔
714
        Applier(Group& group)
228✔
715
            : group{group}
228✔
716
            , table{group}
228✔
717
        {
228✔
718
        }
228✔
719
        Group& group;
228✔
720
        TableHelper table;
228✔
721

114✔
722
        void operator()(AddTable op)
228✔
723
        {
121✔
724
            create_table(group, *op.object);
14✔
725
        }
14✔
726
        void operator()(RemoveTable) {}
114✔
727
        void operator()(ChangeTableType)
228✔
728
        { /* delayed until after the migration */
139✔
729
        }
50✔
730
        void operator()(AddInitialProperties op)
228✔
731
        {
121✔
732
            add_initial_columns(group, *op.object);
14✔
733
        }
14✔
734
        void operator()(AddProperty op)
228✔
735
        {
148✔
736
            add_column(group, table(op.object), *op.property);
68✔
737
        }
68✔
738
        void operator()(RemoveProperty)
228✔
739
        { /* delayed until after the migration */
168✔
740
        }
108✔
741
        void operator()(ChangePropertyType op)
228✔
742
        {
122✔
743
            replace_column(group, table(op.object), *op.old_property, *op.new_property);
16✔
744
        }
16✔
745
        void operator()(MakePropertyNullable op)
228✔
746
        {
117✔
747
            make_property_optional(table(op.object), *op.property);
6✔
748
        }
6✔
749
        void operator()(MakePropertyRequired op)
228✔
750
        {
118✔
751
            make_property_required(group, table(op.object), *op.property);
8✔
752
        }
8✔
753
        void operator()(ChangePrimaryKey op)
228✔
754
        {
127✔
755
            table(op.object).set_primary_key_column(ColKey{});
26✔
756
        }
26✔
757
        void operator()(AddIndex op)
228✔
758
        {
121✔
759
            add_search_index(table(op.object), *op.property, op.type);
14✔
760
        }
14✔
761
        void operator()(RemoveIndex op)
228✔
762
        {
120✔
763
            remove_search_index(table(op.object), *op.property);
12✔
764
        }
12✔
765
    } applier{group};
228✔
766

114✔
767
    for (auto& change : changes) {
336✔
768
        change.visit(applier);
336✔
769
    }
336✔
770
}
228✔
771

772
enum class DidRereadSchema { Yes, No };
773
enum class HandleBackLinksAutomatically { Yes, No };
774

775
static void apply_post_migration_changes(Group& group, std::vector<SchemaChange> const& changes,
776
                                         Schema const& initial_schema, DidRereadSchema did_reread_schema,
777
                                         HandleBackLinksAutomatically handle_backlinks_automatically)
778
{
202✔
779
    using namespace schema_change;
202✔
780
    struct Applier {
202✔
781
        Applier(Group& group, Schema const& initial_schema, DidRereadSchema did_reread_schema,
202✔
782
                HandleBackLinksAutomatically handle_backlinks_automatically)
202✔
783
            : group{group}
202✔
784
            , initial_schema(initial_schema)
202✔
785
            , table(group)
202✔
786
            , did_reread_schema(did_reread_schema == DidRereadSchema::Yes)
202✔
787
            , handle_backlinks_automatically(handle_backlinks_automatically == HandleBackLinksAutomatically::Yes)
202✔
788
        {
202✔
789
        }
202✔
790
        Group& group;
202✔
791
        Schema const& initial_schema;
202✔
792
        TableHelper table;
202✔
793
        bool did_reread_schema;
202✔
794
        bool handle_backlinks_automatically;
202✔
795

101✔
796
        void operator()(RemoveProperty op)
202✔
797
        {
140✔
798
            if (!initial_schema.empty() &&
78✔
799
                !initial_schema.find(op.object->name)->property_for_name(op.property->name))
76✔
800
                throw LogicError(ErrorCodes::InvalidProperty, util::format("Renamed property '%1.%2' does not exist.",
2✔
801
                                                                           op.object->name, op.property->name));
2✔
802
            auto table = table_for_object_schema(group, *op.object);
76✔
803
            table->remove_column(op.property->column_key);
76✔
804
        }
76✔
805

101✔
806
        void operator()(ChangePrimaryKey op)
202✔
807
        {
123✔
808
            set_primary_key(table(op.object), op.property);
44✔
809
        }
44✔
810

101✔
811
        void operator()(AddTable op)
202✔
812
        {
105✔
813
            create_table(group, *op.object);
8✔
814
        }
8✔
815

101✔
816
        void operator()(AddInitialProperties op)
202✔
817
        {
105✔
818
            if (did_reread_schema)
8✔
819
                add_initial_columns(group, *op.object);
2✔
820
            else {
6✔
821
                // If we didn't re-read the schema then AddInitialProperties was already taken care of
3✔
822
                // during apply_pre_migration_changes.
3✔
823
            }
6✔
824
        }
8✔
825

101✔
826
        void operator()(AddIndex op)
202✔
827
        {
114✔
828
            table(op.object).add_search_index(op.property->column_key);
26✔
829
        }
26✔
830
        void operator()(RemoveIndex op)
202✔
831
        {
104✔
832
            table(op.object).remove_search_index(op.property->column_key);
6✔
833
        }
6✔
834

101✔
835
        void operator()(ChangeTableType op)
202✔
836
        {
126✔
837
            table(op.object).set_table_type(static_cast<Table::Type>(*op.new_table_type),
50✔
838
                                            handle_backlinks_automatically);
50✔
839
        }
50✔
840
        void operator()(RemoveTable) {}
101✔
841
        void operator()(ChangePropertyType) {}
104✔
842
        void operator()(MakePropertyNullable) {}
103✔
843
        void operator()(MakePropertyRequired) {}
103✔
844
        void operator()(AddProperty) {}
104✔
845
    } applier{group, initial_schema, did_reread_schema, handle_backlinks_automatically};
202✔
846

101✔
847
    for (auto& change : changes) {
240✔
848
        change.visit(applier);
240✔
849
    }
240✔
850
}
202✔
851

852

853
void ObjectStore::apply_schema_changes(Transaction& group, uint64_t schema_version, Schema& target_schema,
854
                                       uint64_t target_schema_version, SchemaMode mode,
855
                                       std::vector<SchemaChange> const& changes, bool handle_automatically_backlinks,
856
                                       std::function<void()> migration_function)
857
{
21,716✔
858
    create_metadata_tables(group);
21,716✔
859

10,745✔
860
    if (mode == SchemaMode::AdditiveDiscovered || mode == SchemaMode::AdditiveExplicit) {
21,716✔
861
        bool target_schema_is_newer =
7,792✔
862
            (schema_version < target_schema_version || schema_version == ObjectStore::NotVersioned);
7,792✔
863

3,821✔
864
        // With sync v2.x, indexes are no longer synced, so there's no reason to avoid creating them.
3,821✔
865
        bool update_indexes = true;
7,792✔
866
        apply_additive_changes(group, changes, update_indexes);
7,792✔
867

3,821✔
868
        if (target_schema_is_newer)
7,792✔
869
            set_schema_version(group, target_schema_version);
7,690✔
870

3,821✔
871
        set_schema_keys(group, target_schema);
7,792✔
872
        return;
7,792✔
873
    }
7,792✔
874

6,924✔
875
    if (schema_version == ObjectStore::NotVersioned) {
13,924✔
876
        if (mode != SchemaMode::ReadOnly) {
13,580✔
877
            create_initial_tables(group, changes);
13,576✔
878
        }
13,576✔
879
        set_schema_version(group, target_schema_version);
13,580✔
880
        set_schema_keys(group, target_schema);
13,580✔
881
        return;
13,580✔
882
    }
13,580✔
883

172✔
884
    if (mode == SchemaMode::Manual) {
344✔
885
        if (migration_function) {
58✔
886
            migration_function();
54✔
887
        }
54✔
888

29✔
889
        verify_no_changes_required(schema_from_group(group).compare(target_schema));
58✔
890
        group.validate_primary_columns();
58✔
891
        set_schema_keys(group, target_schema);
58✔
892
        set_schema_version(group, target_schema_version);
58✔
893
        return;
58✔
894
    }
58✔
895

143✔
896
    if (schema_version == target_schema_version) {
286✔
897
        apply_non_migration_changes(group, changes);
58✔
898
        set_schema_keys(group, target_schema);
58✔
899
        return;
58✔
900
    }
58✔
901

114✔
902
    auto old_schema = schema_from_group(group);
228✔
903
    apply_pre_migration_changes(group, changes);
228✔
904
    HandleBackLinksAutomatically handle_backlinks =
228✔
905
        handle_automatically_backlinks ? HandleBackLinksAutomatically::Yes : HandleBackLinksAutomatically::No;
218✔
906
    if (migration_function) {
228✔
907
        set_schema_keys(group, target_schema);
156✔
908
        migration_function();
156✔
909

78✔
910
        // Migration function may have changed the schema, so we need to re-read it
78✔
911
        auto schema = schema_from_group(group);
156✔
912
        apply_post_migration_changes(group, schema.compare(target_schema, mode), old_schema, DidRereadSchema::Yes,
156✔
913
                                     handle_backlinks);
156✔
914
        group.validate_primary_columns();
156✔
915
    }
156✔
916
    else {
72✔
917
        apply_post_migration_changes(group, changes, {}, DidRereadSchema::No, handle_backlinks);
72✔
918
    }
72✔
919

114✔
920
    set_schema_version(group, target_schema_version);
228✔
921
    set_schema_keys(group, target_schema);
228✔
922
}
228✔
923

924
Schema ObjectStore::schema_from_group(Group const& group)
925
{
99,093✔
926
    std::vector<ObjectSchema> schema;
99,093✔
927
    schema.reserve(group.size());
99,093✔
928
    for (auto key : group.get_table_keys()) {
259,590✔
929
        auto object_type = object_type_for_table_name(group.get_table_name(key));
259,590✔
930
        if (object_type.size()) {
259,590✔
931
            schema.emplace_back(group, object_type, key);
193,800✔
932
        }
193,800✔
933
    }
259,590✔
934
    return schema;
99,093✔
935
}
99,093✔
936

937
void ObjectStore::set_schema_keys(Group const& group, Schema& schema)
938
{
21,764✔
939
    for (auto& object_schema : schema) {
61,251✔
940
        auto table = table_for_object_schema(group, object_schema);
61,251✔
941
        if (!table) {
61,251✔
942
            continue;
2✔
943
        }
2✔
944
        object_schema.table_key = table->get_key();
61,249✔
945
        for (auto& property : object_schema.persisted_properties) {
222,475✔
946
            property.column_key = table->get_column_key(property.name);
222,475✔
947
        }
222,475✔
948
    }
61,249✔
949
}
21,764✔
950

951
void ObjectStore::delete_data_for_object(Group& group, StringData object_type)
952
{
6✔
953
    if (TableRef table = table_for_object_type(group, object_type)) {
6✔
954
        group.remove_table(table->get_key());
4✔
955
    }
4✔
956
}
6✔
957

958
bool ObjectStore::is_empty(Group const& group)
959
{
4✔
960
    for (auto key : group.get_table_keys()) {
40✔
961
        ConstTableRef table = group.get_table(key);
40✔
962
        auto object_type = object_type_for_table_name(table->get_name());
40✔
963
        if (object_type.size() == 0 || object_type.begins_with("__")) {
40✔
964
            continue;
28✔
965
        }
28✔
966
        if (!table->is_empty()) {
12✔
967
            return false;
×
968
        }
×
969
    }
12✔
970
    return true;
4✔
971
}
4✔
972

973
void ObjectStore::rename_property(Group& group, Schema& target_schema, StringData object_type, StringData old_name,
974
                                  StringData new_name)
975
{
44✔
976
    TableRef table = table_for_object_type(group, object_type);
44✔
977
    if (!table) {
44✔
978
        throw LogicError(
×
979
            ErrorCodes::NoSuchTable,
×
980
            util::format("Cannot rename properties for type '%1' because it does not exist.", object_type));
×
981
    }
×
982

22✔
983
    auto target_object_schema = target_schema.find(object_type);
44✔
984
    if (target_object_schema == target_schema.end()) {
44✔
985
        throw LogicError(
2✔
986
            ErrorCodes::NoSuchTable,
2✔
987
            util::format("Cannot rename properties for type '%1' because it has been removed from the Realm.",
2✔
988
                         object_type));
2✔
989
    }
2✔
990

21✔
991
    if (target_object_schema->property_for_name(old_name)) {
42✔
992
        throw LogicError(
2✔
993
            ErrorCodes::IllegalOperation,
2✔
994
            util::format("Cannot rename property '%1.%2' to '%3' because the source property still exists.",
2✔
995
                         object_type, old_name, new_name));
2✔
996
    }
2✔
997

20✔
998
    ObjectSchema table_object_schema(group, object_type, table->get_key());
40✔
999
    Property* old_property = table_object_schema.property_for_name(old_name);
40✔
1000
    if (!old_property) {
40✔
1001
        throw LogicError(
4✔
1002
            ErrorCodes::InvalidProperty,
4✔
1003
            util::format("Cannot rename property '%1.%2' because it does not exist.", object_type, old_name));
4✔
1004
    }
4✔
1005

18✔
1006
    Property* new_property = table_object_schema.property_for_name(new_name);
36✔
1007
    if (!new_property) {
36✔
1008
        // New property doesn't exist in the table, which means we're probably
3✔
1009
        // renaming to an intermediate property in a multi-version migration.
3✔
1010
        // This is safe because the migration will fail schema validation unless
3✔
1011
        // this property is renamed again to a valid name before the end.
3✔
1012
        table->rename_column(old_property->column_key, new_name);
6✔
1013
        return;
6✔
1014
    }
6✔
1015

15✔
1016
    if (old_property->type != new_property->type || old_property->object_type != new_property->object_type) {
30✔
1017
        throw LogicError(
8✔
1018
            ErrorCodes::IllegalOperation,
8✔
1019
            util::format("Cannot rename property '%1.%2' to '%3' because it would change from type '%4' to '%5'.",
8✔
1020
                         object_type, old_name, new_name, old_property->type_string(), new_property->type_string()));
8✔
1021
    }
8✔
1022

11✔
1023
    if (is_nullable(old_property->type) && !is_nullable(new_property->type)) {
22✔
1024
        throw LogicError(
2✔
1025
            ErrorCodes::IllegalOperation,
2✔
1026
            util::format("Cannot rename property '%1.%2' to '%3' because it would change from optional to required.",
2✔
1027
                         object_type, old_name, new_name));
2✔
1028
    }
2✔
1029

10✔
1030
    table->remove_column(new_property->column_key);
20✔
1031
    table->rename_column(old_property->column_key, new_name);
20✔
1032

10✔
1033
    if (auto prop = target_object_schema->property_for_name(new_name)) {
20✔
1034
        prop->column_key = old_property->column_key;
20✔
1035
    }
20✔
1036

10✔
1037
    // update nullability for column
10✔
1038
    if (is_nullable(new_property->type) && !is_nullable(old_property->type)) {
20✔
1039
        auto prop = *new_property;
2✔
1040
        prop.column_key = old_property->column_key;
2✔
1041
        make_property_optional(*table, prop);
2✔
1042
    }
2✔
1043
}
20✔
1044

1045
InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version,
1046
                                                             bool must_exactly_equal)
1047
    : LogicError(ErrorCodes::InvalidSchemaVersion,
1048
                 util::format(must_exactly_equal ? "Provided schema version %1 does not equal last set version %2."
1049
                                                 : "Provided schema version %1 is less than last set version %2.",
1050
                              new_version, old_version))
1051
    , m_old_version(old_version)
1052
    , m_new_version(new_version)
1053
{
6✔
1054
}
6✔
1055

1056
static void append_errors(std::string& message, std::vector<ObjectSchemaValidationException> const& errors)
1057
{
314✔
1058
    for (auto const& error : errors) {
378✔
1059
        message += "\n- ";
378✔
1060
        message += error.m_message;
378✔
1061
    }
378✔
1062
}
314✔
1063

1064
static void append_line(std::string& message, std::string_view line)
1065
{
48✔
1066
    message += "\n";
48✔
1067
    message += line;
48✔
1068
}
48✔
1069

1070
SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors)
1071
    : LogicError(ErrorCodes::SchemaValidationFailed, [&] {
152✔
1072
        std::string message = "Schema validation failed due to the following errors:";
152✔
1073
        append_errors(message, errors);
152✔
1074
        return message;
152✔
1075
    }())
152✔
1076
{
152✔
1077
}
152✔
1078

1079
SchemaMismatchException::SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors)
1080
    : LogicError(ErrorCodes::SchemaMismatch, [&] {
84✔
1081
        std::string message = "Migration is required due to the following errors:";
84✔
1082
        append_errors(message, errors);
84✔
1083
        return message;
84✔
1084
    }())
84✔
1085
{
84✔
1086
}
84✔
1087

1088
InvalidReadOnlySchemaChangeException::InvalidReadOnlySchemaChangeException(
1089
    std::vector<ObjectSchemaValidationException> const& errors)
1090
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
30✔
1091
        std::string message = "The following changes cannot be made in read-only schema mode:";
30✔
1092
        append_errors(message, errors);
30✔
1093
        return message;
30✔
1094
    }())
30✔
1095
{
30✔
1096
}
30✔
1097

1098
InvalidAdditiveSchemaChangeException::InvalidAdditiveSchemaChangeException(
1099
    std::vector<ObjectSchemaValidationException> const& errors)
1100
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
38✔
1101
        std::string message = "The following changes cannot be made in additive-only schema mode:";
38✔
1102
        append_errors(message, errors);
38✔
1103
        append_line(message, c_development_mode_msg);
38✔
1104
        return message;
38✔
1105
    }())
38✔
1106
{
38✔
1107
}
38✔
1108

1109
InvalidExternalSchemaChangeException::InvalidExternalSchemaChangeException(
1110
    std::vector<ObjectSchemaValidationException> const& errors)
1111
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
10✔
1112
        std::string message = "Unsupported schema changes were made by another client or process:";
10✔
1113
        append_errors(message, errors);
10✔
1114
        append_line(message, c_development_mode_msg);
10✔
1115
        return message;
10✔
1116
    }())
10✔
1117
{
10✔
1118
}
10✔
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