1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2015 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #include "object_store.hpp"
21 #include "feature_checks.hpp"
22 #include "object_schema.hpp"
24 #include "shared_realm.hpp"
26 #include <realm/descriptor.hpp>
27 #include <realm/group.hpp>
28 #include <realm/table.hpp>
29 #include <realm/table_view.hpp>
30 #include <realm/util/assert.hpp>
33 #include <realm/sync/object.hpp>
34 #endif // REALM_ENABLE_SYNC
38 using namespace realm;
40 constexpr uint64_t ObjectStore::NotVersioned;
43 const char * const c_metadataTableName = "metadata";
44 const char * const c_versionColumnName = "version";
45 const size_t c_versionColumnIndex = 0;
47 const char * const c_primaryKeyTableName = "pk";
48 const char * const c_primaryKeyObjectClassColumnName = "pk_table";
49 const size_t c_primaryKeyObjectClassColumnIndex = 0;
50 const char * const c_primaryKeyPropertyNameColumnName = "pk_property";
51 const size_t c_primaryKeyPropertyNameColumnIndex = 1;
53 const size_t c_zeroRowIndex = 0;
55 const char c_object_table_prefix[] = "class_";
57 void create_metadata_tables(Group& group) {
58 // The tables 'pk' and 'metadata' are treated specially by Sync. The 'pk' table
59 // is populated by `sync::create_table` and friends, while the 'metadata' table
61 TableRef pk_table = group.get_or_add_table(c_primaryKeyTableName);
62 TableRef metadata_table = group.get_or_add_table(c_metadataTableName);
63 const size_t empty_table_size = 0;
65 if (metadata_table->get_column_count() == empty_table_size) {
66 metadata_table->insert_column(c_versionColumnIndex, type_Int, c_versionColumnName);
67 metadata_table->add_empty_row();
68 // set initial version
69 metadata_table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned);
72 if (pk_table->get_column_count() == 0) {
73 pk_table->insert_column(c_primaryKeyObjectClassColumnIndex, type_String, c_primaryKeyObjectClassColumnName);
74 pk_table->insert_column(c_primaryKeyPropertyNameColumnIndex, type_String, c_primaryKeyPropertyNameColumnName);
76 pk_table->add_search_index(c_primaryKeyObjectClassColumnIndex);
79 void set_schema_version(Group& group, uint64_t version) {
80 TableRef table = group.get_table(c_metadataTableName);
81 table->set_int(c_versionColumnIndex, c_zeroRowIndex, version);
84 template<typename Group>
85 auto table_for_object_schema(Group& group, ObjectSchema const& object_schema)
87 return ObjectStore::table_for_object_type(group, object_schema.name);
90 DataType to_core_type(PropertyType type)
92 REALM_ASSERT(type != PropertyType::Object); // Link columns have to be handled differently
93 REALM_ASSERT(type != PropertyType::Any); // Mixed columns can't be created
94 switch (type & ~PropertyType::Flags) {
95 case PropertyType::Int: return type_Int;
96 case PropertyType::Bool: return type_Bool;
97 case PropertyType::Float: return type_Float;
98 case PropertyType::Double: return type_Double;
99 case PropertyType::String: return type_String;
100 case PropertyType::Date: return type_Timestamp;
101 case PropertyType::Data: return type_Binary;
102 default: REALM_COMPILER_HINT_UNREACHABLE();
106 void insert_column(Group& group, Table& table, Property const& property, size_t col_ndx)
108 // Cannot directly insert a LinkingObjects column (a computed property).
109 // LinkingObjects must be an artifact of an existing link column.
110 REALM_ASSERT(property.type != PropertyType::LinkingObjects);
112 if (property.type == PropertyType::Object) {
113 auto target_name = ObjectStore::table_name_for_object_type(property.object_type);
114 TableRef link_table = group.get_table(target_name);
115 REALM_ASSERT(link_table);
116 table.insert_column_link(col_ndx, is_array(property.type) ? type_LinkList : type_Link,
117 property.name, *link_table);
119 else if (is_array(property.type)) {
121 table.insert_column(col_ndx, type_Table, property.name, &desc);
122 desc->add_column(to_core_type(property.type & ~PropertyType::Flags), ObjectStore::ArrayColumnName,
123 nullptr, is_nullable(property.type));
126 table.insert_column(col_ndx, to_core_type(property.type), property.name, is_nullable(property.type));
127 if (property.requires_index())
128 table.add_search_index(col_ndx);
132 void add_column(Group& group, Table& table, Property const& property)
134 insert_column(group, table, property, table.get_column_count());
137 void replace_column(Group& group, Table& table, Property const& old_property, Property const& new_property)
139 insert_column(group, table, new_property, old_property.table_column);
140 table.remove_column(old_property.table_column + 1);
143 TableRef create_table(Group& group, ObjectSchema const& object_schema)
145 auto name = ObjectStore::table_name_for_object_type(object_schema.name);
148 #if REALM_ENABLE_SYNC
149 if (auto* pk_property = object_schema.primary_key_property()) {
150 table = sync::create_table_with_primary_key(group, name, to_core_type(pk_property->type),
151 pk_property->name, is_nullable(pk_property->type));
154 table = sync::create_table(group, name);
157 table = group.get_or_add_table(name);
158 #endif // REALM_ENABLE_SYNC
160 ObjectStore::set_primary_key_for_object(group, object_schema.name, object_schema.primary_key);
165 void add_initial_columns(Group& group, ObjectSchema const& object_schema)
167 auto name = ObjectStore::table_name_for_object_type(object_schema.name);
168 TableRef table = group.get_table(name);
170 for (auto const& prop : object_schema.persisted_properties) {
171 #if REALM_ENABLE_SYNC
172 // The sync::create_table* functions create the PK column for us.
175 #endif // REALM_ENABLE_SYNC
176 add_column(group, *table, prop);
180 void copy_property_values(Property const& prop, Table& table)
182 auto copy_property_values = [&](auto getter, auto setter) {
183 for (size_t i = 0, count = table.size(); i < count; i++) {
184 bool is_default = false;
185 (table.*setter)(prop.table_column, i, (table.*getter)(prop.table_column + 1, i),
190 switch (prop.type & ~PropertyType::Flags) {
191 case PropertyType::Int:
192 copy_property_values(&Table::get_int, &Table::set_int);
194 case PropertyType::Bool:
195 copy_property_values(&Table::get_bool, &Table::set_bool);
197 case PropertyType::Float:
198 copy_property_values(&Table::get_float, &Table::set_float);
200 case PropertyType::Double:
201 copy_property_values(&Table::get_double, &Table::set_double);
203 case PropertyType::String:
204 copy_property_values(&Table::get_string, &Table::set_string);
206 case PropertyType::Data:
207 copy_property_values(&Table::get_binary, &Table::set_binary);
209 case PropertyType::Date:
210 copy_property_values(&Table::get_timestamp, &Table::set_timestamp);
217 void make_property_optional(Group& group, Table& table, Property property)
219 property.type |= PropertyType::Nullable;
220 insert_column(group, table, property, property.table_column);
221 copy_property_values(property, table);
222 table.remove_column(property.table_column + 1);
225 void make_property_required(Group& group, Table& table, Property property)
227 property.type &= ~PropertyType::Nullable;
228 insert_column(group, table, property, property.table_column);
229 table.remove_column(property.table_column + 1);
232 void validate_primary_column_uniqueness(Group const& group, StringData object_type, StringData primary_property)
234 auto table = ObjectStore::table_for_object_type(group, object_type);
235 if (table->get_distinct_view(table->get_column_index(primary_property)).size() != table->size()) {
236 throw DuplicatePrimaryKeyValueException(object_type, primary_property);
240 void validate_primary_column_uniqueness(Group const& group)
242 auto pk_table = group.get_table(c_primaryKeyTableName);
243 for (size_t i = 0, count = pk_table->size(); i < count; ++i) {
244 validate_primary_column_uniqueness(group,
245 pk_table->get_string(c_primaryKeyObjectClassColumnIndex, i),
246 pk_table->get_string(c_primaryKeyPropertyNameColumnIndex, i));
249 } // anonymous namespace
251 void ObjectStore::set_schema_version(Group& group, uint64_t version) {
252 ::create_metadata_tables(group);
253 ::set_schema_version(group, version);
256 uint64_t ObjectStore::get_schema_version(Group const& group) {
257 ConstTableRef table = group.get_table(c_metadataTableName);
258 if (!table || table->get_column_count() == 0) {
259 return ObjectStore::NotVersioned;
261 return table->get_int(c_versionColumnIndex, c_zeroRowIndex);
264 StringData ObjectStore::get_primary_key_for_object(Group const& group, StringData object_type) {
265 ConstTableRef table = group.get_table(c_primaryKeyTableName);
269 size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
270 if (row == not_found) {
273 return table->get_string(c_primaryKeyPropertyNameColumnIndex, row);
276 void ObjectStore::set_primary_key_for_object(Group& group, StringData object_type, StringData primary_key) {
277 TableRef table = group.get_table(c_primaryKeyTableName);
279 size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
281 #if REALM_ENABLE_SYNC
282 // sync::create_table* functions should have already updated the pk table.
283 if (sync::has_object_ids(group)) {
284 if (primary_key.size() == 0)
285 REALM_ASSERT(row == not_found);
287 REALM_ASSERT(row != not_found);
288 REALM_ASSERT(table->get_string(c_primaryKeyPropertyNameColumnIndex, row) == primary_key);
292 #endif // REALM_ENABLE_SYNC
294 if (row == not_found && primary_key.size()) {
295 row = table->add_empty_row();
296 table->set_string_unique(c_primaryKeyObjectClassColumnIndex, row, object_type);
297 table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key);
300 // set if changing, or remove if setting to nil
301 if (primary_key.size() == 0) {
302 if (row != not_found) {
303 table->move_last_over(row);
307 table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key);
311 StringData ObjectStore::object_type_for_table_name(StringData table_name) {
312 if (table_name.begins_with(c_object_table_prefix)) {
313 return table_name.substr(sizeof(c_object_table_prefix) - 1);
318 std::string ObjectStore::table_name_for_object_type(StringData object_type) {
319 return std::string(c_object_table_prefix) + std::string(object_type);
322 TableRef ObjectStore::table_for_object_type(Group& group, StringData object_type) {
323 auto name = table_name_for_object_type(object_type);
324 return group.get_table(name);
327 ConstTableRef ObjectStore::table_for_object_type(Group const& group, StringData object_type) {
328 auto name = table_name_for_object_type(object_type);
329 return group.get_table(name);
333 struct SchemaDifferenceExplainer {
334 std::vector<ObjectSchemaValidationException> errors;
336 void operator()(schema_change::AddTable op)
338 errors.emplace_back("Class '%1' has been added.", op.object->name);
341 void operator()(schema_change::AddInitialProperties)
343 // Nothing. Always preceded by AddTable.
346 void operator()(schema_change::AddProperty op)
348 errors.emplace_back("Property '%1.%2' has been added.", op.object->name, op.property->name);
351 void operator()(schema_change::RemoveProperty op)
353 errors.emplace_back("Property '%1.%2' has been removed.", op.object->name, op.property->name);
356 void operator()(schema_change::ChangePropertyType op)
358 errors.emplace_back("Property '%1.%2' has been changed from '%3' to '%4'.",
359 op.object->name, op.new_property->name,
360 op.old_property->type_string(),
361 op.new_property->type_string());
364 void operator()(schema_change::MakePropertyNullable op)
366 errors.emplace_back("Property '%1.%2' has been made optional.", op.object->name, op.property->name);
369 void operator()(schema_change::MakePropertyRequired op)
371 errors.emplace_back("Property '%1.%2' has been made required.", op.object->name, op.property->name);
374 void operator()(schema_change::ChangePrimaryKey op)
376 if (op.property && !op.object->primary_key.empty()) {
377 errors.emplace_back("Primary Key for class '%1' has changed from '%2' to '%3'.",
378 op.object->name, op.object->primary_key, op.property->name);
380 else if (op.property) {
381 errors.emplace_back("Primary Key for class '%1' has been added.", op.object->name);
384 errors.emplace_back("Primary Key for class '%1' has been removed.", op.object->name);
388 void operator()(schema_change::AddIndex op)
390 errors.emplace_back("Property '%1.%2' has been made indexed.", op.object->name, op.property->name);
393 void operator()(schema_change::RemoveIndex op)
395 errors.emplace_back("Property '%1.%2' has been made unindexed.", op.object->name, op.property->name);
401 TableHelper(Group& g) : m_group(g) { }
403 Table& operator()(const ObjectSchema* object_schema)
405 if (object_schema != m_current_object_schema) {
406 m_current_table = table_for_object_schema(m_group, *object_schema);
407 m_current_object_schema = object_schema;
409 REALM_ASSERT(m_current_table);
410 return *m_current_table;
415 const ObjectSchema* m_current_object_schema = nullptr;
416 TableRef m_current_table;
419 template<typename ErrorType, typename Verifier>
420 void verify_no_errors(Verifier&& verifier, std::vector<SchemaChange> const& changes)
422 for (auto& change : changes) {
423 change.visit(verifier);
426 if (!verifier.errors.empty()) {
427 throw ErrorType(verifier.errors);
430 } // anonymous namespace
432 bool ObjectStore::needs_migration(std::vector<SchemaChange> const& changes)
434 using namespace schema_change;
436 bool operator()(AddIndex) { return false; }
437 bool operator()(AddInitialProperties) { return false; }
438 bool operator()(AddProperty) { return true; }
439 bool operator()(AddTable) { return false; }
440 bool operator()(ChangePrimaryKey) { return true; }
441 bool operator()(ChangePropertyType) { return true; }
442 bool operator()(MakePropertyNullable) { return true; }
443 bool operator()(MakePropertyRequired) { return true; }
444 bool operator()(RemoveIndex) { return false; }
445 bool operator()(RemoveProperty) { return true; }
448 return std::any_of(begin(changes), end(changes),
449 [](auto&& change) { return change.visit(Visitor()); });
452 void ObjectStore::verify_no_changes_required(std::vector<SchemaChange> const& changes)
454 verify_no_errors<SchemaMismatchException>(SchemaDifferenceExplainer(), changes);
457 void ObjectStore::verify_no_migration_required(std::vector<SchemaChange> const& changes)
459 using namespace schema_change;
460 struct Verifier : SchemaDifferenceExplainer {
461 using SchemaDifferenceExplainer::operator();
463 // Adding a table or adding/removing indexes can be done automatically.
464 // All other changes require migrations.
465 void operator()(AddTable) { }
466 void operator()(AddInitialProperties) { }
467 void operator()(AddIndex) { }
468 void operator()(RemoveIndex) { }
470 verify_no_errors<SchemaMismatchException>(verifier, changes);
473 bool ObjectStore::verify_valid_additive_changes(std::vector<SchemaChange> const& changes, bool update_indexes)
475 using namespace schema_change;
476 struct Verifier : SchemaDifferenceExplainer {
477 using SchemaDifferenceExplainer::operator();
479 bool index_changes = false;
480 bool other_changes = false;
482 // Additive mode allows adding things, extra columns, and adding/removing indexes
483 void operator()(AddTable) { other_changes = true; }
484 void operator()(AddInitialProperties) { other_changes = true; }
485 void operator()(AddProperty) { other_changes = true; }
486 void operator()(RemoveProperty) { }
487 void operator()(AddIndex) { index_changes = true; }
488 void operator()(RemoveIndex) { index_changes = true; }
490 verify_no_errors<InvalidSchemaChangeException>(verifier, changes);
491 return verifier.other_changes || (verifier.index_changes && update_indexes);
494 void ObjectStore::verify_valid_external_changes(std::vector<SchemaChange> const& changes)
496 using namespace schema_change;
497 struct Verifier : SchemaDifferenceExplainer {
498 using SchemaDifferenceExplainer::operator();
500 // Adding new things is fine
501 void operator()(AddTable) { }
502 void operator()(AddInitialProperties) { }
503 void operator()(AddProperty) { }
504 void operator()(AddIndex) { }
505 void operator()(RemoveIndex) { }
507 verify_no_errors<InvalidSchemaChangeException>(verifier, changes);
510 void ObjectStore::verify_compatible_for_immutable_and_readonly(std::vector<SchemaChange> const& changes)
512 using namespace schema_change;
513 struct Verifier : SchemaDifferenceExplainer {
514 using SchemaDifferenceExplainer::operator();
516 void operator()(AddTable) { }
517 void operator()(AddInitialProperties) { }
518 void operator()(RemoveProperty) { }
519 void operator()(AddIndex) { }
520 void operator()(RemoveIndex) { }
522 verify_no_errors<InvalidSchemaChangeException>(verifier, changes);
525 static void apply_non_migration_changes(Group& group, std::vector<SchemaChange> const& changes)
527 using namespace schema_change;
528 struct Applier : SchemaDifferenceExplainer {
529 Applier(Group& group) : group{group}, table{group} { }
533 // Produce an exception listing the unsupported schema changes for
534 // everything but the explicitly supported ones
535 using SchemaDifferenceExplainer::operator();
537 void operator()(AddTable op) { create_table(group, *op.object); }
538 void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); }
539 void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); }
540 void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
542 verify_no_errors<SchemaMismatchException>(applier, changes);
545 static void create_initial_tables(Group& group, std::vector<SchemaChange> const& changes)
547 using namespace schema_change;
549 Applier(Group& group) : group{group}, table{group} { }
553 void operator()(AddTable op) { create_table(group, *op.object); }
554 void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); }
556 // Note that in normal operation none of these will be hit, as if we're
557 // creating the initial tables there shouldn't be anything to update.
558 // Implementing these makes us better able to handle weird
559 // not-quite-correct files produced by other things and has no obvious
561 void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); }
562 void operator()(RemoveProperty op) { table(op.object).remove_column(op.property->table_column); }
563 void operator()(MakePropertyNullable op) { make_property_optional(group, table(op.object), *op.property); }
564 void operator()(MakePropertyRequired op) { make_property_required(group, table(op.object), *op.property); }
565 void operator()(ChangePrimaryKey op) { ObjectStore::set_primary_key_for_object(group, op.object->name, op.property ? StringData{op.property->name} : ""); }
566 void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); }
567 void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
569 void operator()(ChangePropertyType op)
571 replace_column(group, table(op.object), *op.old_property, *op.new_property);
575 for (auto& change : changes) {
576 change.visit(applier);
580 void ObjectStore::apply_additive_changes(Group& group, std::vector<SchemaChange> const& changes, bool update_indexes)
582 using namespace schema_change;
584 Applier(Group& group, bool update_indexes)
585 : group{group}, table{group}, update_indexes{update_indexes} { }
590 void operator()(AddTable op) { create_table(group, *op.object); }
591 void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); }
592 void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); }
593 void operator()(AddIndex op) { if (update_indexes) table(op.object).add_search_index(op.property->table_column); }
594 void operator()(RemoveIndex op) { if (update_indexes) table(op.object).remove_search_index(op.property->table_column); }
595 void operator()(RemoveProperty) { }
597 // No need for errors for these, as we've already verified that they aren't present
598 void operator()(ChangePrimaryKey) { }
599 void operator()(ChangePropertyType) { }
600 void operator()(MakePropertyNullable) { }
601 void operator()(MakePropertyRequired) { }
602 } applier{group, update_indexes};
604 for (auto& change : changes) {
605 change.visit(applier);
609 static void apply_pre_migration_changes(Group& group, std::vector<SchemaChange> const& changes)
611 using namespace schema_change;
613 Applier(Group& group) : group{group}, table{group} { }
617 void operator()(AddTable op) { create_table(group, *op.object); }
618 void operator()(AddInitialProperties op) { add_initial_columns(group, *op.object); }
619 void operator()(AddProperty op) { add_column(group, table(op.object), *op.property); }
620 void operator()(RemoveProperty) { /* delayed until after the migration */ }
621 void operator()(ChangePropertyType op) { replace_column(group, table(op.object), *op.old_property, *op.new_property); }
622 void operator()(MakePropertyNullable op) { make_property_optional(group, table(op.object), *op.property); }
623 void operator()(MakePropertyRequired op) { make_property_required(group, table(op.object), *op.property); }
624 void operator()(ChangePrimaryKey op) { ObjectStore::set_primary_key_for_object(group, op.object->name.c_str(), op.property ? op.property->name.c_str() : ""); }
625 void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); }
626 void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
629 for (auto& change : changes) {
630 change.visit(applier);
634 enum class DidRereadSchema { Yes, No };
636 static void apply_post_migration_changes(Group& group, std::vector<SchemaChange> const& changes, Schema const& initial_schema,
637 DidRereadSchema did_reread_schema)
639 using namespace schema_change;
641 Applier(Group& group, Schema const& initial_schema, DidRereadSchema did_reread_schema)
642 : group{group}, initial_schema(initial_schema), table(group)
643 , did_reread_schema(did_reread_schema == DidRereadSchema::Yes)
646 Schema const& initial_schema;
648 bool did_reread_schema;
650 void operator()(RemoveProperty op)
652 if (!initial_schema.empty() && !initial_schema.find(op.object->name)->property_for_name(op.property->name))
653 throw std::logic_error(util::format("Renamed property '%1.%2' does not exist.", op.object->name, op.property->name));
654 auto table = table_for_object_schema(group, *op.object);
655 table->remove_column(op.property->table_column);
658 void operator()(ChangePrimaryKey op)
661 validate_primary_column_uniqueness(group, op.object->name, op.property->name);
665 void operator()(AddTable op) { create_table(group, *op.object); }
667 void operator()(AddInitialProperties op) {
668 if (did_reread_schema)
669 add_initial_columns(group, *op.object);
671 // If we didn't re-read the schema then AddInitialProperties was already taken care of
672 // during apply_pre_migration_changes.
676 void operator()(AddIndex op) { table(op.object).add_search_index(op.property->table_column); }
677 void operator()(RemoveIndex op) { table(op.object).remove_search_index(op.property->table_column); }
679 void operator()(ChangePropertyType) { }
680 void operator()(MakePropertyNullable) { }
681 void operator()(MakePropertyRequired) { }
682 void operator()(AddProperty) { }
683 } applier{group, initial_schema, did_reread_schema};
685 for (auto& change : changes) {
686 change.visit(applier);
690 void ObjectStore::apply_schema_changes(Group& group, uint64_t schema_version,
691 Schema& target_schema, uint64_t target_schema_version,
692 SchemaMode mode, std::vector<SchemaChange> const& changes,
693 std::function<void()> migration_function)
695 create_metadata_tables(group);
697 if (mode == SchemaMode::Additive) {
698 bool target_schema_is_newer = (schema_version < target_schema_version
699 || schema_version == ObjectStore::NotVersioned);
701 // With sync v2.x, indexes are no longer synced, so there's no reason to avoid creating them.
702 bool update_indexes = true;
703 apply_additive_changes(group, changes, update_indexes);
705 if (target_schema_is_newer)
706 set_schema_version(group, target_schema_version);
708 set_schema_columns(group, target_schema);
712 if (schema_version == ObjectStore::NotVersioned) {
713 create_initial_tables(group, changes);
714 set_schema_version(group, target_schema_version);
715 set_schema_columns(group, target_schema);
719 if (mode == SchemaMode::Manual) {
720 set_schema_columns(group, target_schema);
721 if (migration_function) {
722 migration_function();
725 verify_no_changes_required(schema_from_group(group).compare(target_schema));
726 validate_primary_column_uniqueness(group);
727 set_schema_columns(group, target_schema);
728 set_schema_version(group, target_schema_version);
732 if (schema_version == target_schema_version) {
733 apply_non_migration_changes(group, changes);
734 set_schema_columns(group, target_schema);
738 auto old_schema = schema_from_group(group);
739 apply_pre_migration_changes(group, changes);
740 if (migration_function) {
741 set_schema_columns(group, target_schema);
742 migration_function();
744 // Migration function may have changed the schema, so we need to re-read it
745 auto schema = schema_from_group(group);
746 apply_post_migration_changes(group, schema.compare(target_schema), old_schema, DidRereadSchema::Yes);
747 validate_primary_column_uniqueness(group);
750 apply_post_migration_changes(group, changes, {}, DidRereadSchema::No);
753 set_schema_version(group, target_schema_version);
754 set_schema_columns(group, target_schema);
757 Schema ObjectStore::schema_from_group(Group const& group) {
758 std::vector<ObjectSchema> schema;
759 schema.reserve(group.size());
760 for (size_t i = 0; i < group.size(); i++) {
761 auto object_type = object_type_for_table_name(group.get_table_name(i));
762 if (object_type.size()) {
763 schema.emplace_back(group, object_type, i);
769 util::Optional<Property> ObjectStore::property_for_column_index(ConstTableRef& table, size_t column_index)
771 StringData column_name = table->get_column_name(column_index);
773 #if REALM_ENABLE_SYNC
774 // The object ID column is an implementation detail, and is omitted from the schema.
775 // FIXME: Consider filtering out all column names starting with `!`.
776 if (column_name == sync::object_id_column_name)
780 if (table->get_column_type(column_index) == type_Table) {
781 auto subdesc = table->get_subdescriptor(column_index);
782 if (subdesc->get_column_count() != 1 || subdesc->get_column_name(0) != ObjectStore::ArrayColumnName)
787 property.name = column_name;
788 property.type = ObjectSchema::from_core_type(*table->get_descriptor(), column_index);
789 property.is_indexed = table->has_search_index(column_index);
790 property.table_column = column_index;
792 if (property.type == PropertyType::Object) {
793 // set link type for objects and arrays
794 ConstTableRef linkTable = table->get_link_target(column_index);
795 property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data());
800 void ObjectStore::set_schema_columns(Group const& group, Schema& schema)
802 for (auto& object_schema : schema) {
803 auto table = table_for_object_schema(group, object_schema);
807 for (auto& property : object_schema.persisted_properties) {
808 property.table_column = table->get_column_index(property.name);
813 void ObjectStore::delete_data_for_object(Group& group, StringData object_type) {
814 if (TableRef table = table_for_object_type(group, object_type)) {
815 group.remove_table(table->get_index_in_group());
816 ObjectStore::set_primary_key_for_object(group, object_type, "");
820 bool ObjectStore::is_empty(Group const& group) {
821 for (size_t i = 0; i < group.size(); i++) {
822 ConstTableRef table = group.get_table(i);
823 std::string object_type = object_type_for_table_name(table->get_name());
824 if (!object_type.length()) {
827 if (!table->is_empty()) {
834 void ObjectStore::rename_property(Group& group, Schema& target_schema, StringData object_type, StringData old_name, StringData new_name)
836 TableRef table = table_for_object_type(group, object_type);
838 throw std::logic_error(util::format("Cannot rename properties for type '%1' because it does not exist.", object_type));
841 auto target_object_schema = target_schema.find(object_type);
842 if (target_object_schema == target_schema.end()) {
843 throw std::logic_error(util::format("Cannot rename properties for type '%1' because it has been removed from the Realm.", object_type));
846 if (target_object_schema->property_for_name(old_name)) {
847 throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because the source property still exists.",
848 object_type, old_name, new_name));
851 ObjectSchema table_object_schema(group, object_type);
852 Property *old_property = table_object_schema.property_for_name(old_name);
854 throw std::logic_error(util::format("Cannot rename property '%1.%2' because it does not exist.", object_type, old_name));
857 Property *new_property = table_object_schema.property_for_name(new_name);
859 // New property doesn't exist in the table, which means we're probably
860 // renaming to an intermediate property in a multi-version migration.
861 // This is safe because the migration will fail schema validation unless
862 // this property is renamed again to a valid name before the end.
863 table->rename_column(old_property->table_column, new_name);
867 if (old_property->type != new_property->type || old_property->object_type != new_property->object_type) {
868 throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because it would change from type '%4' to '%5'.",
869 object_type, old_name, new_name, old_property->type_string(), new_property->type_string()));
872 if (is_nullable(old_property->type) && !is_nullable(new_property->type)) {
873 throw std::logic_error(util::format("Cannot rename property '%1.%2' to '%3' because it would change from optional to required.",
874 object_type, old_name, new_name));
877 size_t column_to_remove = new_property->table_column;
878 table->rename_column(old_property->table_column, new_name);
879 table->remove_column(column_to_remove);
881 // update table_column for each property since it may have shifted
882 for (auto& current_prop : target_object_schema->persisted_properties) {
883 if (current_prop.table_column == column_to_remove)
884 current_prop.table_column = old_property->table_column;
885 else if (current_prop.table_column > column_to_remove)
886 --current_prop.table_column;
889 // update nullability for column
890 if (is_nullable(new_property->type) && !is_nullable(old_property->type)) {
891 auto prop = *new_property;
892 prop.table_column = old_property->table_column;
893 make_property_optional(group, *table, prop);
897 InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version)
898 : logic_error(util::format("Provided schema version %1 is less than last set version %2.", new_version, old_version))
899 , m_old_version(old_version), m_new_version(new_version)
903 DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string object_type, std::string property)
904 : logic_error(util::format("Primary key property '%1.%2' has duplicate values after migration.", object_type, property))
905 , m_object_type(object_type), m_property(property)
909 SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors)
910 : std::logic_error([&] {
911 std::string message = "Schema validation failed due to the following errors:";
912 for (auto const& error : errors) {
913 message += std::string("\n- ") + error.what();
920 SchemaMismatchException::SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors)
921 : std::logic_error([&] {
922 std::string message = "Migration is required due to the following errors:";
923 for (auto const& error : errors) {
924 message += std::string("\n- ") + error.what();
931 InvalidSchemaChangeException::InvalidSchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors)
932 : std::logic_error([&] {
933 std::string message = "The following changes cannot be made in additive-only schema mode:";
934 for (auto const& error : errors) {
935 message += std::string("\n- ") + error.what();