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

realm / realm-core / github_pull_request_275914

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

Pull #6073

Evergreen

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

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

96928 of 177706 branches covered (0.0%)

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

181 existing lines in 28 files now uncovered.

247505 of 266379 relevant lines covered (92.91%)

7164945.17 hits per line

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

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

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

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

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

73
DataType to_core_type(PropertyType type)
74
{
194,885✔
75
    REALM_ASSERT(type != PropertyType::Object); // Link columns have to be handled differently
194,885✔
76
    switch (type & ~PropertyType::Flags) {
194,885✔
77
        case PropertyType::Int:
47,917✔
78
            return type_Int;
47,917✔
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:
88,916✔
86
            return type_String;
88,916✔
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,064✔
92
            return type_ObjectId;
14,064✔
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,022✔
98
            return type_Mixed;
4,022✔
99
        default:
✔
100
            REALM_COMPILER_HINT_UNREACHABLE();
×
101
    }
194,885✔
102
}
194,885✔
103

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

109,364✔
110
    if (property.is_primary) {
174,749✔
111
        // Primary key columns should have been created when the table was created
10,399✔
112
        if (auto col = table.get_column_key(property.name)) {
10,399✔
113
            return col;
70,550✔
114
        }
8,828✔
115
    }
102,633✔
116
    if (property.type == PropertyType::Object) {
155,527✔
117
        auto target_name = ObjectStore::table_name_for_object_type(property.object_type);
73,573✔
118
        TableRef link_table = group.get_table(target_name);
11,851✔
119
        REALM_ASSERT(link_table);
11,851✔
120
        if (is_array(property.type)) {
108,354✔
121
            return table.add_column_list(*link_table, property.name);
4,209✔
122
        }
4,209✔
123
        else if (is_set(property.type)) {
104,145✔
124
            return table.add_column_set(*link_table, property.name);
1,702✔
125
        }
98,205✔
126
        else if (is_dictionary(property.type)) {
5,940✔
127
            return table.add_column_dictionary(*link_table, property.name);
1,749✔
128
        }
1,744✔
129
        else {
4,196✔
130
            return table.add_column(*link_table, property.name);
100,699✔
131
        }
100,699✔
132
    }
178,457✔
133
    else if (is_array(property.type)) {
94,128✔
134
        return table.add_column_list(to_core_type(property.type & ~PropertyType::Flags), property.name,
23,274✔
135
                                     is_nullable(property.type));
23,274✔
136
    }
23,274✔
137
    else if (is_set(property.type)) {
83,028✔
138
        return table.add_column_set(to_core_type(property.type & ~PropertyType::Flags), property.name,
92,878✔
139
                                    is_nullable(property.type));
92,878✔
140
    }
92,878✔
141
    else if (is_dictionary(property.type)) {
146,634✔
142
        return table.add_column_dictionary(to_core_type(property.type & ~PropertyType::Flags), property.name,
7,246✔
143
                                           is_nullable(property.type));
91,305✔
144
    }
6,977✔
145
    else {
139,658✔
146
        auto key = table.add_column(to_core_type(property.type), property.name, is_nullable(property.type));
139,658✔
147
        if (property.requires_index())
151,832✔
148
            table.add_search_index(key);
270✔
149
        if (property.requires_fulltext_index())
55,329✔
150
            table.add_fulltext_index(key);
9✔
151
        return key;
55,337✔
152
    }
55,337✔
153
}
93,813✔
154

155
void replace_column(Group& group, Table& table, Property const& old_property, Property const& new_property)
156
{
30,343✔
157
    table.remove_column(old_property.column_key);
30,343✔
158
    add_column(group, table, new_property);
8✔
159
}
30,343✔
160

30,335✔
161
TableRef create_table(Group& group, ObjectSchema const& object_schema)
3✔
162
{
29,722✔
163
    auto name = ObjectStore::table_name_for_object_type(object_schema.name);
60,054✔
164

44,199✔
165
    TableRef table = group.get_table(name);
29,736✔
166
    if (table)
44,185✔
167
        return table;
14,480✔
168

44,196✔
169
    if (auto* pk_property = object_schema.primary_key_property()) {
44,196✔
170
        auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
29,980✔
171
                              ? Table::Type::TopLevelAsymmetric
29,980✔
172
                              : Table::Type::TopLevel;
16,897✔
173
        table = group.add_table_with_primary_key(name, to_core_type(pk_property->type), pk_property->name,
16,897✔
174
                                                 is_nullable(pk_property->type), table_type);
27,208✔
175
    }
27,208✔
176
    else {
15,594✔
177
        if (object_schema.table_type == ObjectSchema::ObjectType::Embedded) {
28,677✔
178
            table = group.add_table(name, Table::Type::Embedded);
15,748✔
179
        }
15,748✔
180
        else {
28,784✔
181
            auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
12,929✔
182
                                  ? Table::Type::TopLevelAsymmetric
43,261✔
183
                                  : Table::Type::TopLevel;
43,261✔
184
            table = group.get_or_add_table(name, table_type);
12,929✔
185
        }
12,929✔
186
    }
45,926✔
187

60,051✔
188
    return table;
60,051✔
189
}
29,719✔
190

110,903✔
191
void add_initial_columns(Group& group, ObjectSchema const& object_schema)
110,903✔
192
{
29,719✔
193
    auto name = ObjectStore::table_name_for_object_type(object_schema.name);
140,622✔
194
    TableRef table = group.get_table(name);
44,196✔
195

126,145✔
196
    for (auto const& prop : object_schema.persisted_properties) {
204,279✔
197
#if REALM_ENABLE_SYNC
204,279✔
198
        // The sync::create_table* functions create the PK column for us.
138,185✔
199
        if (prop.is_primary)
107,853✔
200
            continue;
14,125✔
201
#endif // REALM_ENABLE_SYNC
93,732✔
202
        add_column(group, *table, prop);
93,732✔
203
    }
93,732✔
204
}
29,723✔
205

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

4✔
213
void make_property_required(Group& group, Table& table, Property property)
214
{
4✔
215
    property.type &= ~PropertyType::Nullable;
16✔
216
    table.remove_column(property.column_key);
16✔
217
    property.column_key = add_column(group, table, property);
16✔
218
}
4✔
219

220
void add_search_index(Table& table, Property property, IndexType type)
6✔
221
{
18✔
222
    table.add_search_index(table.get_column_key(property.name), type);
18✔
223
}
12✔
224

225
void remove_search_index(Table& table, Property property)
226
{
6✔
227
    table.remove_search_index(table.get_column_key(property.name));
10,862✔
228
}
10,862✔
229

10,856✔
230
} // anonymous namespace
10,856✔
231

232
void ObjectStore::set_schema_version(Group& group, uint64_t version)
233
{
44,845✔
234
    ::create_metadata_tables(group);
44,845✔
235
    ::set_schema_version(group, version);
44,845✔
236
}
21,513✔
237

10,905✔
238
uint64_t ObjectStore::get_schema_version(Group const& group)
23,332✔
239
{
56,733✔
240
    ConstTableRef table = group.get_table(c_metadataTableName);
33,401✔
241
    if (!table || table->get_column_count() == 0) {
33,401✔
242
        return ObjectStore::NotVersioned;
177,225✔
243
    }
177,225✔
244
    return table->get_object(0).get<int64_t>(c_versionColumnName);
156,420✔
245
}
156,420✔
246

32,924✔
247
StringData ObjectStore::object_type_for_table_name(StringData table_name)
32,924✔
248
{
162,344✔
249
    if (table_name.begins_with(c_object_table_prefix)) {
162,344✔
250
        return table_name.substr(sizeof(c_object_table_prefix) - 1);
268,142✔
251
    }
268,142✔
252
    return StringData();
170,382✔
253
}
32,292✔
254

255
std::string ObjectStore::table_name_for_object_type(StringData object_type)
33,887✔
256
{
169,265✔
257
    return std::string(c_object_table_prefix) + std::string(object_type);
169,265✔
258
}
169,265✔
259

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

266
ConstTableRef ObjectStore::table_for_object_type(Group const& group, StringData object_type)
267
{
30,266✔
268
    auto name = table_name_for_object_type(object_type);
30,266✔
269
    return group.get_table(name);
30,266✔
270
}
30,266✔
271

2✔
272
namespace {
2✔
273
struct SchemaDifferenceExplainer {
2✔
274
    std::vector<ObjectSchemaValidationException> errors;
275

276
    void operator()(schema_change::AddTable op)
98✔
277
    {
2✔
278
        errors.emplace_back("Class '%1' has been added.", op.object->name);
100✔
279
    }
2✔
280

281
    void operator()(schema_change::RemoveTable)
6✔
282
    {
104✔
283
        // We never do anything for RemoveTable
104✔
284
    }
104✔
285

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

292
    void operator()(schema_change::AddInitialProperties)
13✔
293
    {
15✔
294
        // Nothing. Always preceded by AddTable.
15✔
295
    }
2✔
296

297
    void operator()(schema_change::AddProperty op)
8✔
298
    {
21✔
299
        errors.emplace_back("Property '%1.%2' has been added.", op.object->name, op.property->name);
21✔
300
    }
13✔
301

302
    void operator()(schema_change::RemoveProperty op)
24✔
303
    {
32✔
304
        errors.emplace_back("Property '%1.%2' has been removed.", op.object->name, op.property->name);
32✔
305
    }
32✔
306

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

313
    void operator()(schema_change::MakePropertyNullable op)
5✔
314
    {
11✔
315
        errors.emplace_back("Property '%1.%2' has been made optional.", op.object->name, op.property->name);
11✔
316
    }
6✔
317

318
    void operator()(schema_change::MakePropertyRequired op)
13✔
319
    {
18✔
320
        errors.emplace_back("Property '%1.%2' has been made required.", op.object->name, op.property->name);
7✔
321
    }
7✔
322

2✔
323
    void operator()(schema_change::ChangePrimaryKey op)
11✔
324
    {
18✔
325
        if (op.property && !op.object->primary_key.empty()) {
18✔
326
            errors.emplace_back("Primary Key for class '%1' has changed from '%2' to '%3'.", op.object->name,
8✔
327
                                op.object->primary_key, op.property->name);
8✔
328
        }
8✔
329
        else if (op.property) {
24✔
330
            errors.emplace_back("Primary Key for class '%1' has been added.", op.object->name);
5✔
331
        }
5✔
332
        else {
10✔
333
            errors.emplace_back("Primary Key for class '%1' has been removed.", op.object->name);
10✔
334
        }
10✔
335
    }
13✔
336

337
    void operator()(schema_change::AddIndex op)
6✔
338
    {
10✔
339
        errors.emplace_back("Property '%1.%2' has been made indexed.", op.object->name, op.property->name);
10✔
340
    }
4✔
341

342
    void operator()(schema_change::RemoveIndex op)
343
    {
6✔
344
        errors.emplace_back("Property '%1.%2' has been made unindexed.", op.object->name, op.property->name);
6✔
345
    }
6✔
346
};
11,037✔
347

11,037✔
348
class TableHelper {
349
public:
350
    TableHelper(Group& g)
183✔
351
        : m_group(g)
183✔
352
    {
10,934✔
353
    }
10,934✔
354

145✔
355
    Table& operator()(const ObjectSchema* object_schema)
183✔
356
    {
366✔
357
        if (object_schema != m_current_object_schema) {
366✔
358
            m_current_table = table_for_object_schema(m_group, *object_schema);
145✔
359
            m_current_object_schema = object_schema;
145✔
360
        }
145✔
361
        REALM_ASSERT(m_current_table);
183✔
362
        return *m_current_table;
183✔
363
    }
183✔
364

365
private:
366
    Group& m_group;
367
    const ObjectSchema* m_current_object_schema = nullptr;
4,464✔
368
    TableRef m_current_table;
20,072✔
369
};
20,072✔
370

20,072✔
371
template <typename ErrorType, typename Verifier>
372
void verify_no_errors(Verifier&& verifier, std::vector<SchemaChange> const& changes)
4,464✔
373
{
4,381✔
374
    for (auto& change : changes) {
19,679✔
375
        change.visit(verifier);
24,062✔
376
    }
19,598✔
377

4,300✔
378
    if (!verifier.errors.empty()) {
4,300✔
379
        throw ErrorType(verifier.errors);
86✔
380
    }
86✔
381
}
4,305✔
382
} // anonymous namespace
5✔
383

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

5✔
438
    return std::any_of(begin(changes), end(changes), [](auto&& change) {
49✔
439
        return change.visit(Visitor());
49✔
440
    });
49✔
441
}
5✔
442

443
void ObjectStore::verify_no_changes_required(std::vector<SchemaChange> const& changes)
1✔
444
{
44✔
445
    verify_no_errors<SchemaMismatchException>(SchemaDifferenceExplainer(), changes);
44✔
446
}
44✔
447

448
void ObjectStore::verify_no_migration_required(std::vector<SchemaChange> const& changes)
449
{
1✔
450
    using namespace schema_change;
1✔
451
    struct Verifier : SchemaDifferenceExplainer {
1✔
452
        using SchemaDifferenceExplainer::operator();
1✔
453

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

464
bool ObjectStore::verify_valid_additive_changes(std::vector<SchemaChange> const& changes, bool update_indexes)
4,012✔
465
{
7,867✔
466
    using namespace schema_change;
3,855✔
467
    struct Verifier : SchemaDifferenceExplainer {
3,855✔
468
        using SchemaDifferenceExplainer::operator();
7,867✔
469

13,436✔
470
        bool index_changes = false;
13,436✔
471
        bool other_changes = false;
13,436✔
472

7,867✔
473
        // Additive mode allows adding things, extra columns, and adding/removing indexes
13,436✔
474
        void operator()(AddTable)
13,436✔
475
        {
18,923✔
476
            other_changes = true;
13,354✔
477
        }
9,373✔
478
        void operator()(AddInitialProperties)
3,886✔
479
        {
9,373✔
480
            other_changes = true;
9,405✔
481
        }
13,354✔
482
        void operator()(AddProperty)
3,864✔
483
        {
3,864✔
484
            other_changes = true;
40✔
485
        }
4,043✔
486
        void operator()(RemoveProperty) {}
3,861✔
487
        void operator()(AddIndex)
3,861✔
488
        {
3,861✔
489
            index_changes = true;
4,021✔
490
        }
4,021✔
491
        void operator()(RemoveIndex)
7,867✔
492
        {
7,867✔
493
            index_changes = true;
6✔
494
        }
6✔
495
    } verifier;
4,096✔
496
    verify_no_errors<InvalidAdditiveSchemaChangeException>(verifier, changes);
4,096✔
497
    return verifier.other_changes || (verifier.index_changes && update_indexes);
4,096✔
498
}
4,096✔
499

500
void ObjectStore::verify_valid_external_changes(std::vector<SchemaChange> const& changes)
501
{
243✔
502
    using namespace schema_change;
243✔
503
    struct Verifier : SchemaDifferenceExplainer {
250✔
504
        using SchemaDifferenceExplainer::operator();
754✔
505

237✔
506
        // Adding new things is fine
234✔
507
        void operator()(AddTable) {}
475✔
508
        void operator()(AddInitialProperties) {}
235✔
509
        void operator()(AddProperty) {}
235✔
510
        void operator()(AddIndex) {}
525✔
511
        void operator()(RemoveIndex) {}
475✔
512
        // Deleting tables is not okay
475✔
513
        void operator()(RemoveTable op)
475✔
514
        {
234✔
515
            errors.emplace_back("Class '%1' has been removed.", op.object->name);
1✔
516
        }
139✔
517
    } verifier;
372✔
518
    verify_no_errors<InvalidExternalSchemaChangeException>(verifier, changes);
372✔
519
}
372✔
520

521
void ObjectStore::verify_compatible_for_immutable_and_readonly(std::vector<SchemaChange> const& changes)
7✔
522
{
145✔
523
    using namespace schema_change;
140✔
524
    struct Verifier : SchemaDifferenceExplainer {
143✔
525
        using SchemaDifferenceExplainer::operator();
140✔
526

140✔
527
        void operator()(AddTable) {}
276✔
528
        void operator()(AddInitialProperties) {}
276✔
529
        void operator()(ChangeTableType) {}
276✔
530
        void operator()(RemoveProperty) {}
138✔
531
        void operator()(AddIndex) {}
138✔
532
        void operator()(RemoveIndex) {}
167✔
533
    } verifier;
167✔
534
    verify_no_errors<InvalidReadOnlySchemaChangeException>(verifier, changes);
167✔
535
}
167✔
536

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

41✔
549
        // Produce an exception listing the unsupported schema changes for
41✔
550
        // everything but the explicitly supported ones
41✔
551
        using SchemaDifferenceExplainer::operator();
58✔
552

41✔
553
        void operator()(AddTable op)
41✔
554
        {
41✔
555
            create_table(group, *op.object);
41✔
556
        }
16✔
557
        void operator()(AddInitialProperties op)
33✔
558
        {
33✔
559
            add_initial_columns(group, *op.object);
41✔
560
        }
15✔
561
        void operator()(AddIndex op)
32✔
562
        {
32✔
563
            table(op.object).add_search_index(op.property->column_key, op.type);
33✔
564
        }
33✔
565
        void operator()(RemoveIndex op)
58✔
566
        {
29✔
567
            table(op.object).remove_search_index(op.property->column_key);
3✔
568
        }
25✔
569
    } applier{group};
51✔
570
    verify_no_errors<SchemaMismatchException>(applier, changes);
51✔
571
}
48✔
572

19✔
573
static void set_primary_key(Table& table, const Property* property)
19✔
574
{
44✔
575
    ColKey col;
44✔
576
    if (property) {
22✔
577
        col = table.get_column_key(property->name);
19✔
578
        REALM_ASSERT(col);
6,856✔
579
    }
6,856✔
580
    table.set_primary_key_column(col);
6,859✔
581
}
6,859✔
582

6,837✔
583
static void create_initial_tables(Group& group, std::vector<SchemaChange> const& changes)
6,837✔
584
{
13,576✔
585
    using namespace schema_change;
13,576✔
586
    struct Applier {
13,576✔
587
        Applier(Group& group)
13,576✔
588
            : group{group}
6,739✔
589
            , table{group}
13,576✔
590
        {
27,478✔
591
        }
27,478✔
592
        Group& group;
27,478✔
593
        TableHelper table;
6,739✔
594

13,576✔
595
        void operator()(AddTable op)
27,478✔
596
        {
41,096✔
597
            create_table(group, *op.object);
41,096✔
598
        }
20,357✔
599
        void operator()(RemoveTable) {}
6,739✔
600
        void operator()(AddInitialProperties op)
6,739✔
601
        {
20,357✔
602
            add_initial_columns(group, *op.object);
20,357✔
603
        }
20,357✔
604

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

6,739✔
643
        void operator()(ChangePropertyType op)
48,217✔
644
        {
48,217✔
645
            replace_column(group, table(op.object), *op.old_property, *op.new_property);
41,478✔
646
        }
6,837✔
647
    } applier{group};
6,739✔
648

6,739✔
649
    for (auto& change : changes) {
44,670✔
650
        change.visit(applier);
44,670✔
651
    }
44,670✔
652
}
10,695✔
653

3,956✔
654
void ObjectStore::apply_additive_changes(Group& group, std::vector<SchemaChange> const& changes, bool update_indexes)
3,956✔
655
{
7,762✔
656
    using namespace schema_change;
7,762✔
657
    struct Applier {
7,762✔
658
        Applier(Group& group, bool update_indexes)
7,762✔
659
            : group{group}
7,762✔
660
            , table{group}
7,762✔
661
            , update_indexes{update_indexes}
3,806✔
662
        {
7,762✔
663
        }
13,379✔
664
        Group& group;
13,379✔
665
        TableHelper table;
13,379✔
666
        bool update_indexes;
3,806✔
667

7,762✔
668
        void operator()(AddTable op)
13,379✔
669
        {
18,915✔
670
            create_table(group, *op.object);
18,915✔
671
        }
13,298✔
672
        void operator()(RemoveTable) {}
3,837✔
673
        void operator()(AddInitialProperties op)
3,837✔
674
        {
9,373✔
675
            add_initial_columns(group, *op.object);
13,298✔
676
        }
9,347✔
677
        void operator()(AddProperty op)
3,811✔
678
        {
3,811✔
679
            add_column(group, table(op.object), *op.property);
36✔
680
        }
36✔
681
        void operator()(AddIndex op)
7,762✔
682
        {
3,808✔
683
            if (update_indexes) {
7✔
684
                add_search_index(table(op.object), *op.property, op.type);
7✔
685
            }
7✔
686
        }
14✔
687
        void operator()(RemoveIndex op)
3,806✔
688
        {
3,806✔
689
            if (update_indexes)
2✔
690
                table(op.object).remove_search_index(op.property->column_key);
2✔
691
        }
2✔
692
        void operator()(RemoveProperty) {}
3,806✔
693

3,806✔
694
        // No need for errors for these, as we've already verified that they aren't present
7,762✔
695
        void operator()(ChangeTableType) {}
3,806✔
696
        void operator()(ChangePrimaryKey) {}
22,999✔
697
        void operator()(ChangePropertyType) {}
22,999✔
698
        void operator()(MakePropertyNullable) {}
22,999✔
699
        void operator()(MakePropertyRequired) {}
7,762✔
700
    } applier{group, update_indexes};
3,806✔
701

3,806✔
702
    for (auto& change : changes) {
18,845✔
703
        change.visit(applier);
18,845✔
704
    }
18,845✔
705
}
3,920✔
706

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

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

114✔
764
    for (auto& change : changes) {
168✔
765
        change.visit(applier);
168✔
766
    }
168✔
767
}
114✔
768

769
enum class DidRereadSchema { Yes, No };
101✔
770
enum class HandleBackLinksAutomatically { Yes, No };
101✔
771

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

102✔
793
        void operator()(RemoveProperty op)
139✔
794
        {
139✔
795
            if (!initial_schema.empty() &&
77✔
796
                !initial_schema.find(op.object->name)->property_for_name(op.property->name))
39✔
797
                throw LogicError(ErrorCodes::InvalidProperty, util::format("Renamed property '%1.%2' does not exist.",
102✔
798
                                                                           op.object->name, op.property->name));
23✔
799
            auto table = table_for_object_schema(group, *op.object);
60✔
800
            table->remove_column(op.property->column_key);
60✔
801
        }
38✔
802

202✔
803
        void operator()(ChangePrimaryKey op)
105✔
804
        {
105✔
805
            set_primary_key(table(op.object), op.property);
26✔
806
        }
22✔
807

202✔
808
        void operator()(AddTable op)
105✔
809
        {
105✔
810
            create_table(group, *op.object);
5✔
811
        }
7✔
812

101✔
813
        void operator()(AddInitialProperties op)
101✔
814
        {
104✔
815
            if (did_reread_schema)
8✔
816
                add_initial_columns(group, *op.object);
1✔
817
            else {
104✔
818
                // If we didn't re-read the schema then AddInitialProperties was already taken care of
16✔
819
                // during apply_pre_migration_changes.
16✔
820
            }
16✔
821
        }
105✔
822

104✔
823
        void operator()(AddIndex op)
104✔
824
        {
104✔
825
            table(op.object).add_search_index(op.property->column_key);
13✔
826
        }
114✔
827
        void operator()(RemoveIndex op)
126✔
828
        {
126✔
829
            table(op.object).remove_search_index(op.property->column_key);
28✔
830
        }
28✔
831

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

101✔
844
    for (auto& change : changes) {
120✔
845
        change.visit(applier);
120✔
846
    }
120✔
847
}
101✔
848

10,967✔
849

10,967✔
850
void ObjectStore::apply_schema_changes(Transaction& group, uint64_t schema_version, Schema& target_schema,
851
                                       uint64_t target_schema_version, SchemaMode mode,
10,967✔
852
                                       std::vector<SchemaChange> const& changes, bool handle_automatically_backlinks,
3,956✔
853
                                       std::function<void()> migration_function)
3,956✔
854
{
10,719✔
855
    create_metadata_tables(group);
10,719✔
856

14,675✔
857
    if (mode == SchemaMode::AdditiveDiscovered || mode == SchemaMode::AdditiveExplicit) {
14,675✔
858
        bool target_schema_is_newer =
3,806✔
859
            (schema_version < target_schema_version || schema_version == ObjectStore::NotVersioned);
7,762✔
860

7,713✔
861
        // With sync v2.x, indexes are no longer synced, so there's no reason to avoid creating them.
3,806✔
862
        bool update_indexes = true;
7,762✔
863
        apply_additive_changes(group, changes, update_indexes);
7,762✔
864

7,762✔
865
        if (target_schema_is_newer)
3,806✔
866
            set_schema_version(group, target_schema_version);
10,768✔
867

10,645✔
868
        set_schema_keys(group, target_schema);
10,643✔
869
        return;
10,643✔
870
    }
10,645✔
871

13,752✔
872
    if (schema_version == ObjectStore::NotVersioned) {
13,752✔
873
        if (mode != SchemaMode::ReadOnly) {
13,580✔
874
            create_initial_tables(group, changes);
6,739✔
875
        }
6,911✔
876
        set_schema_version(group, target_schema_version);
6,770✔
877
        set_schema_keys(group, target_schema);
6,768✔
878
        return;
6,768✔
879
    }
6,741✔
880

201✔
881
    if (mode == SchemaMode::Manual) {
201✔
882
        if (migration_function) {
58✔
883
            migration_function();
56✔
884
        }
56✔
885

58✔
886
        verify_no_changes_required(schema_from_group(group).compare(target_schema));
29✔
887
        group.validate_primary_columns();
172✔
888
        set_schema_keys(group, target_schema);
58✔
889
        set_schema_version(group, target_schema_version);
58✔
890
        return;
58✔
891
    }
58✔
892

143✔
893
    if (schema_version == target_schema_version) {
257✔
894
        apply_non_migration_changes(group, changes);
143✔
895
        set_schema_keys(group, target_schema);
143✔
896
        return;
133✔
897
    }
143✔
898

192✔
899
    auto old_schema = schema_from_group(group);
192✔
900
    apply_pre_migration_changes(group, changes);
114✔
901
    HandleBackLinksAutomatically handle_backlinks =
114✔
902
        handle_automatically_backlinks ? HandleBackLinksAutomatically::Yes : HandleBackLinksAutomatically::No;
192✔
903
    if (migration_function) {
192✔
904
        set_schema_keys(group, target_schema);
156✔
905
        migration_function();
156✔
906

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

50,119✔
917
    set_schema_version(group, target_schema_version);
50,119✔
918
    set_schema_keys(group, target_schema);
50,119✔
919
}
130,683✔
920

130,569✔
921
Schema ObjectStore::schema_from_group(Group const& group)
130,569✔
922
{
146,546✔
923
    std::vector<ObjectSchema> schema;
146,546✔
924
    schema.reserve(group.size());
179,463✔
925
    for (auto key : group.get_table_keys()) {
177,758✔
926
        auto object_type = object_type_for_table_name(group.get_table_name(key));
177,758✔
927
        if (object_type.size()) {
127,753✔
928
            schema.emplace_back(group, object_type, key);
95,468✔
929
        }
106,459✔
930
    }
158,615✔
931
    return schema;
79,756✔
932
}
79,756✔
933

1✔
934
void ObjectStore::set_schema_keys(Group const& group, Schema& schema)
1✔
935
{
41,604✔
936
    for (auto& object_schema : schema) {
142,742✔
937
        auto table = table_for_object_schema(group, object_schema);
142,742✔
938
        if (!table) {
142,742✔
939
            continue;
30,862✔
940
        }
10,992✔
941
        object_schema.table_key = table->get_key();
30,248✔
942
        for (auto& property : object_schema.persisted_properties) {
109,443✔
943
            property.column_key = table->get_column_key(property.name);
109,446✔
944
        }
109,446✔
945
    }
30,250✔
946
}
10,745✔
947

3✔
948
void ObjectStore::delete_data_for_object(Group& group, StringData object_type)
949
{
3✔
950
    if (TableRef table = table_for_object_type(group, object_type)) {
4✔
951
        group.remove_table(table->get_key());
12✔
952
    }
12✔
953
}
13✔
954

10✔
955
bool ObjectStore::is_empty(Group const& group)
7✔
956
{
8✔
957
    for (auto key : group.get_table_keys()) {
13✔
958
        ConstTableRef table = group.get_table(key);
10✔
959
        auto object_type = object_type_for_table_name(table->get_name());
10✔
960
        if (object_type.size() == 0 || object_type.begins_with("__")) {
13✔
961
            continue;
8✔
962
        }
8✔
963
        if (!table->is_empty()) {
3✔
964
            return false;
965
        }
966
    }
25✔
967
    return true;
23✔
968
}
23✔
969

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

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

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

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

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

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

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

20✔
1027
    table->remove_column(new_property->column_key);
10✔
1028
    table->rename_column(old_property->column_key, new_name);
10✔
1029

20✔
1030
    if (auto prop = target_object_schema->property_for_name(new_name)) {
11✔
1031
        prop->column_key = old_property->column_key;
11✔
1032
    }
11✔
1033

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

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

189✔
1053
static void append_errors(std::string& message, std::vector<ObjectSchemaValidationException> const& errors)
157✔
1054
{
157✔
1055
    for (auto const& error : errors) {
189✔
1056
        message += "\n- ";
213✔
1057
        message += error.m_message;
213✔
1058
    }
213✔
1059
}
181✔
1060

1061
static void append_line(std::string& message, std::string_view line)
1062
{
100✔
1063
    message += "\n";
100✔
1064
    message += line;
100✔
1065
}
100✔
1066

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

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

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

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

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

© 2026 Coveralls, Inc