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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

95.03
/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
#include <realm/util/scope_exit.hpp>
31

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

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

39
using namespace realm;
40

41
constexpr uint64_t ObjectStore::NotVersioned;
42

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

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

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

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

21,393✔
57
    if (metadata_table->get_column_count() == 0) {
43,238✔
58
        metadata_table->add_column(type_Int, c_versionColumnName);
21,278✔
59
        metadata_table->create_object().set(c_versionColumnName, int64_t(ObjectStore::NotVersioned));
21,278✔
60
    }
21,278✔
61
}
43,238✔
62

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

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

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

105
std::optional<CollectionType> process_collection(const Property& property)
106
{
190,968✔
107
    // check if the final type is itself a collection.
94,146✔
108
    if (is_array(property.type)) {
190,968✔
109
        return CollectionType::List;
30,928✔
110
    }
30,928✔
111
    else if (is_set(property.type)) {
160,040✔
112
        return CollectionType::Set;
20,657✔
113
    }
20,657✔
114
    else if (is_dictionary(property.type)) {
139,383✔
115
        return CollectionType::Dictionary;
17,600✔
116
    }
17,600✔
117
    return {};
121,783✔
118
}
121,783✔
119

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

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

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

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

29,788✔
160
    TableRef table = group.get_table(name);
60,167✔
161
    if (table)
60,167✔
162
        return table;
6✔
163

29,785✔
164
    if (auto* pk_property = object_schema.primary_key_property()) {
60,161✔
165
        auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
28,733✔
166
                              ? Table::Type::TopLevelAsymmetric
14,216✔
167
                              : Table::Type::TopLevel;
28,715✔
168
        table = group.add_table_with_primary_key(name, to_core_type(pk_property->type), pk_property->name,
28,733✔
169
                                                 is_nullable(pk_property->type), table_type);
28,733✔
170
    }
28,733✔
171
    else {
31,428✔
172
        if (object_schema.table_type == ObjectSchema::ObjectType::Embedded) {
31,428✔
173
            table = group.add_table(name, Table::Type::Embedded);
5,445✔
174
        }
5,445✔
175
        else {
25,983✔
176
            auto table_type = object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric
25,983✔
177
                                  ? Table::Type::TopLevelAsymmetric
12,918✔
178
                                  : Table::Type::TopLevel;
25,983✔
179
            table = group.get_or_add_table(name, table_type);
25,983✔
180
        }
25,983✔
181
    }
31,428✔
182

29,785✔
183
    return table;
60,161✔
184
}
60,161✔
185

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

29,785✔
191
    for (auto const& prop : object_schema.persisted_properties) {
219,547✔
192
#if REALM_ENABLE_SYNC
219,547✔
193
        // The sync::create_table* functions create the PK column for us.
108,267✔
194
        if (prop.is_primary)
219,547✔
195
            continue;
28,733✔
196
#endif // REALM_ENABLE_SYNC
190,814✔
197
        add_column(group, *table, prop);
190,814✔
198
    }
190,814✔
199
}
60,161✔
200

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

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

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

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

225
} // anonymous namespace
226

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

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

242
StringData ObjectStore::object_type_for_table_name(StringData table_name)
243
{
329,814✔
244
    if (table_name.begins_with(c_object_table_prefix)) {
329,814✔
245
        return table_name.substr(sizeof(c_object_table_prefix) - 1);
264,389✔
246
    }
264,389✔
247
    return StringData();
65,425✔
248
}
65,425✔
249

250
std::string ObjectStore::table_name_for_object_type(StringData object_type)
251
{
274,027✔
252
    return std::string(c_object_table_prefix) + std::string(object_type);
274,027✔
253
}
274,027✔
254

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

343
class TableHelper {
344
public:
345
    TableHelper(Group& g)
346
        : m_group(g)
347
    {
21,870✔
348
    }
21,870✔
349

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

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

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

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

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

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

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

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

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

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

3,858✔
465
        bool index_changes = false;
7,869✔
466
        bool other_changes = false;
7,869✔
467

3,858✔
468
        // Additive mode allows adding things, extra columns, and adding/removing indexes
3,858✔
469
        void operator()(AddTable)
7,869✔
470
        {
18,955✔
471
            other_changes = true;
18,955✔
472
        }
18,955✔
473
        void operator()(AddInitialProperties)
7,869✔
474
        {
18,955✔
475
            other_changes = true;
18,955✔
476
        }
18,955✔
477
        void operator()(AddProperty)
7,869✔
478
        {
3,889✔
479
            other_changes = true;
62✔
480
        }
62✔
481
        void operator()(RemoveProperty) {}
3,921✔
482
        void operator()(AddIndex)
7,869✔
483
        {
3,867✔
484
            index_changes = true;
18✔
485
        }
18✔
486
        void operator()(RemoveIndex)
7,869✔
487
        {
3,864✔
488
            index_changes = true;
12✔
489
        }
12✔
490
    } verifier;
7,869✔
491
    verify_no_errors<InvalidAdditiveSchemaChangeException>(verifier, changes);
7,869✔
492
    return verifier.other_changes || (verifier.index_changes && update_indexes);
7,869✔
493
}
7,869✔
494

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

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

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

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

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

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

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

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

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

6,769✔
590
        void operator()(AddTable op)
13,614✔
591
        {
41,170✔
592
            create_table(group, *op.object);
41,170✔
593
        }
41,170✔
594
        void operator()(RemoveTable) {}
6,769✔
595
        void operator()(AddInitialProperties op)
13,614✔
596
        {
41,170✔
597
            add_initial_columns(group, *op.object);
41,170✔
598
        }
41,170✔
599

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

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

6,769✔
644
    for (auto& change : changes) {
82,340✔
645
        change.visit(applier);
82,340✔
646
    }
82,340✔
647
}
13,614✔
648

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

3,809✔
663
        void operator()(AddTable op)
7,768✔
664
        {
18,951✔
665
            create_table(group, *op.object);
18,951✔
666
        }
18,951✔
667
        void operator()(RemoveTable) {}
3,809✔
668
        void operator()(AddInitialProperties op)
7,768✔
669
        {
18,951✔
670
            add_initial_columns(group, *op.object);
18,951✔
671
        }
18,951✔
672
        void operator()(AddProperty op)
7,768✔
673
        {
3,840✔
674
            add_column(group, table(op.object), *op.property);
62✔
675
        }
62✔
676
        void operator()(AddIndex op)
7,768✔
677
        {
3,814✔
678
            if (update_indexes) {
10✔
679
                add_search_index(table(op.object), *op.property, op.type);
10✔
680
            }
10✔
681
        }
10✔
682
        void operator()(RemoveIndex op)
7,768✔
683
        {
3,811✔
684
            if (update_indexes)
4✔
685
                table(op.object).remove_search_index(op.property->column_key);
4✔
686
        }
4✔
687
        void operator()(RemoveProperty) {}
3,818✔
688

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

3,809✔
697
    for (auto& change : changes) {
37,996✔
698
        change.visit(applier);
37,996✔
699
    }
37,996✔
700
}
7,768✔
701

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

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

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

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

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

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

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

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

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

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

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

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

844
static const char* schema_mode_to_string(SchemaMode mode)
845
{
21,730✔
846
    switch (mode) {
21,730✔
847
        case SchemaMode::Automatic:
13,814✔
848
            return "Automatic";
13,814✔
NEW
849
        case SchemaMode::Immutable:
✔
NEW
850
            return "Immutable";
×
851
        case SchemaMode::ReadOnly:
4✔
852
            return "ReadOnly";
4✔
853
        case SchemaMode::SoftResetFile:
32✔
854
            return "SoftResetFile";
32✔
855
        case SchemaMode::HardResetFile:
14✔
856
            return "HardResetFile";
14✔
857
        case SchemaMode::AdditiveDiscovered:
82✔
858
            return "AdditiveDiscovered";
82✔
859
        case SchemaMode::AdditiveExplicit:
7,686✔
860
            return "AdditiveExplicit";
7,686✔
861
        case SchemaMode::Manual:
98✔
862
            return "Manual";
98✔
NEW
863
    }
×
NEW
864
    return "";
×
NEW
865
}
×
866

867
void ObjectStore::apply_schema_changes(Transaction& transaction, uint64_t schema_version, Schema& target_schema,
868
                                       uint64_t target_schema_version, SchemaMode mode,
869
                                       std::vector<SchemaChange> const& changes, bool handle_automatically_backlinks,
870
                                       std::function<void()> migration_function)
871
{
21,730✔
872
    using namespace std::chrono;
21,730✔
873
    auto t1 = steady_clock::now();
21,730✔
874
    auto logger = transaction.get_logger();
21,730✔
875
    if (schema_version == ObjectStore::NotVersioned) {
21,730✔
876
        logger->debug("Creating schema version %1 in mode '%2'", target_schema_version, schema_mode_to_string(mode));
21,264✔
877
    }
21,264✔
878
    else {
466✔
879
        logger->debug("Migrating from schema version %1 to %2 in mode '%3'", schema_version, target_schema_version,
466✔
880
                      schema_mode_to_string(mode));
466✔
881
    }
466✔
882
    util::ScopeExit cleanup([&]() noexcept {
21,730✔
883
        auto t2 = steady_clock::now();
21,730✔
884
        logger->debug("Migration did run in %1 us (%2 changes)", duration_cast<microseconds>(t2 - t1).count(),
21,730✔
885
                      changes.size());
21,730✔
886
    });
21,730✔
887

10,752✔
888
    create_metadata_tables(transaction);
21,730✔
889

10,752✔
890
    if (mode == SchemaMode::AdditiveDiscovered || mode == SchemaMode::AdditiveExplicit) {
21,730✔
891
        bool target_schema_is_newer =
7,768✔
892
            (schema_version < target_schema_version || schema_version == ObjectStore::NotVersioned);
7,768✔
893

3,809✔
894
        // With sync v2.x, indexes are no longer synced, so there's no reason to avoid creating them.
3,809✔
895
        bool update_indexes = true;
7,768✔
896
        apply_additive_changes(transaction, changes, update_indexes);
7,768✔
897

3,809✔
898
        if (target_schema_is_newer)
7,768✔
899
            set_schema_version(transaction, target_schema_version);
7,670✔
900

3,809✔
901
        set_schema_keys(transaction, target_schema);
7,768✔
902
        return;
7,768✔
903
    }
7,768✔
904

6,943✔
905
    if (schema_version == ObjectStore::NotVersioned) {
13,962✔
906
        if (mode != SchemaMode::ReadOnly) {
13,618✔
907
            create_initial_tables(transaction, changes);
13,614✔
908
        }
13,614✔
909
        set_schema_version(transaction, target_schema_version);
13,618✔
910
        set_schema_keys(transaction, target_schema);
13,618✔
911
        return;
13,618✔
912
    }
13,618✔
913

172✔
914
    auto call_migration = [&] {
344✔
915
        logger->debug("Calling migration function");
210✔
916
        auto t3 = steady_clock::now();
210✔
917
        migration_function();
210✔
918
        auto t4 = steady_clock::now();
210✔
919
        logger->debug("Migration function did run in %1 us", duration_cast<microseconds>(t4 - t3).count());
210✔
920
    };
210✔
921

172✔
922
    if (mode == SchemaMode::Manual) {
344✔
923
        if (migration_function) {
58✔
924
            call_migration();
54✔
925
        }
54✔
926

29✔
927
        verify_no_changes_required(schema_from_group(transaction).compare(target_schema));
58✔
928
        transaction.validate_primary_columns();
58✔
929
        set_schema_keys(transaction, target_schema);
58✔
930
        set_schema_version(transaction, target_schema_version);
58✔
931
        return;
58✔
932
    }
58✔
933

143✔
934
    if (schema_version == target_schema_version) {
286✔
935
        apply_non_migration_changes(transaction, changes);
58✔
936
        set_schema_keys(transaction, target_schema);
58✔
937
        return;
58✔
938
    }
58✔
939

114✔
940
    auto old_schema = schema_from_group(transaction);
228✔
941
    apply_pre_migration_changes(transaction, changes);
228✔
942
    HandleBackLinksAutomatically handle_backlinks =
228✔
943
        handle_automatically_backlinks ? HandleBackLinksAutomatically::Yes : HandleBackLinksAutomatically::No;
218✔
944
    if (migration_function) {
228✔
945
        set_schema_keys(transaction, target_schema);
156✔
946
        call_migration();
156✔
947

78✔
948
        // Migration function may have changed the schema, so we need to re-read it
78✔
949
        auto schema = schema_from_group(transaction);
156✔
950
        apply_post_migration_changes(transaction, schema.compare(target_schema, mode), old_schema,
156✔
951
                                     DidRereadSchema::Yes, handle_backlinks);
156✔
952
        transaction.validate_primary_columns();
156✔
953
    }
156✔
954
    else {
72✔
955
        apply_post_migration_changes(transaction, changes, {}, DidRereadSchema::No, handle_backlinks);
72✔
956
    }
72✔
957

114✔
958
    set_schema_version(transaction, target_schema_version);
228✔
959
    set_schema_keys(transaction, target_schema);
228✔
960
}
228✔
961

962
Schema ObjectStore::schema_from_group(Group const& group)
963
{
99,102✔
964
    std::vector<ObjectSchema> schema;
99,102✔
965
    schema.reserve(group.size());
99,102✔
966
    for (auto key : group.get_table_keys()) {
258,862✔
967
        auto object_type = object_type_for_table_name(group.get_table_name(key));
258,862✔
968
        if (object_type.size()) {
258,862✔
969
            schema.emplace_back(group, object_type, key);
193,465✔
970
        }
193,465✔
971
    }
258,862✔
972
    return schema;
99,102✔
973
}
99,102✔
974

975
void ObjectStore::set_schema_keys(Group const& group, Schema& schema)
976
{
21,778✔
977
    for (auto& object_schema : schema) {
61,221✔
978
        auto table = table_for_object_schema(group, object_schema);
61,221✔
979
        if (!table) {
61,221✔
980
            continue;
2✔
981
        }
2✔
982
        object_schema.table_key = table->get_key();
61,219✔
983
        for (auto& property : object_schema.persisted_properties) {
222,727✔
984
            property.column_key = table->get_column_key(property.name);
222,727✔
985
        }
222,727✔
986
    }
61,219✔
987
}
21,778✔
988

989
void ObjectStore::delete_data_for_object(Group& group, StringData object_type)
990
{
6✔
991
    if (TableRef table = table_for_object_type(group, object_type)) {
6✔
992
        group.remove_table(table->get_key());
4✔
993
    }
4✔
994
}
6✔
995

996
bool ObjectStore::is_empty(Group const& group)
997
{
4✔
998
    for (auto key : group.get_table_keys()) {
40✔
999
        ConstTableRef table = group.get_table(key);
40✔
1000
        auto object_type = object_type_for_table_name(table->get_name());
40✔
1001
        if (object_type.size() == 0 || object_type.begins_with("__")) {
40✔
1002
            continue;
28✔
1003
        }
28✔
1004
        if (!table->is_empty()) {
12✔
1005
            return false;
×
1006
        }
×
1007
    }
12✔
1008
    return true;
4✔
1009
}
4✔
1010

1011
void ObjectStore::rename_property(Group& group, Schema& target_schema, StringData object_type, StringData old_name,
1012
                                  StringData new_name)
1013
{
44✔
1014
    TableRef table = table_for_object_type(group, object_type);
44✔
1015
    if (!table) {
44✔
1016
        throw LogicError(
×
1017
            ErrorCodes::NoSuchTable,
×
1018
            util::format("Cannot rename properties for type '%1' because it does not exist.", object_type));
×
1019
    }
×
1020

22✔
1021
    auto target_object_schema = target_schema.find(object_type);
44✔
1022
    if (target_object_schema == target_schema.end()) {
44✔
1023
        throw LogicError(
2✔
1024
            ErrorCodes::NoSuchTable,
2✔
1025
            util::format("Cannot rename properties for type '%1' because it has been removed from the Realm.",
2✔
1026
                         object_type));
2✔
1027
    }
2✔
1028

21✔
1029
    if (target_object_schema->property_for_name(old_name)) {
42✔
1030
        throw LogicError(
2✔
1031
            ErrorCodes::IllegalOperation,
2✔
1032
            util::format("Cannot rename property '%1.%2' to '%3' because the source property still exists.",
2✔
1033
                         object_type, old_name, new_name));
2✔
1034
    }
2✔
1035

20✔
1036
    ObjectSchema table_object_schema(group, object_type, table->get_key());
40✔
1037
    Property* old_property = table_object_schema.property_for_name(old_name);
40✔
1038
    if (!old_property) {
40✔
1039
        throw LogicError(
4✔
1040
            ErrorCodes::InvalidProperty,
4✔
1041
            util::format("Cannot rename property '%1.%2' because it does not exist.", object_type, old_name));
4✔
1042
    }
4✔
1043

18✔
1044
    Property* new_property = table_object_schema.property_for_name(new_name);
36✔
1045
    if (!new_property) {
36✔
1046
        // New property doesn't exist in the table, which means we're probably
3✔
1047
        // renaming to an intermediate property in a multi-version migration.
3✔
1048
        // This is safe because the migration will fail schema validation unless
3✔
1049
        // this property is renamed again to a valid name before the end.
3✔
1050
        table->rename_column(old_property->column_key, new_name);
6✔
1051
        return;
6✔
1052
    }
6✔
1053

15✔
1054
    if (old_property->type != new_property->type || old_property->object_type != new_property->object_type) {
30✔
1055
        throw LogicError(
8✔
1056
            ErrorCodes::IllegalOperation,
8✔
1057
            util::format("Cannot rename property '%1.%2' to '%3' because it would change from type '%4' to '%5'.",
8✔
1058
                         object_type, old_name, new_name, old_property->type_string(), new_property->type_string()));
8✔
1059
    }
8✔
1060

11✔
1061
    if (is_nullable(old_property->type) && !is_nullable(new_property->type)) {
22✔
1062
        throw LogicError(
2✔
1063
            ErrorCodes::IllegalOperation,
2✔
1064
            util::format("Cannot rename property '%1.%2' to '%3' because it would change from optional to required.",
2✔
1065
                         object_type, old_name, new_name));
2✔
1066
    }
2✔
1067

10✔
1068
    table->remove_column(new_property->column_key);
20✔
1069
    table->rename_column(old_property->column_key, new_name);
20✔
1070

10✔
1071
    if (auto prop = target_object_schema->property_for_name(new_name)) {
20✔
1072
        prop->column_key = old_property->column_key;
20✔
1073
    }
20✔
1074

10✔
1075
    // update nullability for column
10✔
1076
    if (is_nullable(new_property->type) && !is_nullable(old_property->type)) {
20✔
1077
        auto prop = *new_property;
2✔
1078
        prop.column_key = old_property->column_key;
2✔
1079
        make_property_optional(*table, prop);
2✔
1080
    }
2✔
1081
}
20✔
1082

1083
InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version,
1084
                                                             bool must_exactly_equal)
1085
    : LogicError(ErrorCodes::InvalidSchemaVersion,
1086
                 util::format(must_exactly_equal ? "Provided schema version %1 does not equal last set version %2."
1087
                                                 : "Provided schema version %1 is less than last set version %2.",
1088
                              new_version, old_version))
1089
    , m_old_version(old_version)
1090
    , m_new_version(new_version)
1091
{
6✔
1092
}
6✔
1093

1094
static void append_errors(std::string& message, std::vector<ObjectSchemaValidationException> const& errors)
1095
{
314✔
1096
    for (auto const& error : errors) {
378✔
1097
        message += "\n- ";
378✔
1098
        message += error.m_message;
378✔
1099
    }
378✔
1100
}
314✔
1101

1102
static void append_line(std::string& message, std::string_view line)
1103
{
48✔
1104
    message += "\n";
48✔
1105
    message += line;
48✔
1106
}
48✔
1107

1108
SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors)
1109
    : LogicError(ErrorCodes::SchemaValidationFailed, [&] {
152✔
1110
        std::string message = "Schema validation failed due to the following errors:";
152✔
1111
        append_errors(message, errors);
152✔
1112
        return message;
152✔
1113
    }())
152✔
1114
{
152✔
1115
}
152✔
1116

1117
SchemaMismatchException::SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors)
1118
    : LogicError(ErrorCodes::SchemaMismatch, [&] {
84✔
1119
        std::string message = "Migration is required due to the following errors:";
84✔
1120
        append_errors(message, errors);
84✔
1121
        return message;
84✔
1122
    }())
84✔
1123
{
84✔
1124
}
84✔
1125

1126
InvalidReadOnlySchemaChangeException::InvalidReadOnlySchemaChangeException(
1127
    std::vector<ObjectSchemaValidationException> const& errors)
1128
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
30✔
1129
        std::string message = "The following changes cannot be made in read-only schema mode:";
30✔
1130
        append_errors(message, errors);
30✔
1131
        return message;
30✔
1132
    }())
30✔
1133
{
30✔
1134
}
30✔
1135

1136
InvalidAdditiveSchemaChangeException::InvalidAdditiveSchemaChangeException(
1137
    std::vector<ObjectSchemaValidationException> const& errors)
1138
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
38✔
1139
        std::string message = "The following changes cannot be made in additive-only schema mode:";
38✔
1140
        append_errors(message, errors);
38✔
1141
        append_line(message, c_development_mode_msg);
38✔
1142
        return message;
38✔
1143
    }())
38✔
1144
{
38✔
1145
}
38✔
1146

1147
InvalidExternalSchemaChangeException::InvalidExternalSchemaChangeException(
1148
    std::vector<ObjectSchemaValidationException> const& errors)
1149
    : LogicError(ErrorCodes::InvalidSchemaChange, [&] {
10✔
1150
        std::string message = "Unsupported schema changes were made by another client or process:";
10✔
1151
        append_errors(message, errors);
10✔
1152
        append_line(message, c_development_mode_msg);
10✔
1153
        return message;
10✔
1154
    }())
10✔
1155
{
10✔
1156
}
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