// set references to tables on targetSchema and create/update any missing or out-of-date tables // if update existing is true, updates existing tables, otherwise validates existing tables // NOTE: must be called from within write transaction std::vector<std::pair<std::string, Property>> ObjectStore::create_tables(Group *group, Schema &target_schema, bool update_existing) { // properties to delete std::vector<std::pair<std::string,Property>> to_delete; // first pass to create missing tables std::vector<ObjectSchema *> to_update; for (auto& object_schema : target_schema) { bool created = false; ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); // we will modify tables for any new objectSchema (table was created) or for all if update_existing is true if (update_existing || created) { to_update.push_back(&object_schema); } } // second pass adds/removes columns for out of date tables for (auto& target_object_schema : to_update) { TableRef table = table_for_object_type(group, target_object_schema->name); ObjectSchema current_schema(group, target_object_schema->name); std::vector<Property> &target_props = target_object_schema->persisted_properties; // handle columns changing from required to optional for (auto& current_prop : current_schema.persisted_properties) { auto target_prop = target_object_schema->property_for_name(current_prop.name); if (!target_prop || !property_can_be_migrated_to_nullable(current_prop, *target_prop)) continue; target_prop->table_column = current_prop.table_column; current_prop.table_column = current_prop.table_column + 1; table->insert_column(target_prop->table_column, DataType(target_prop->type), target_prop->name, target_prop->is_nullable); copy_property_values(current_prop, *target_prop, *table); table->remove_column(current_prop.table_column); current_prop.table_column = target_prop->table_column; } bool inserted_placeholder_column = false; // remove extra columns size_t deleted = 0; for (auto& current_prop : current_schema.persisted_properties) { current_prop.table_column -= deleted; auto target_prop = target_object_schema->property_for_name(current_prop.name); // mark object name & property pairs for deletion if (!target_prop) { to_delete.push_back(std::make_pair(current_schema.name, current_prop)); } else if ((property_has_changed(current_prop, *target_prop) && !property_can_be_migrated_to_nullable(current_prop, *target_prop))) { if (deleted == current_schema.persisted_properties.size() - 1) { // We're about to remove the last column from the table. Insert a placeholder column to preserve // the number of rows in the table for the addition of new columns below. table->add_column(type_Bool, "placeholder"); inserted_placeholder_column = true; } table->remove_column(current_prop.table_column); ++deleted; current_prop.table_column = npos; } } // add missing columns for (auto& target_prop : target_props) { auto current_prop = current_schema.property_for_name(target_prop.name); // add any new properties (no old column or old column was removed due to not matching) if (!current_prop || current_prop->table_column == npos) { switch (target_prop.type) { // for objects and arrays, we have to specify target table case PropertyType::Object: case PropertyType::Array: { TableRef link_table = ObjectStore::table_for_object_type(group, target_prop.object_type); REALM_ASSERT(link_table); target_prop.table_column = table->add_column_link(DataType(target_prop.type), target_prop.name, *link_table); break; } default: target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name, target_prop.is_nullable); break; } } else { target_prop.table_column = current_prop->table_column; } } if (inserted_placeholder_column) { // We inserted a placeholder due to removing all columns from the table. Remove it, and update the indices // of any columns that we inserted after it. table->remove_column(0); for (auto& target_prop : target_props) { target_prop.table_column--; } } // update table metadata if (target_object_schema->primary_key.length()) { // if there is a primary key set, check if it is the same as the old key if (current_schema.primary_key != target_object_schema->primary_key) { set_primary_key_for_object(group, target_object_schema->name, target_object_schema->primary_key); } } else if (current_schema.primary_key.length()) { // there is no primary key, so if there was one nil out set_primary_key_for_object(group, target_object_schema->name, ""); } } return to_delete; }
// set references to tables on targetSchema and create/update any missing or out-of-date tables // if update existing is true, updates existing tables, otherwise validates existing tables // NOTE: must be called from within write transaction bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema, bool update_existing) { bool changed = false; // first pass to create missing tables vector<ObjectSchema *> to_update; for (auto& object_schema : target_schema) { bool created = false; ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); // we will modify tables for any new objectSchema (table was created) or for all if update_existing is true if (update_existing || created) { to_update.push_back(&object_schema); changed = true; } } // second pass adds/removes columns for out of date tables for (auto& target_object_schema : to_update) { TableRef table = table_for_object_type(group, target_object_schema->name); ObjectSchema current_schema(group, target_object_schema->name); vector<Property> &target_props = target_object_schema->properties; // add missing columns for (auto& target_prop : target_props) { auto current_prop = current_schema.property_for_name(target_prop.name); // add any new properties (new name or different type) if (!current_prop || property_has_changed(*current_prop, target_prop)) { switch (target_prop.type) { // for objects and arrays, we have to specify target table case PropertyTypeObject: case PropertyTypeArray: { TableRef link_table = ObjectStore::table_for_object_type(group, target_prop.object_type); target_prop.table_column = table->add_column_link(DataType(target_prop.type), target_prop.name, *link_table); break; } default: target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name); break; } changed = true; } } // remove extra columns sort(begin(current_schema.properties), end(current_schema.properties), [](Property &i, Property &j) { return j.table_column < i.table_column; }); for (auto& current_prop : current_schema.properties) { auto target_prop = target_object_schema->property_for_name(current_prop.name); if (!target_prop || property_has_changed(current_prop, *target_prop)) { table->remove_column(current_prop.table_column); changed = true; } } // update table metadata if (target_object_schema->primary_key.length()) { // if there is a primary key set, check if it is the same as the old key if (current_schema.primary_key != target_object_schema->primary_key) { set_primary_key_for_object(group, target_object_schema->name, target_object_schema->primary_key); changed = true; } } else if (current_schema.primary_key.length()) { // there is no primary key, so if there was one nil out set_primary_key_for_object(group, target_object_schema->name, ""); changed = true; } } return changed; }