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

realm / realm-core / nicola.cabiddu_1042

27 Sep 2023 06:04PM UTC coverage: 91.085% (-1.8%) from 92.915%
nicola.cabiddu_1042

Pull #6766

Evergreen

nicola-cab
Fix logic for dictionaries
Pull Request #6766: Client Reset for collections in mixed / nested collections

97276 of 178892 branches covered (0.0%)

1994 of 2029 new or added lines in 7 files covered. (98.28%)

4556 existing lines in 112 files now uncovered.

237059 of 260260 relevant lines covered (91.09%)

6321099.55 hits per line

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

95.34
/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,698✔
53
    // The 'metadata' table is simply ignored by Sync
21,623✔
54
    TableRef metadata_table = group.get_or_add_table(c_metadataTableName);
43,698✔
55

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

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

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

73
DataType to_core_type(PropertyType type)
74
{
197,662✔
75
    REALM_ASSERT(type != PropertyType::Object); // Link columns have to be handled differently
197,662✔
76
    switch (type & ~PropertyType::Flags) {
197,662✔
77
        case PropertyType::Int:
48,352✔
78
            return type_Int;
48,352✔
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,954✔
84
            return type_Double;
5,954✔
85
        case PropertyType::String:
90,428✔
86
            return type_String;
90,428✔
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,388✔
92
            return type_ObjectId;
14,388✔
93
        case PropertyType::Decimal:
5,666✔
94
            return type_Decimal;
5,666✔
95
        case PropertyType::UUID:
5,624✔
96
            return type_UUID;
5,624✔
97
        case PropertyType::Mixed:
4,528✔
98
            return type_Mixed;
4,528✔
99
        default:
✔
100
            REALM_COMPILER_HINT_UNREACHABLE();
×
101
    }
197,662✔
102
}
197,662✔
103

104
std::optional<CollectionType> process_collection(const Property& property)
105
{
192,662✔
106
    // check if the final type is itself a collection.
94,993✔
107
    if (is_array(property.type)) {
192,662✔
108
        return CollectionType::List;
31,036✔
109
    }
31,036✔
110
    else if (is_set(property.type)) {
161,626✔
111
        return CollectionType::Set;
20,729✔
112
    }
20,729✔
113
    else if (is_dictionary(property.type)) {
140,897✔
114
        return CollectionType::Dictionary;
17,548✔
115
    }
17,548✔
116
    return {};
123,349✔
117
}
123,349✔
118

119
ColKey add_column(Group& group, Table& table, Property const& property)
120
{
192,662✔
121
    // Cannot directly insert a LinkingObjects column (a computed property).
94,993✔
122
    // LinkingObjects must be an artifact of an existing link column.
94,993✔
123
    REALM_ASSERT(property.type != PropertyType::LinkingObjects);
192,662✔
124

94,993✔
125
    if (property.is_primary) {
192,662✔
126
        // Primary key columns should have been created when the table was created
5✔
127
        if (auto col = table.get_column_key(property.name)) {
10✔
UNCOV
128
            return col;
×
UNCOV
129
        }
×
130
    }
192,662✔
131
    auto collection_type = process_collection(property);
192,662✔
132
    if (property.type == PropertyType::Object) {
192,662✔
133
        auto target_name = ObjectStore::table_name_for_object_type(property.object_type);
24,109✔
134
        TableRef link_table = group.get_table(target_name);
24,109✔
135
        REALM_ASSERT(link_table);
24,109✔
136
        return table.add_column(*link_table, property.name, collection_type);
24,109✔
137
    }
24,109✔
138
    else {
168,553✔
139
        auto key =
168,553✔
140
            table.add_column(to_core_type(property.type), property.name, is_nullable(property.type), collection_type);
168,553✔
141
        if (property.requires_index())
168,553✔
142
            table.add_search_index(key);
540✔
143
        if (property.requires_fulltext_index())
168,553✔
144
            table.add_fulltext_index(key);
2✔
145
        return key;
168,553✔
146
    }
168,553✔
147
}
192,662✔
148

149
void replace_column(Group& group, Table& table, Property const& old_property, Property const& new_property)
150
{
16✔
151
    table.remove_column(old_property.column_key);
16✔
152
    add_column(group, table, new_property);
16✔
153
}
16✔
154

155
TableRef create_table(Group& group, ObjectSchema const& object_schema)
156
{
60,823✔
157
    auto name = ObjectStore::table_name_for_object_type(object_schema.name);
60,823✔
158

30,116✔
159
    TableRef table = group.get_table(name);
60,823✔
160
    if (table)
60,823✔
161
        return table;
6✔
162

30,113✔
163
    if (auto* pk_property = object_schema.primary_key_property()) {
60,817✔
164
        auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
29,109✔
165
                              ? Table::Type::TopLevelAsymmetric
14,400✔
166
                              : Table::Type::TopLevel;
29,095✔
167
        table = group.add_table_with_primary_key(name, to_core_type(pk_property->type), pk_property->name,
29,109✔
168
                                                 is_nullable(pk_property->type), table_type);
29,109✔
169
    }
29,109✔
170
    else {
31,708✔
171
        if (object_schema.table_type == ObjectSchema::ObjectType::Embedded) {
31,708✔
172
            table = group.add_table(name, Table::Type::Embedded);
5,521✔
173
        }
5,521✔
174
        else {
26,187✔
175
            auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
26,187✔
176
                                  ? Table::Type::TopLevelAsymmetric
13,020✔
177
                                  : Table::Type::TopLevel;
26,187✔
178
            table = group.get_or_add_table(name, table_type);
26,187✔
179
        }
26,187✔
180
    }
31,708✔
181

30,113✔
182
    return table;
60,817✔
183
}
60,817✔
184

185
void add_initial_columns(Group& group, ObjectSchema const& object_schema)
186
{
60,817✔
187
    auto name = ObjectStore::table_name_for_object_type(object_schema.name);
60,817✔
188
    TableRef table = group.get_table(name);
60,817✔
189

30,113✔
190
    for (auto const& prop : object_schema.persisted_properties) {
221,617✔
191
#if REALM_ENABLE_SYNC
221,617✔
192
        // The sync::create_table* functions create the PK column for us.
109,302✔
193
        if (prop.is_primary)
221,617✔
194
            continue;
29,109✔
195
#endif // REALM_ENABLE_SYNC
192,508✔
196
        add_column(group, *table, prop);
192,508✔
197
    }
192,508✔
198
}
60,817✔
199

200
void make_property_optional(Table& table, Property property)
201
{
8✔
202
    property.type |= PropertyType::Nullable;
8✔
203
    const bool throw_on_null = false;
8✔
204
    property.column_key = table.set_nullability(property.column_key, true, throw_on_null);
8✔
205
}
8✔
206

207
void make_property_required(Group& group, Table& table, Property property)
208
{
8✔
209
    property.type &= ~PropertyType::Nullable;
8✔
210
    table.remove_column(property.column_key);
8✔
211
    property.column_key = add_column(group, table, property);
8✔
212
}
8✔
213

214
void add_search_index(Table& table, Property property, IndexType type)
215
{
24✔
216
    table.add_search_index(table.get_column_key(property.name), type);
24✔
217
}
24✔
218

219
void remove_search_index(Table& table, Property property)
220
{
12✔
221
    table.remove_search_index(table.get_column_key(property.name));
12✔
222
}
12✔
223

224
} // anonymous namespace
225

226
void ObjectStore::set_schema_version(Group& group, uint64_t version)
227
{
21,738✔
228
    ::create_metadata_tables(group);
21,738✔
229
    ::set_schema_version(group, version);
21,738✔
230
}
21,738✔
231

232
uint64_t ObjectStore::get_schema_version(Group const& group)
233
{
68,608✔
234
    ConstTableRef table = group.get_table(c_metadataTableName);
68,608✔
235
    if (!table || table->get_column_count() == 0) {
68,608✔
236
        return ObjectStore::NotVersioned;
21,818✔
237
    }
21,818✔
238
    return table->get_object(0).get<int64_t>(c_versionColumnName);
46,790✔
239
}
46,790✔
240

241
StringData ObjectStore::object_type_for_table_name(StringData table_name)
242
{
332,789✔
243
    if (table_name.begins_with(c_object_table_prefix)) {
332,789✔
244
        return table_name.substr(sizeof(c_object_table_prefix) - 1);
266,777✔
245
    }
266,777✔
246
    return StringData();
66,012✔
247
}
66,012✔
248

249
std::string ObjectStore::table_name_for_object_type(StringData object_type)
250
{
277,159✔
251
    return std::string(c_object_table_prefix) + std::string(object_type);
277,159✔
252
}
277,159✔
253

254
TableRef ObjectStore::table_for_object_type(Group& group, StringData object_type)
255
{
68,533✔
256
    auto name = table_name_for_object_type(object_type);
68,533✔
257
    return group.get_table(name);
68,533✔
258
}
68,533✔
259

260
ConstTableRef ObjectStore::table_for_object_type(Group const& group, StringData object_type)
261
{
61,911✔
262
    auto name = table_name_for_object_type(object_type);
61,911✔
263
    return group.get_table(name);
61,911✔
264
}
61,911✔
265

266
namespace {
267
struct SchemaDifferenceExplainer {
268
    std::vector<ObjectSchemaValidationException> errors;
269

270
    void operator()(schema_change::AddTable op)
271
    {
4✔
272
        errors.emplace_back("Class '%1' has been added.", op.object->name);
4✔
273
    }
4✔
274

275
    void operator()(schema_change::RemoveTable)
276
    {
196✔
277
        // We never do anything for RemoveTable
98✔
278
    }
196✔
279

280
    void operator()(schema_change::ChangeTableType op)
281
    {
12✔
282
        errors.emplace_back("Class '%1' has been changed from %2 to %3.", op.object->name, *op.old_table_type,
12✔
283
                            *op.new_table_type);
12✔
284
    }
12✔
285

286
    void operator()(schema_change::AddInitialProperties)
287
    {
4✔
288
        // Nothing. Always preceded by AddTable.
2✔
289
    }
4✔
290

291
    void operator()(schema_change::AddProperty op)
292
    {
26✔
293
        errors.emplace_back("Property '%1.%2' has been added.", op.object->name, op.property->name);
26✔
294
    }
26✔
295

296
    void operator()(schema_change::RemoveProperty op)
297
    {
16✔
298
        errors.emplace_back("Property '%1.%2' has been removed.", op.object->name, op.property->name);
16✔
299
    }
16✔
300

301
    void operator()(schema_change::ChangePropertyType op)
302
    {
48✔
303
        errors.emplace_back("Property '%1.%2' has been changed from '%3' to '%4'.", op.object->name,
48✔
304
                            op.new_property->name, op.old_property->type_string(), op.new_property->type_string());
48✔
305
    }
48✔
306

307
    void operator()(schema_change::MakePropertyNullable op)
308
    {
12✔
309
        errors.emplace_back("Property '%1.%2' has been made optional.", op.object->name, op.property->name);
12✔
310
    }
12✔
311

312
    void operator()(schema_change::MakePropertyRequired op)
313
    {
10✔
314
        errors.emplace_back("Property '%1.%2' has been made required.", op.object->name, op.property->name);
10✔
315
    }
10✔
316

317
    void operator()(schema_change::ChangePrimaryKey op)
318
    {
26✔
319
        if (op.property && !op.object->primary_key.empty()) {
26✔
320
            errors.emplace_back("Primary Key for class '%1' has changed from '%2' to '%3'.", op.object->name,
4✔
321
                                op.object->primary_key, op.property->name);
4✔
322
        }
4✔
323
        else if (op.property) {
22✔
324
            errors.emplace_back("Primary Key for class '%1' has been added.", op.object->name);
10✔
325
        }
10✔
326
        else {
12✔
327
            errors.emplace_back("Primary Key for class '%1' has been removed.", op.object->name);
12✔
328
        }
12✔
329
    }
26✔
330

331
    void operator()(schema_change::AddIndex op)
332
    {
8✔
333
        errors.emplace_back("Property '%1.%2' has been made indexed.", op.object->name, op.property->name);
8✔
334
    }
8✔
335

336
    void operator()(schema_change::RemoveIndex op)
337
    {
12✔
338
        errors.emplace_back("Property '%1.%2' has been made unindexed.", op.object->name, op.property->name);
12✔
339
    }
12✔
340
};
341

342
class TableHelper {
343
public:
344
    TableHelper(Group& g)
345
        : m_group(g)
346
    {
22,100✔
347
    }
22,100✔
348

349
    Table& operator()(const ObjectSchema* object_schema)
350
    {
366✔
351
        if (object_schema != m_current_object_schema) {
366✔
352
            m_current_table = table_for_object_schema(m_group, *object_schema);
290✔
353
            m_current_object_schema = object_schema;
290✔
354
        }
290✔
355
        REALM_ASSERT(m_current_table);
366✔
356
        return *m_current_table;
366✔
357
    }
366✔
358

359
private:
360
    Group& m_group;
361
    const ObjectSchema* m_current_object_schema = nullptr;
362
    TableRef m_current_table;
363
};
364

365
template <typename ErrorType, typename Verifier>
366
void verify_no_errors(Verifier&& verifier, std::vector<SchemaChange> const& changes)
367
{
8,936✔
368
    for (auto& change : changes) {
40,318✔
369
        change.visit(verifier);
40,318✔
370
    }
40,318✔
371

4,384✔
372
    if (!verifier.errors.empty()) {
8,936✔
373
        throw ErrorType(verifier.errors);
162✔
374
    }
162✔
375
}
8,936✔
376
} // anonymous namespace
377

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

5✔
432
    return std::any_of(begin(changes), end(changes), [](auto&& change) {
12✔
433
        return change.visit(Visitor());
12✔
434
    });
12✔
435
}
10✔
436

437
void ObjectStore::verify_no_changes_required(std::vector<SchemaChange> const& changes)
438
{
86✔
439
    verify_no_errors<SchemaMismatchException>(SchemaDifferenceExplainer(), changes);
86✔
440
}
86✔
441

442
void ObjectStore::verify_no_migration_required(std::vector<SchemaChange> const& changes)
443
{
2✔
444
    using namespace schema_change;
2✔
445
    struct Verifier : SchemaDifferenceExplainer {
2✔
446
        using SchemaDifferenceExplainer::operator();
2✔
447

1✔
448
        // Adding a table or adding/removing indexes can be done automatically.
1✔
449
        // All other changes require migrations.
1✔
450
        void operator()(AddTable) {}
1✔
451
        void operator()(AddInitialProperties) {}
1✔
452
        void operator()(AddIndex) {}
1✔
453
        void operator()(RemoveIndex) {}
1✔
454
    } verifier;
2✔
455
    verify_no_errors<SchemaMismatchException>(verifier, changes);
2✔
456
}
2✔
457

458
bool ObjectStore::verify_valid_additive_changes(std::vector<SchemaChange> const& changes, bool update_indexes)
459
{
8,033✔
460
    using namespace schema_change;
8,033✔
461
    struct Verifier : SchemaDifferenceExplainer {
8,033✔
462
        using SchemaDifferenceExplainer::operator();
8,033✔
463

3,939✔
464
        bool index_changes = false;
8,033✔
465
        bool other_changes = false;
8,033✔
466

3,939✔
467
        // Additive mode allows adding things, extra columns, and adding/removing indexes
3,939✔
468
        void operator()(AddTable)
8,033✔
469
        {
19,246✔
470
            other_changes = true;
19,246✔
471
        }
19,246✔
472
        void operator()(AddInitialProperties)
8,033✔
473
        {
19,246✔
474
            other_changes = true;
19,246✔
475
        }
19,246✔
476
        void operator()(AddProperty)
8,033✔
477
        {
3,970✔
478
            other_changes = true;
62✔
479
        }
62✔
480
        void operator()(RemoveProperty) {}
4,002✔
481
        void operator()(AddIndex)
8,033✔
482
        {
3,948✔
483
            index_changes = true;
18✔
484
        }
18✔
485
        void operator()(RemoveIndex)
8,033✔
486
        {
3,945✔
487
            index_changes = true;
12✔
488
        }
12✔
489
    } verifier;
8,033✔
490
    verify_no_errors<InvalidAdditiveSchemaChangeException>(verifier, changes);
8,033✔
491
    return verifier.other_changes || (verifier.index_changes && update_indexes);
8,033✔
492
}
8,033✔
493

494
void ObjectStore::verify_valid_external_changes(std::vector<SchemaChange> const& changes)
495
{
481✔
496
    using namespace schema_change;
481✔
497
    struct Verifier : SchemaDifferenceExplainer {
481✔
498
        using SchemaDifferenceExplainer::operator();
481✔
499

234✔
500
        // Adding new things is fine
234✔
501
        void operator()(AddTable) {}
243✔
502
        void operator()(AddInitialProperties) {}
243✔
503
        void operator()(AddProperty) {}
250✔
504
        void operator()(AddIndex) {}
1,046✔
505
        void operator()(RemoveIndex) {}
237✔
506
        // Deleting tables is not okay
234✔
507
        void operator()(RemoveTable op)
481✔
508
        {
235✔
509
            errors.emplace_back("Class '%1' has been removed.", op.object->name);
2✔
510
        }
2✔
511
    } verifier;
481✔
512
    verify_no_errors<InvalidExternalSchemaChangeException>(verifier, changes);
481✔
513
}
481✔
514

515
void ObjectStore::verify_compatible_for_immutable_and_readonly(std::vector<SchemaChange> const& changes)
516
{
276✔
517
    using namespace schema_change;
276✔
518
    struct Verifier : SchemaDifferenceExplainer {
276✔
519
        using SchemaDifferenceExplainer::operator();
276✔
520

138✔
521
        void operator()(AddTable) {}
145✔
522
        void operator()(AddInitialProperties) {}
145✔
523
        void operator()(ChangeTableType) {}
140✔
524
        void operator()(RemoveProperty) {}
143✔
525
        void operator()(AddIndex) {}
140✔
526
        void operator()(RemoveIndex) {}
140✔
527
    } verifier;
276✔
528
    verify_no_errors<InvalidReadOnlySchemaChangeException>(verifier, changes);
276✔
529
}
276✔
530

531
static void apply_non_migration_changes(Group& group, std::vector<SchemaChange> const& changes)
532
{
58✔
533
    using namespace schema_change;
58✔
534
    struct Applier : SchemaDifferenceExplainer {
58✔
535
        Applier(Group& group)
58✔
536
            : group{group}
58✔
537
            , table{group}
58✔
538
        {
58✔
539
        }
58✔
540
        Group& group;
58✔
541
        TableHelper table;
58✔
542

29✔
543
        // Produce an exception listing the unsupported schema changes for
29✔
544
        // everything but the explicitly supported ones
29✔
545
        using SchemaDifferenceExplainer::operator();
58✔
546

29✔
547
        void operator()(AddTable op)
58✔
548
        {
41✔
549
            create_table(group, *op.object);
24✔
550
        }
24✔
551
        void operator()(AddInitialProperties op)
58✔
552
        {
41✔
553
            add_initial_columns(group, *op.object);
24✔
554
        }
24✔
555
        void operator()(AddIndex op)
58✔
556
        {
33✔
557
            table(op.object).add_search_index(op.property->column_key, op.type);
8✔
558
        }
8✔
559
        void operator()(RemoveIndex op)
58✔
560
        {
32✔
561
            table(op.object).remove_search_index(op.property->column_key);
6✔
562
        }
6✔
563
    } applier{group};
58✔
564
    verify_no_errors<SchemaMismatchException>(applier, changes);
58✔
565
}
58✔
566

567
static void set_primary_key(Table& table, const Property* property)
568
{
44✔
569
    ColKey col;
44✔
570
    if (property) {
44✔
571
        col = table.get_column_key(property->name);
38✔
572
        REALM_ASSERT(col);
38✔
573
    }
38✔
574
    table.set_primary_key_column(col);
44✔
575
}
44✔
576

577
static void create_initial_tables(Group& group, std::vector<SchemaChange> const& changes)
578
{
13,682✔
579
    using namespace schema_change;
13,682✔
580
    struct Applier {
13,682✔
581
        Applier(Group& group)
13,682✔
582
            : group{group}
13,682✔
583
            , table{group}
13,682✔
584
        {
13,682✔
585
        }
13,682✔
586
        Group& group;
13,682✔
587
        TableHelper table;
13,682✔
588

6,803✔
589
        void operator()(AddTable op)
13,682✔
590
        {
41,538✔
591
            create_table(group, *op.object);
41,538✔
592
        }
41,538✔
593
        void operator()(RemoveTable) {}
6,803✔
594
        void operator()(AddInitialProperties op)
13,682✔
595
        {
41,538✔
596
            add_initial_columns(group, *op.object);
41,538✔
597
        }
41,538✔
598

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

6,803✔
637
        void operator()(ChangePropertyType op)
13,682✔
638
        {
6,803✔
UNCOV
639
            replace_column(group, table(op.object), *op.old_property, *op.new_property);
×
640
        }
×
641
    } applier{group};
13,682✔
642

6,803✔
643
    for (auto& change : changes) {
83,076✔
644
        change.visit(applier);
83,076✔
645
    }
83,076✔
646
}
13,682✔
647

648
void ObjectStore::apply_additive_changes(Group& group, std::vector<SchemaChange> const& changes, bool update_indexes)
649
{
7,930✔
650
    using namespace schema_change;
7,930✔
651
    struct Applier {
7,930✔
652
        Applier(Group& group, bool update_indexes)
7,930✔
653
            : group{group}
7,930✔
654
            , table{group}
7,930✔
655
            , update_indexes{update_indexes}
7,930✔
656
        {
7,930✔
657
        }
7,930✔
658
        Group& group;
7,930✔
659
        TableHelper table;
7,930✔
660
        bool update_indexes;
7,930✔
661

3,890✔
662
        void operator()(AddTable op)
7,930✔
663
        {
19,239✔
664
            create_table(group, *op.object);
19,239✔
665
        }
19,239✔
666
        void operator()(RemoveTable) {}
3,890✔
667
        void operator()(AddInitialProperties op)
7,930✔
668
        {
19,239✔
669
            add_initial_columns(group, *op.object);
19,239✔
670
        }
19,239✔
671
        void operator()(AddProperty op)
7,930✔
672
        {
3,921✔
673
            add_column(group, table(op.object), *op.property);
62✔
674
        }
62✔
675
        void operator()(AddIndex op)
7,930✔
676
        {
3,895✔
677
            if (update_indexes) {
10✔
678
                add_search_index(table(op.object), *op.property, op.type);
10✔
679
            }
10✔
680
        }
10✔
681
        void operator()(RemoveIndex op)
7,930✔
682
        {
3,892✔
683
            if (update_indexes)
4✔
684
                table(op.object).remove_search_index(op.property->column_key);
4✔
685
        }
4✔
686
        void operator()(RemoveProperty) {}
3,899✔
687

3,890✔
688
        // No need for errors for these, as we've already verified that they aren't present
3,890✔
689
        void operator()(ChangeTableType) {}
3,890✔
690
        void operator()(ChangePrimaryKey) {}
3,890✔
691
        void operator()(ChangePropertyType) {}
3,890✔
692
        void operator()(MakePropertyNullable) {}
3,890✔
693
        void operator()(MakePropertyRequired) {}
3,890✔
694
    } applier{group, update_indexes};
7,930✔
695

3,890✔
696
    for (auto& change : changes) {
38,572✔
697
        change.visit(applier);
38,572✔
698
    }
38,572✔
699
}
7,930✔
700

701
static void apply_pre_migration_changes(Group& group, std::vector<SchemaChange> const& changes)
702
{
228✔
703
    using namespace schema_change;
228✔
704
    struct Applier {
228✔
705
        Applier(Group& group)
228✔
706
            : group{group}
228✔
707
            , table{group}
228✔
708
        {
228✔
709
        }
228✔
710
        Group& group;
228✔
711
        TableHelper table;
228✔
712

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

114✔
758
    for (auto& change : changes) {
336✔
759
        change.visit(applier);
336✔
760
    }
336✔
761
}
228✔
762

763
enum class DidRereadSchema { Yes, No };
764
enum class HandleBackLinksAutomatically { Yes, No };
765

766
static void apply_post_migration_changes(Group& group, std::vector<SchemaChange> const& changes,
767
                                         Schema const& initial_schema, DidRereadSchema did_reread_schema,
768
                                         HandleBackLinksAutomatically handle_backlinks_automatically)
769
{
202✔
770
    using namespace schema_change;
202✔
771
    struct Applier {
202✔
772
        Applier(Group& group, Schema const& initial_schema, DidRereadSchema did_reread_schema,
202✔
773
                HandleBackLinksAutomatically handle_backlinks_automatically)
202✔
774
            : group{group}
202✔
775
            , initial_schema(initial_schema)
202✔
776
            , table(group)
202✔
777
            , did_reread_schema(did_reread_schema == DidRereadSchema::Yes)
202✔
778
            , handle_backlinks_automatically(handle_backlinks_automatically == HandleBackLinksAutomatically::Yes)
202✔
779
        {
202✔
780
        }
202✔
781
        Group& group;
202✔
782
        Schema const& initial_schema;
202✔
783
        TableHelper table;
202✔
784
        bool did_reread_schema;
202✔
785
        bool handle_backlinks_automatically;
202✔
786

101✔
787
        void operator()(RemoveProperty op)
202✔
788
        {
140✔
789
            if (!initial_schema.empty() &&
78✔
790
                !initial_schema.find(op.object->name)->property_for_name(op.property->name))
76✔
791
                throw LogicError(ErrorCodes::InvalidProperty, util::format("Renamed property '%1.%2' does not exist.",
2✔
792
                                                                           op.object->name, op.property->name));
2✔
793
            auto table = table_for_object_schema(group, *op.object);
76✔
794
            table->remove_column(op.property->column_key);
76✔
795
        }
76✔
796

101✔
797
        void operator()(ChangePrimaryKey op)
202✔
798
        {
123✔
799
            set_primary_key(table(op.object), op.property);
44✔
800
        }
44✔
801

101✔
802
        void operator()(AddTable op)
202✔
803
        {
105✔
804
            create_table(group, *op.object);
8✔
805
        }
8✔
806

101✔
807
        void operator()(AddInitialProperties op)
202✔
808
        {
105✔
809
            if (did_reread_schema)
8✔
810
                add_initial_columns(group, *op.object);
2✔
811
            else {
6✔
812
                // If we didn't re-read the schema then AddInitialProperties was already taken care of
3✔
813
                // during apply_pre_migration_changes.
3✔
814
            }
6✔
815
        }
8✔
816

101✔
817
        void operator()(AddIndex op)
202✔
818
        {
114✔
819
            table(op.object).add_search_index(op.property->column_key);
26✔
820
        }
26✔
821
        void operator()(RemoveIndex op)
202✔
822
        {
104✔
823
            table(op.object).remove_search_index(op.property->column_key);
6✔
824
        }
6✔
825

101✔
826
        void operator()(ChangeTableType op)
202✔
827
        {
126✔
828
            table(op.object).set_table_type(static_cast<Table::Type>(*op.new_table_type),
50✔
829
                                            handle_backlinks_automatically);
50✔
830
        }
50✔
831
        void operator()(RemoveTable) {}
101✔
832
        void operator()(ChangePropertyType) {}
104✔
833
        void operator()(MakePropertyNullable) {}
103✔
834
        void operator()(MakePropertyRequired) {}
103✔
835
        void operator()(AddProperty) {}
104✔
836
    } applier{group, initial_schema, did_reread_schema, handle_backlinks_automatically};
202✔
837

101✔
838
    for (auto& change : changes) {
240✔
839
        change.visit(applier);
240✔
840
    }
240✔
841
}
202✔
842

843

844
void ObjectStore::apply_schema_changes(Transaction& group, uint64_t schema_version, Schema& target_schema,
845
                                       uint64_t target_schema_version, SchemaMode mode,
846
                                       std::vector<SchemaChange> const& changes, bool handle_automatically_backlinks,
847
                                       std::function<void()> migration_function)
848
{
21,960✔
849
    create_metadata_tables(group);
21,960✔
850

10,867✔
851
    if (mode == SchemaMode::AdditiveDiscovered || mode == SchemaMode::AdditiveExplicit) {
21,960✔
852
        bool target_schema_is_newer =
7,930✔
853
            (schema_version < target_schema_version || schema_version == ObjectStore::NotVersioned);
7,930✔
854

3,890✔
855
        // With sync v2.x, indexes are no longer synced, so there's no reason to avoid creating them.
3,890✔
856
        bool update_indexes = true;
7,930✔
857
        apply_additive_changes(group, changes, update_indexes);
7,930✔
858

3,890✔
859
        if (target_schema_is_newer)
7,930✔
860
            set_schema_version(group, target_schema_version);
7,832✔
861

3,890✔
862
        set_schema_keys(group, target_schema);
7,930✔
863
        return;
7,930✔
864
    }
7,930✔
865

6,977✔
866
    if (schema_version == ObjectStore::NotVersioned) {
14,030✔
867
        if (mode != SchemaMode::ReadOnly) {
13,686✔
868
            create_initial_tables(group, changes);
13,682✔
869
        }
13,682✔
870
        set_schema_version(group, target_schema_version);
13,686✔
871
        set_schema_keys(group, target_schema);
13,686✔
872
        return;
13,686✔
873
    }
13,686✔
874

172✔
875
    if (mode == SchemaMode::Manual) {
344✔
876
        if (migration_function) {
58✔
877
            migration_function();
54✔
878
        }
54✔
879

29✔
880
        verify_no_changes_required(schema_from_group(group).compare(target_schema));
58✔
881
        group.validate_primary_columns();
58✔
882
        set_schema_keys(group, target_schema);
58✔
883
        set_schema_version(group, target_schema_version);
58✔
884
        return;
58✔
885
    }
58✔
886

143✔
887
    if (schema_version == target_schema_version) {
286✔
888
        apply_non_migration_changes(group, changes);
58✔
889
        set_schema_keys(group, target_schema);
58✔
890
        return;
58✔
891
    }
58✔
892

114✔
893
    auto old_schema = schema_from_group(group);
228✔
894
    apply_pre_migration_changes(group, changes);
228✔
895
    HandleBackLinksAutomatically handle_backlinks =
228✔
896
        handle_automatically_backlinks ? HandleBackLinksAutomatically::Yes : HandleBackLinksAutomatically::No;
218✔
897
    if (migration_function) {
228✔
898
        set_schema_keys(group, target_schema);
156✔
899
        migration_function();
156✔
900

78✔
901
        // Migration function may have changed the schema, so we need to re-read it
78✔
902
        auto schema = schema_from_group(group);
156✔
903
        apply_post_migration_changes(group, schema.compare(target_schema, mode), old_schema, DidRereadSchema::Yes,
156✔
904
                                     handle_backlinks);
156✔
905
        group.validate_primary_columns();
156✔
906
    }
156✔
907
    else {
72✔
908
        apply_post_migration_changes(group, changes, {}, DidRereadSchema::No, handle_backlinks);
72✔
909
    }
72✔
910

114✔
911
    set_schema_version(group, target_schema_version);
228✔
912
    set_schema_keys(group, target_schema);
228✔
913
}
228✔
914

915
Schema ObjectStore::schema_from_group(Group const& group)
916
{
100,165✔
917
    std::vector<ObjectSchema> schema;
100,165✔
918
    schema.reserve(group.size());
100,165✔
919
    for (auto key : group.get_table_keys()) {
261,755✔
920
        auto object_type = object_type_for_table_name(group.get_table_name(key));
261,755✔
921
        if (object_type.size()) {
261,755✔
922
            schema.emplace_back(group, object_type, key);
195,757✔
923
        }
195,757✔
924
    }
261,755✔
925
    return schema;
100,165✔
926
}
100,165✔
927

928
void ObjectStore::set_schema_keys(Group const& group, Schema& schema)
929
{
22,008✔
930
    for (auto& object_schema : schema) {
61,877✔
931
        auto table = table_for_object_schema(group, object_schema);
61,877✔
932
        if (!table) {
61,877✔
933
            continue;
2✔
934
        }
2✔
935
        object_schema.table_key = table->get_key();
61,875✔
936
        for (auto& property : object_schema.persisted_properties) {
224,797✔
937
            property.column_key = table->get_column_key(property.name);
224,797✔
938
        }
224,797✔
939
    }
61,875✔
940
}
22,008✔
941

942
void ObjectStore::delete_data_for_object(Group& group, StringData object_type)
943
{
6✔
944
    if (TableRef table = table_for_object_type(group, object_type)) {
6✔
945
        group.remove_table(table->get_key());
4✔
946
    }
4✔
947
}
6✔
948

949
bool ObjectStore::is_empty(Group const& group)
950
{
2✔
951
    for (auto key : group.get_table_keys()) {
20✔
952
        ConstTableRef table = group.get_table(key);
20✔
953
        auto object_type = object_type_for_table_name(table->get_name());
20✔
954
        if (object_type.size() == 0 || object_type.begins_with("__")) {
20✔
955
            continue;
14✔
956
        }
14✔
957
        if (!table->is_empty()) {
6✔
UNCOV
958
            return false;
×
UNCOV
959
        }
×
960
    }
6✔
961
    return true;
2✔
962
}
2✔
963

964
void ObjectStore::rename_property(Group& group, Schema& target_schema, StringData object_type, StringData old_name,
965
                                  StringData new_name)
966
{
44✔
967
    TableRef table = table_for_object_type(group, object_type);
44✔
968
    if (!table) {
44✔
UNCOV
969
        throw LogicError(
×
UNCOV
970
            ErrorCodes::NoSuchTable,
×
UNCOV
971
            util::format("Cannot rename properties for type '%1' because it does not exist.", object_type));
×
UNCOV
972
    }
×
973

22✔
974
    auto target_object_schema = target_schema.find(object_type);
44✔
975
    if (target_object_schema == target_schema.end()) {
44✔
976
        throw LogicError(
2✔
977
            ErrorCodes::NoSuchTable,
2✔
978
            util::format("Cannot rename properties for type '%1' because it has been removed from the Realm.",
2✔
979
                         object_type));
2✔
980
    }
2✔
981

21✔
982
    if (target_object_schema->property_for_name(old_name)) {
42✔
983
        throw LogicError(
2✔
984
            ErrorCodes::IllegalOperation,
2✔
985
            util::format("Cannot rename property '%1.%2' to '%3' because the source property still exists.",
2✔
986
                         object_type, old_name, new_name));
2✔
987
    }
2✔
988

20✔
989
    ObjectSchema table_object_schema(group, object_type, table->get_key());
40✔
990
    Property* old_property = table_object_schema.property_for_name(old_name);
40✔
991
    if (!old_property) {
40✔
992
        throw LogicError(
4✔
993
            ErrorCodes::InvalidProperty,
4✔
994
            util::format("Cannot rename property '%1.%2' because it does not exist.", object_type, old_name));
4✔
995
    }
4✔
996

18✔
997
    Property* new_property = table_object_schema.property_for_name(new_name);
36✔
998
    if (!new_property) {
36✔
999
        // New property doesn't exist in the table, which means we're probably
3✔
1000
        // renaming to an intermediate property in a multi-version migration.
3✔
1001
        // This is safe because the migration will fail schema validation unless
3✔
1002
        // this property is renamed again to a valid name before the end.
3✔
1003
        table->rename_column(old_property->column_key, new_name);
6✔
1004
        return;
6✔
1005
    }
6✔
1006

15✔
1007
    if (old_property->type != new_property->type || old_property->object_type != new_property->object_type) {
30✔
1008
        throw LogicError(
8✔
1009
            ErrorCodes::IllegalOperation,
8✔
1010
            util::format("Cannot rename property '%1.%2' to '%3' because it would change from type '%4' to '%5'.",
8✔
1011
                         object_type, old_name, new_name, old_property->type_string(), new_property->type_string()));
8✔
1012
    }
8✔
1013

11✔
1014
    if (is_nullable(old_property->type) && !is_nullable(new_property->type)) {
22✔
1015
        throw LogicError(
2✔
1016
            ErrorCodes::IllegalOperation,
2✔
1017
            util::format("Cannot rename property '%1.%2' to '%3' because it would change from optional to required.",
2✔
1018
                         object_type, old_name, new_name));
2✔
1019
    }
2✔
1020

10✔
1021
    table->remove_column(new_property->column_key);
20✔
1022
    table->rename_column(old_property->column_key, new_name);
20✔
1023

10✔
1024
    if (auto prop = target_object_schema->property_for_name(new_name)) {
20✔
1025
        prop->column_key = old_property->column_key;
20✔
1026
    }
20✔
1027

10✔
1028
    // update nullability for column
10✔
1029
    if (is_nullable(new_property->type) && !is_nullable(old_property->type)) {
20✔
1030
        auto prop = *new_property;
2✔
1031
        prop.column_key = old_property->column_key;
2✔
1032
        make_property_optional(*table, prop);
2✔
1033
    }
2✔
1034
}
20✔
1035

1036
InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version,
1037
                                                             bool must_exactly_equal)
1038
    : LogicError(ErrorCodes::InvalidSchemaVersion,
1039
                 util::format(must_exactly_equal ? "Provided schema version %1 does not equal last set version %2."
1040
                                                 : "Provided schema version %1 is less than last set version %2.",
1041
                              new_version, old_version))
1042
    , m_old_version(old_version)
1043
    , m_new_version(new_version)
1044
{
6✔
1045
}
6✔
1046

1047
static void append_errors(std::string& message, std::vector<ObjectSchemaValidationException> const& errors)
1048
{
314✔
1049
    for (auto const& error : errors) {
378✔
1050
        message += "\n- ";
378✔
1051
        message += error.m_message;
378✔
1052
    }
378✔
1053
}
314✔
1054

1055
static void append_line(std::string& message, std::string_view line)
1056
{
48✔
1057
    message += "\n";
48✔
1058
    message += line;
48✔
1059
}
48✔
1060

1061
SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors)
1062
    : LogicError(ErrorCodes::SchemaValidationFailed, [&] {
152✔
1063
        std::string message = "Schema validation failed due to the following errors:";
152✔
1064
        append_errors(message, errors);
152✔
1065
        return message;
152✔
1066
    }())
152✔
1067
{
152✔
1068
}
152✔
1069

1070
SchemaMismatchException::SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors)
1071
    : LogicError(ErrorCodes::SchemaMismatch, [&] {
84✔
1072
        std::string message = "Migration is required due to the following errors:";
84✔
1073
        append_errors(message, errors);
84✔
1074
        return message;
84✔
1075
    }())
84✔
1076
{
84✔
1077
}
84✔
1078

1079
InvalidReadOnlySchemaChangeException::InvalidReadOnlySchemaChangeException(
1080
    std::vector<ObjectSchemaValidationException> const& errors)
1081
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
30✔
1082
        std::string message = "The following changes cannot be made in read-only schema mode:";
30✔
1083
        append_errors(message, errors);
30✔
1084
        return message;
30✔
1085
    }())
30✔
1086
{
30✔
1087
}
30✔
1088

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

1100
InvalidExternalSchemaChangeException::InvalidExternalSchemaChangeException(
1101
    std::vector<ObjectSchemaValidationException> const& errors)
1102
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
10✔
1103
        std::string message = "Unsupported schema changes were made by another client or process:";
10✔
1104
        append_errors(message, errors);
10✔
1105
        append_line(message, c_development_mode_msg);
10✔
1106
        return message;
10✔
1107
    }())
10✔
1108
{
10✔
1109
}
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