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 ////////////////////////////////////////////////////////////////////////////
21 #include "object_schema.hpp"
22 #include "object_store.hpp"
23 #include "object_schema.hpp"
24 #include "property.hpp"
28 using namespace realm;
31 bool operator==(Schema const& a, Schema const& b)
33 return static_cast<Schema::base const&>(a) == static_cast<Schema::base const&>(b);
37 Schema::Schema() = default;
38 Schema::~Schema() = default;
39 Schema::Schema(Schema const&) = default;
40 Schema::Schema(Schema &&) = default;
41 Schema& Schema::operator=(Schema const&) = default;
42 Schema& Schema::operator=(Schema&&) = default;
44 Schema::Schema(std::initializer_list<ObjectSchema> types) : Schema(base(types)) { }
46 Schema::Schema(base types) : base(std::move(types))
48 std::sort(begin(), end(), [](ObjectSchema const& lft, ObjectSchema const& rgt) {
49 return lft.name < rgt.name;
53 Schema::iterator Schema::find(StringData name)
55 auto it = std::lower_bound(begin(), end(), name, [](ObjectSchema const& lft, StringData rgt) {
56 return lft.name < rgt;
58 if (it != end() && it->name != name) {
64 Schema::const_iterator Schema::find(StringData name) const
66 return const_cast<Schema *>(this)->find(name);
69 Schema::iterator Schema::find(ObjectSchema const& object) noexcept
71 return find(object.name);
74 Schema::const_iterator Schema::find(ObjectSchema const& object) const noexcept
76 return const_cast<Schema *>(this)->find(object);
79 void Schema::validate() const
81 std::vector<ObjectSchemaValidationException> exceptions;
82 for (auto const& object : *this) {
83 object.validate(*this, exceptions);
86 if (exceptions.size()) {
87 throw SchemaValidationException(exceptions);
92 struct IsNotRemoveProperty {
93 bool operator()(SchemaChange sc) const { return sc.visit(*this); }
94 bool operator()(schema_change::RemoveProperty) const { return false; }
95 template<typename T> bool operator()(T) const { return true; }
97 struct GetRemovedColumn {
98 size_t operator()(SchemaChange sc) const { return sc.visit(*this); }
99 size_t operator()(schema_change::RemoveProperty p) const { return p.property->table_column; }
100 template<typename T> size_t operator()(T) const { REALM_COMPILER_HINT_UNREACHABLE(); }
104 static void compare(ObjectSchema const& existing_schema,
105 ObjectSchema const& target_schema,
106 std::vector<SchemaChange>& changes)
108 for (auto& current_prop : existing_schema.persisted_properties) {
109 auto target_prop = target_schema.property_for_name(current_prop.name);
112 changes.emplace_back(schema_change::RemoveProperty{&existing_schema, ¤t_prop});
115 if (target_schema.property_is_computed(*target_prop)) {
116 changes.emplace_back(schema_change::RemoveProperty{&existing_schema, ¤t_prop});
119 if (current_prop.type != target_prop->type ||
120 current_prop.object_type != target_prop->object_type ||
121 is_array(current_prop.type) != is_array(target_prop->type)) {
123 changes.emplace_back(schema_change::ChangePropertyType{&existing_schema, ¤t_prop, target_prop});
126 if (is_nullable(current_prop.type) != is_nullable(target_prop->type)) {
127 if (is_nullable(current_prop.type))
128 changes.emplace_back(schema_change::MakePropertyRequired{&existing_schema, ¤t_prop});
130 changes.emplace_back(schema_change::MakePropertyNullable{&existing_schema, ¤t_prop});
132 if (target_prop->requires_index()) {
133 if (!current_prop.is_indexed)
134 changes.emplace_back(schema_change::AddIndex{&existing_schema, ¤t_prop});
136 else if (current_prop.requires_index()) {
137 changes.emplace_back(schema_change::RemoveIndex{&existing_schema, ¤t_prop});
141 if (existing_schema.primary_key != target_schema.primary_key) {
142 changes.emplace_back(schema_change::ChangePrimaryKey{&existing_schema, target_schema.primary_key_property()});
145 for (auto& target_prop : target_schema.persisted_properties) {
146 if (!existing_schema.property_for_name(target_prop.name)) {
147 changes.emplace_back(schema_change::AddProperty{&existing_schema, &target_prop});
151 // Move all RemovePropertys to the end and sort in descending order of
152 // column index, as removing a column will shift all columns after that one
153 auto it = std::partition(begin(changes), end(changes), IsNotRemoveProperty{});
154 std::sort(it, end(changes),
155 [](auto a, auto b) { return GetRemovedColumn()(a) > GetRemovedColumn()(b); });
158 template<typename T, typename U, typename Func>
159 void Schema::zip_matching(T&& a, U&& b, Func&& func)
162 while (i < a.size() && j < b.size()) {
163 auto& object_schema = a[i];
164 auto& matching_schema = b[j];
165 int cmp = object_schema.name.compare(matching_schema.name);
167 func(&object_schema, &matching_schema);
172 func(&object_schema, nullptr);
176 func(nullptr, &matching_schema);
180 for (; i < a.size(); ++i)
181 func(&a[i], nullptr);
182 for (; j < b.size(); ++j)
183 func(nullptr, &b[j]);
187 std::vector<SchemaChange> Schema::compare(Schema const& target_schema) const
189 std::vector<SchemaChange> changes;
191 // Add missing tables
192 zip_matching(target_schema, *this, [&](const ObjectSchema* target, const ObjectSchema* existing) {
193 if (target && !existing) {
194 changes.emplace_back(schema_change::AddTable{target});
199 zip_matching(target_schema, *this, [&](const ObjectSchema* target, const ObjectSchema* existing) {
200 if (target && existing)
201 ::compare(*existing, *target, changes);
203 // Target is a new table -- add all properties
204 changes.emplace_back(schema_change::AddInitialProperties{target});
206 // nothing for tables in existing but not target
211 void Schema::copy_table_columns_from(realm::Schema const& other)
213 zip_matching(*this, other, [&](ObjectSchema* existing, const ObjectSchema* other) {
214 if (!existing || !other)
217 for (auto& current_prop : other->persisted_properties) {
218 auto target_prop = existing->property_for_name(current_prop.name);
220 target_prop->table_column = current_prop.table_column;
227 bool operator==(SchemaChange const& lft, SchemaChange const& rgt)
229 if (lft.m_kind != rgt.m_kind)
232 using namespace schema_change;
234 SchemaChange const& value;
236 #define REALM_SC_COMPARE(type, ...) \
237 bool operator()(type rgt) const \
239 auto cmp = [](auto&& v) { return std::tie(__VA_ARGS__); }; \
240 return cmp(value.type) == cmp(rgt); \
243 REALM_SC_COMPARE(AddIndex, v.object, v.property)
244 REALM_SC_COMPARE(AddProperty, v.object, v.property)
245 REALM_SC_COMPARE(AddInitialProperties, v.object)
246 REALM_SC_COMPARE(AddTable, v.object)
247 REALM_SC_COMPARE(ChangePrimaryKey, v.object, v.property)
248 REALM_SC_COMPARE(ChangePropertyType, v.object, v.old_property, v.new_property)
249 REALM_SC_COMPARE(MakePropertyNullable, v.object, v.property)
250 REALM_SC_COMPARE(MakePropertyRequired, v.object, v.property)
251 REALM_SC_COMPARE(RemoveIndex, v.object, v.property)
252 REALM_SC_COMPARE(RemoveProperty, v.object, v.property)
254 #undef REALM_SC_COMPARE
256 return rgt.visit(visitor);