Newer
Older
LaserMethane / Pods / Realm / Realm / RLMObjectStore.mm
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#import "RLMObjectStore.h"

#import "RLMAccessor.hpp"
#import "RLMArray_Private.hpp"
#import "RLMObservation.hpp"
#import "RLMObject_Private.hpp"
#import "RLMObjectSchema_Private.hpp"
#import "RLMProperty_Private.h"
#import "RLMQueryUtil.hpp"
#import "RLMRealm_Private.hpp"
#import "RLMSchema_Private.h"
#import "RLMSet_Private.hpp"
#import "RLMSwiftCollectionBase.h"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
#import "RLMSwiftValueStorage.h"

#import <realm/object-store/object_store.hpp>
#import <realm/object-store/results.hpp>
#import <realm/object-store/shared_realm.hpp>
#import <realm/group.hpp>

#import <objc/message.h>

using namespace realm;

void RLMRealmCreateAccessors(RLMSchema *schema) {
    const size_t bufferSize = sizeof("RLM:Managed  ") // includes null terminator
                            + std::numeric_limits<unsigned long long>::digits10
                            + realm::Group::max_table_name_length;

    char className[bufferSize] = "RLM:Managed ";
    char *const start = className + strlen(className);

    for (RLMObjectSchema *objectSchema in schema.objectSchema) {
        if (objectSchema.accessorClass != objectSchema.objectClass) {
            continue;
        }

        static unsigned long long count = 0;
        sprintf(start, "%llu %s", count++, objectSchema.className.UTF8String);
        objectSchema.accessorClass = RLMManagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema, className);
    }
}

static inline void RLMVerifyRealmRead(__unsafe_unretained RLMRealm *const realm) {
    if (!realm) {
        @throw RLMException(@"Realm must not be nil");
    }
    [realm verifyThread];
    if (realm->_realm->is_closed()) {
        // This message may seem overly specific, but frozen Realms are currently
        // the only ones which we outright close.
        @throw RLMException(@"Cannot read from a frozen Realm which has been invalidated.");
    }
}

void RLMVerifyInWriteTransaction(__unsafe_unretained RLMRealm *const realm) {
    RLMVerifyRealmRead(realm);
    // if realm is not writable throw
    if (!realm.inWriteTransaction) {
        @throw RLMException(@"Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.");
    }
}

void RLMInitializeSwiftAccessor(__unsafe_unretained RLMObjectBase *const object, bool promoteExisting) {
    if (!object || !object->_row || !object->_objectSchema->_isSwiftClass) {
        return;
    }
    if (![object isKindOfClass:object->_objectSchema.objectClass]) {
        // It can be a different class if it's a dynamic object, and those don't
        // require any init here (and would crash since they don't have the ivars)
        return;
    }

    if (promoteExisting) {
        for (RLMProperty *prop in object->_objectSchema.swiftGenericProperties) {
            [prop.swiftAccessor promote:prop on:object];
        }
    }
    else {
        for (RLMProperty *prop in object->_objectSchema.swiftGenericProperties) {
            [prop.swiftAccessor initialize:prop on:object];
        }
    }
}

void RLMVerifyHasPrimaryKey(Class cls) {
    RLMObjectSchema *schema = [cls sharedSchema];
    if (!schema.primaryKeyProperty) {
        NSString *reason = [NSString stringWithFormat:@"'%@' does not have a primary key and can not be updated", schema.className];
        @throw [NSException exceptionWithName:@"RLMException" reason:reason userInfo:nil];
    }
}

static CreatePolicy updatePolicyToCreatePolicy(RLMUpdatePolicy policy) {
    CreatePolicy createPolicy = {.create = true, .copy = false, .diff = false, .update = false};
    switch (policy) {
        case RLMUpdatePolicyError:
            break;
        case RLMUpdatePolicyUpdateChanged:
            createPolicy.diff = true;
            [[clang::fallthrough]];
        case RLMUpdatePolicyUpdateAll:
            createPolicy.update = true;
            break;
    }
    return createPolicy;
}

void RLMAddObjectToRealm(__unsafe_unretained RLMObjectBase *const object,
                         __unsafe_unretained RLMRealm *const realm,
                         RLMUpdatePolicy updatePolicy) {
    RLMVerifyInWriteTransaction(realm);

    CreatePolicy createPolicy = updatePolicyToCreatePolicy(updatePolicy);
    createPolicy.copy = false;
    auto& info = realm->_info[object->_objectSchema.className];
    RLMAccessorContext c{info};
    c.createObject(object, createPolicy);
}

RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *className,
                                               id value, RLMUpdatePolicy updatePolicy) {
    RLMVerifyInWriteTransaction(realm);

    CreatePolicy createPolicy = updatePolicyToCreatePolicy(updatePolicy);
    createPolicy.copy = true;

    auto& info = realm->_info[className];
    RLMAccessorContext c{info};
    RLMObjectBase *object = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info);
    auto [obj, reuseExisting] = c.createObject(value, createPolicy, true);
    if (reuseExisting) {
        return value;
    }
    object->_row = std::move(obj);
    RLMInitializeSwiftAccessor(object, false);
    return object;
}

RLMObjectBase *RLMObjectFromObjLink(RLMRealm *realm, realm::ObjLink&& objLink, bool parentIsSwiftObject) {
    if (auto* tableInfo = realm->_info[objLink.get_table_key()]) {
        return RLMCreateObjectAccessor(*tableInfo, objLink.get_obj_key().value);
    } else {
        // Construct the object dynamically.
        // This code path should only be hit on first access of the object.
        Class cls = parentIsSwiftObject ? [RealmSwiftDynamicObject class] : [RLMDynamicObject class];
        auto& group = realm->_realm->read_group();
        auto schema = std::make_unique<realm::ObjectSchema>(group,
                                                            group.get_table_name(objLink.get_table_key()),
                                                            objLink.get_table_key());
        RLMObjectSchema *rlmObjectSchema = [RLMObjectSchema objectSchemaForObjectStoreSchema:*schema];
        rlmObjectSchema.accessorClass = cls;
        rlmObjectSchema.isSwiftClass = parentIsSwiftObject;
        realm->_info.appendDynamicObjectSchema(std::move(schema), rlmObjectSchema, realm);
        return RLMCreateObjectAccessor(realm->_info[rlmObjectSchema.className], objLink.get_obj_key().value);
    }
}

void RLMDeleteObjectFromRealm(__unsafe_unretained RLMObjectBase *const object,
                              __unsafe_unretained RLMRealm *const realm) {
    if (realm != object->_realm) {
        @throw RLMException(@"Can only delete an object from the Realm it belongs to.");
    }

    RLMVerifyInWriteTransaction(object->_realm);

    if (object->_row.is_valid()) {
        RLMObservationTracker tracker(realm, true);
        object->_row.remove();
    }
    object->_realm = nil;
}

void RLMDeleteAllObjectsFromRealm(RLMRealm *realm) {
    RLMVerifyInWriteTransaction(realm);

    // clear table for each object schema
    for (auto& info : realm->_info) {
        RLMClearTable(info.second);
    }
}

RLMResults *RLMGetObjects(__unsafe_unretained RLMRealm *const realm,
                          NSString *objectClassName,
                          NSPredicate *predicate) {
    RLMVerifyRealmRead(realm);

    // create view from table and predicate
    RLMClassInfo& info = realm->_info[objectClassName];
    if (!info.table()) {
        // read-only realms may be missing tables since we can't add any
        // missing ones on init
        return [RLMResults resultsWithObjectInfo:info results:{}];
    }

    if (predicate) {
        realm::Query query = RLMPredicateToQuery(predicate, info.rlmObjectSchema, realm.schema, realm.group);
        return [RLMResults resultsWithObjectInfo:info
                                         results:realm::Results(realm->_realm, std::move(query))];
    }

    return [RLMResults resultsWithObjectInfo:info
                                     results:realm::Results(realm->_realm, info.table())];
}

id RLMGetObject(RLMRealm *realm, NSString *objectClassName, id key) {
    RLMVerifyRealmRead(realm);

    auto& info = realm->_info[objectClassName];
    if (RLMProperty *prop = info.propertyForPrimaryKey()) {
        RLMValidateValueForProperty(key, info.rlmObjectSchema, prop);
    }
    try {
        RLMAccessorContext c{info};
        auto obj = realm::Object::get_for_primary_key(c, realm->_realm, *info.objectSchema,
                                                      key ?: NSNull.null);
        if (!obj.is_valid())
            return nil;
        return RLMCreateObjectAccessor(info, obj.obj());
    }
    catch (std::exception const& e) {
        @throw RLMException(e);
    }
}

RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, int64_t key) {
    return RLMCreateObjectAccessor(info, info.table()->get_object(realm::ObjKey(key)));
}

// Create accessor and register with realm
RLMObjectBase *RLMCreateObjectAccessor(RLMClassInfo& info, realm::Obj&& obj) {
    RLMObjectBase *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info);
    accessor->_row = std::move(obj);
    RLMInitializeSwiftAccessor(accessor, false);
    return accessor;
}