1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2016 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 #ifndef REALM_OS_OBJECT_ACCESSOR_HPP
20 #define REALM_OS_OBJECT_ACCESSOR_HPP
24 #include "feature_checks.hpp"
26 #include "object_schema.hpp"
27 #include "object_store.hpp"
28 #include "results.hpp"
30 #include "shared_realm.hpp"
32 #include <realm/link_view.hpp>
33 #include <realm/util/assert.hpp>
34 #include <realm/table_view.hpp>
37 #include <realm/sync/object.hpp>
38 #endif // REALM_ENABLE_SYNC
43 template <typename ValueType, typename ContextType>
44 void Object::set_property_value(ContextType& ctx, StringData prop_name, ValueType value, bool try_update)
47 m_realm->verify_in_write();
48 auto& property = property_for_name(prop_name);
50 // Modifying primary keys is allowed in migrations to make it possible to
51 // add a new primary key to a type (or change the property type), but it
52 // is otherwise considered the immutable identity of the row
53 if (property.is_primary && !m_realm->is_in_migration())
54 throw std::logic_error("Cannot modify primary key after creation");
56 set_property_value_impl(ctx, property, value, try_update);
59 template <typename ValueType, typename ContextType>
60 ValueType Object::get_property_value(ContextType& ctx, StringData prop_name)
62 return get_property_value_impl<ValueType>(ctx, property_for_name(prop_name));
65 template <typename ValueType, typename ContextType>
66 void Object::set_property_value_impl(ContextType& ctx, const Property &property,
67 ValueType value, bool try_update, bool is_default)
69 ctx.will_change(*this, property);
71 auto& table = *m_row.get_table();
72 size_t col = property.table_column;
73 size_t row = m_row.get_index();
74 if (is_nullable(property.type) && ctx.is_null(value)) {
75 if (property.type == PropertyType::Object) {
77 table.nullify_link(col, row);
80 table.set_null(col, row, is_default);
87 if (is_array(property.type)) {
88 if (property.type == PropertyType::LinkingObjects)
89 throw ReadOnlyPropertyException(m_object_schema->name, property.name);
91 ContextType child_ctx(ctx, property);
92 List list(m_realm, *m_row.get_table(), col, m_row.get_index());
93 list.assign(child_ctx, value, try_update);
98 switch (property.type & ~PropertyType::Nullable) {
99 case PropertyType::Object: {
100 ContextType child_ctx(ctx, property);
101 auto link = child_ctx.template unbox<RowExpr>(value, true, try_update);
102 table.set_link(col, row, link.get_index(), is_default);
105 case PropertyType::Bool:
106 table.set(col, row, ctx.template unbox<bool>(value), is_default);
108 case PropertyType::Int:
109 table.set(col, row, ctx.template unbox<int64_t>(value), is_default);
111 case PropertyType::Float:
112 table.set(col, row, ctx.template unbox<float>(value), is_default);
114 case PropertyType::Double:
115 table.set(col, row, ctx.template unbox<double>(value), is_default);
117 case PropertyType::String:
118 table.set(col, row, ctx.template unbox<StringData>(value), is_default);
120 case PropertyType::Data:
121 table.set(col, row, ctx.template unbox<BinaryData>(value), is_default);
123 case PropertyType::Any:
124 throw std::logic_error("not supported");
125 case PropertyType::Date:
126 table.set(col, row, ctx.template unbox<Timestamp>(value), is_default);
129 REALM_COMPILER_HINT_UNREACHABLE();
134 template <typename ValueType, typename ContextType>
135 ValueType Object::get_property_value_impl(ContextType& ctx, const Property &property)
139 size_t column = property.table_column;
140 if (is_nullable(property.type) && m_row.is_null(column))
141 return ctx.null_value();
142 if (is_array(property.type) && property.type != PropertyType::LinkingObjects)
143 return ctx.box(List(m_realm, *m_row.get_table(), column, m_row.get_index()));
145 switch (property.type & ~PropertyType::Flags) {
146 case PropertyType::Bool: return ctx.box(m_row.get_bool(column));
147 case PropertyType::Int: return ctx.box(m_row.get_int(column));
148 case PropertyType::Float: return ctx.box(m_row.get_float(column));
149 case PropertyType::Double: return ctx.box(m_row.get_double(column));
150 case PropertyType::String: return ctx.box(m_row.get_string(column));
151 case PropertyType::Data: return ctx.box(m_row.get_binary(column));
152 case PropertyType::Date: return ctx.box(m_row.get_timestamp(column));
153 case PropertyType::Any: return ctx.box(m_row.get_mixed(column));
154 case PropertyType::Object: {
155 auto linkObjectSchema = m_realm->schema().find(property.object_type);
156 TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), property.object_type);
157 return ctx.box(Object(m_realm, *linkObjectSchema, table->get(m_row.get_link(column))));
159 case PropertyType::LinkingObjects: {
160 auto target_object_schema = m_realm->schema().find(property.object_type);
161 auto link_property = target_object_schema->property_for_name(property.link_origin_property_name);
162 TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), target_object_schema->name);
163 auto tv = m_row.get_table()->get_backlink_view(m_row.get_index(), table.get(), link_property->table_column);
164 return ctx.box(Results(m_realm, std::move(tv)));
166 default: REALM_UNREACHABLE();
170 template<typename ValueType, typename ContextType>
171 Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm,
172 StringData object_type, ValueType value,
173 bool try_update, Row* out_row)
175 auto object_schema = realm->schema().find(object_type);
176 REALM_ASSERT(object_schema != realm->schema().end());
177 return create(ctx, realm, *object_schema, value, try_update, out_row);
180 template<typename ValueType, typename ContextType>
181 Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm,
182 ObjectSchema const& object_schema, ValueType value,
183 bool try_update, Row* out_row)
185 realm->verify_in_write();
187 // get or create our accessor
188 bool created = false;
190 // try to get existing row if updating
191 size_t row_index = realm::not_found;
192 TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
194 bool skip_primary = true;
195 if (auto primary_prop = object_schema.primary_key_property()) {
196 // search for existing object based on primary key type
197 auto primary_value = ctx.value_for_property(value, primary_prop->name,
198 primary_prop - &object_schema.persisted_properties[0]);
200 primary_value = ctx.default_value_for_property(object_schema, primary_prop->name);
201 if (!primary_value) {
202 if (!is_nullable(primary_prop->type))
203 throw MissingPropertyValueException(object_schema.name, primary_prop->name);
204 primary_value = ctx.null_value();
206 row_index = get_for_primary_key_impl(ctx, *table, *primary_prop, *primary_value);
208 if (row_index == realm::not_found) {
210 if (primary_prop->type == PropertyType::Int) {
211 #if REALM_ENABLE_SYNC
212 row_index = sync::create_object_with_primary_key(realm->read_group(), *table, ctx.template unbox<util::Optional<int64_t>>(*primary_value));
214 row_index = table->add_empty_row();
215 if (ctx.is_null(*primary_value))
216 table->set_null_unique(primary_prop->table_column, row_index);
218 table->set_unique(primary_prop->table_column, row_index, ctx.template unbox<int64_t>(*primary_value));
219 #endif // REALM_ENABLE_SYNC
221 else if (primary_prop->type == PropertyType::String) {
222 auto value = ctx.template unbox<StringData>(*primary_value);
223 #if REALM_ENABLE_SYNC
224 row_index = sync::create_object_with_primary_key(realm->read_group(), *table, value);
226 row_index = table->add_empty_row();
227 table->set_unique(primary_prop->table_column, row_index, value);
228 #endif // REALM_ENABLE_SYNC
231 REALM_TERMINATE("Unsupported primary key type.");
234 else if (!try_update) {
235 if (realm->is_in_migration()) {
236 // Creating objects with duplicate primary keys is allowed in migrations
237 // as long as there are no duplicates at the end, as adding an entirely
238 // new column which is the PK will inherently result in duplicates at first
239 row_index = table->add_empty_row();
241 skip_primary = false;
244 throw std::logic_error(util::format("Attempting to create an object of type '%1' with an existing primary key value '%2'.",
245 object_schema.name, ctx.print(*primary_value)));
250 #if REALM_ENABLE_SYNC
251 row_index = sync::create_object(realm->read_group(), *table);
253 row_index = table->add_empty_row();
254 #endif // REALM_ENABLE_SYNC
259 Object object(realm, object_schema, table->get(row_index));
261 *out_row = object.row();
262 for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
263 auto& prop = object_schema.persisted_properties[i];
264 if (skip_primary && prop.is_primary)
267 auto v = ctx.value_for_property(value, prop.name, i);
271 bool is_default = false;
273 v = ctx.default_value_for_property(object_schema, prop.name);
276 if ((!v || ctx.is_null(*v)) && !is_nullable(prop.type) && !is_array(prop.type)) {
277 if (prop.is_primary || !ctx.allow_missing(value))
278 throw MissingPropertyValueException(object_schema.name, prop.name);
281 object.set_property_value_impl(ctx, prop, *v, try_update, is_default);
286 template<typename ValueType, typename ContextType>
287 Object Object::get_for_primary_key(ContextType& ctx, std::shared_ptr<Realm> const& realm,
288 StringData object_type, ValueType primary_value)
290 auto object_schema = realm->schema().find(object_type);
291 REALM_ASSERT(object_schema != realm->schema().end());
292 return get_for_primary_key(ctx, realm, *object_schema, primary_value);
295 template<typename ValueType, typename ContextType>
296 Object Object::get_for_primary_key(ContextType& ctx, std::shared_ptr<Realm> const& realm,
297 const ObjectSchema &object_schema,
298 ValueType primary_value)
300 auto primary_prop = object_schema.primary_key_property();
302 throw MissingPrimaryKeyException(object_schema.name);
305 auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
307 return Object(realm, object_schema, RowExpr());
308 auto row_index = get_for_primary_key_impl(ctx, *table, *primary_prop, primary_value);
310 return Object(realm, object_schema, row_index == realm::not_found ? Row() : Row(table->get(row_index)));
313 template<typename ValueType, typename ContextType>
314 size_t Object::get_for_primary_key_impl(ContextType& ctx, Table const& table,
315 const Property &primary_prop,
316 ValueType primary_value) {
317 bool is_null = ctx.is_null(primary_value);
318 if (is_null && !is_nullable(primary_prop.type))
319 throw std::logic_error("Invalid null value for non-nullable primary key.");
320 if (primary_prop.type == PropertyType::String) {
321 return table.find_first(primary_prop.table_column,
322 ctx.template unbox<StringData>(primary_value));
324 if (is_nullable(primary_prop.type))
325 return table.find_first(primary_prop.table_column,
326 ctx.template unbox<util::Optional<int64_t>>(primary_value));
327 return table.find_first(primary_prop.table_column,
328 ctx.template unbox<int64_t>(primary_value));
333 #endif // REALM_OS_OBJECT_ACCESSOR_HPP