/// <summary> /// Updates an object. /// </summary> /// <remarks> /// Updates an object. <pre>Try to update in place. Only change what has changed. This is restricted to particular types (fixed size types). If in place update is /// not possible, then deletes the current object and creates a new at the end of the database file and updates /// OID object position. /// @param object The object to be updated /// @param forceUpdate when true, no verification is done to check if update must be done. /// @return The oid of the object, as a negative number /// @</pre> /// </remarks> public OID UpdateNonNativeObjectInfo(NonNativeObjectInfo nnoi, bool forceUpdate) { var hasObject = true; var @object = nnoi.GetObject(); var oid = nnoi.GetOid(); if (@object == null) hasObject = false; // When there is index,we must *always* load the old meta representation // to compute index keys var withIndex = !nnoi.GetClassInfo().GetIndexes().IsEmpty(); NonNativeObjectInfo oldMetaRepresentation = null; // Used to check consistency, at the end, the number of // nbConnectedObjects must and nbUnconnected must remain unchanged //TODO: why we are not using / checking that? (below is continuity) nnoi.GetClassInfo().CommitedZoneInfo.GetNumberbOfObjects(); nnoi.GetClassInfo().UncommittedZoneInfo.GetNumberbOfObjects(); var objectHasChanged = false; try { var lsession = _session; var positionBeforeWrite = _objectWriter.FileSystemProcessor.FileSystemInterface.GetPosition(); var tmpCache = lsession.GetTmpCache(); var cache = lsession.GetCache(); // Get header of the object (position, previous object position, // next // object position and class info position) // The header must be in the cache. var lastHeader = cache.GetObjectInfoHeaderByOid(oid, true); if (lastHeader == null) throw new OdbRuntimeException( NDatabaseError.UnexpectedSituation.AddParameter("Header is null in update")); if (lastHeader.GetOid() == null) throw new OdbRuntimeException( NDatabaseError.InternalError.AddParameter("Header oid is null for oid " + oid)); var objectIsInConnectedZone = cache.IsInCommitedZone(oid); var currentPosition = lastHeader.GetPosition(); if (currentPosition == -1) { throw new OdbRuntimeException( NDatabaseError.InstancePositionIsNegative.AddParameter(currentPosition).AddParameter(oid). AddParameter("In Object Info Header")); } // triggers,FIXME passing null to old object representation _storageEngine.GetTriggerManager().ManageUpdateTriggerBefore(nnoi.GetClassInfo().UnderlyingType, null, hasObject ? @object : nnoi, oid); // Use to control if the in place update is ok. The // ObjectInstrospector stores the number of changes // that were detected and here we try to apply them using in place // update.If at the end // of the in place update the number of applied changes is smaller // then the number // of detected changes, then in place update was not successfully, // we // must do a real update, // creating an object elsewhere :-( if (!forceUpdate) { var cachedOid = cache.IdOfInsertingObject(@object); if (cachedOid != null) { // The object is being inserted (must be a cyclic // reference), simply returns id id return cachedOid; } // the nnoi (NonNativeObjectInfo is the meta representation of // the object to update // To know what must be upated we must get the meta // representation of this object before // The modification. Taking this 'old' meta representation from // the // cache does not resolve // : because cache is a reference to the real object and object // has been changed, // so the cache is pointing to the reference, that has changed! // This old meta representation must be re-read from the last // committed database // false, = returnInstance (java object) = false try { var useCache = !objectIsInConnectedZone; oldMetaRepresentation = _objectReader.ReadNonNativeObjectInfoFromPosition(null, oid, currentPosition, useCache, false); tmpCache.ClearObjectInfos(); } catch (OdbRuntimeException e) { var position = currentPosition.ToString(); throw new OdbRuntimeException( NDatabaseError.InternalError.AddParameter("Error while reading old Object Info of oid " + oid + " at pos " + position), e); } // Make sure we work with the last version of the object var onDiskVersion = oldMetaRepresentation.GetHeader().GetObjectVersion(); var onDiskUpdateDate = oldMetaRepresentation.GetHeader().GetUpdateDate(); var inCacheVersion = lastHeader.GetObjectVersion(); var inCacheUpdateDate = lastHeader.GetUpdateDate(); if (onDiskUpdateDate > inCacheUpdateDate || onDiskVersion > inCacheVersion) lastHeader = oldMetaRepresentation.GetHeader(); nnoi.SetHeader(lastHeader); // increase the object version number from the old meta // representation nnoi.GetHeader().IncrementVersionAndUpdateDate(); // Keep the creation date nnoi.GetHeader().SetCreationDate(oldMetaRepresentation.GetHeader().GetCreationDate()); // Set the object of the old meta to make the object comparator // understand, they are 2 // meta representation of the same object // TODO , check if if is the best way to do oldMetaRepresentation.SetObject(nnoi.GetObject()); // Reset the comparator _comparator.Clear(); objectHasChanged = _comparator.HasChanged(oldMetaRepresentation, nnoi); if (!objectHasChanged) { _objectWriter.FileSystemProcessor.FileSystemInterface.SetWritePosition(positionBeforeWrite, true); return oid; } } // If we reach this update, In Place Update was not possible. Do a // normal update. Deletes the // current object and creates a new one if (oldMetaRepresentation == null && withIndex) { // We must load old meta representation to be able to compute // old index key to update index oldMetaRepresentation = _objectReader.ReadNonNativeObjectInfoFromPosition(null, oid, currentPosition, false, false); } var previousObjectOID = lastHeader.GetPreviousObjectOID(); var nextObjectOid = lastHeader.GetNextObjectOID(); nnoi.SetPreviousInstanceOID(previousObjectOID); nnoi.SetNextObjectOID(nextObjectOid); // Mark the block of current object as deleted _objectWriter.MarkAsDeleted(currentPosition, objectIsInConnectedZone); // Creates the new object oid = InsertNonNativeObject(oid, nnoi, false); // This position after write must be call just after the insert!! var positionAfterWrite = _objectWriter.FileSystemProcessor.FileSystemInterface.GetPosition(); if (hasObject) { // update cache cache.AddObject(oid, @object, nnoi.GetHeader()); } _objectWriter.FileSystemProcessor.FileSystemInterface.SetWritePosition(positionAfterWrite, true); //TODO: why we are not using / checking that? (below is continuity) nnoi.GetClassInfo().CommitedZoneInfo.GetNumberbOfObjects(); nnoi.GetClassInfo().UncommittedZoneInfo.GetNumberbOfObjects(); return oid; } catch (Exception e) { var message = string.Format("Error updating object {0} : {1}", nnoi, e); throw new OdbRuntimeException(e, message); } finally { if (objectHasChanged) { if (withIndex) ManageIndexesForUpdate(oid, nnoi, oldMetaRepresentation); // triggers,FIXME passing null to old object representation // (oldMetaRepresentation may be null) _storageEngine.GetTriggerManager().ManageUpdateTriggerAfter( nnoi.GetClassInfo().UnderlyingType, oldMetaRepresentation, hasObject ? @object : nnoi, oid); } } }
/// <summary> /// Updates pointers of objects, Only changes uncommitted info pointers /// </summary> /// <param name="objectInfo"> The meta representation of the object being inserted </param> /// <param name="classInfo"> The class of the object being inserted </param> public void ManageNewObjectPointers(NonNativeObjectInfo objectInfo, ClassInfo classInfo) { var cache = _storageEngine.GetSession().GetCache(); var isFirstUncommitedObject = !classInfo.UncommittedZoneInfo.HasObjects(); // if it is the first uncommitted object if (isFirstUncommitedObject) { classInfo.UncommittedZoneInfo.First = objectInfo.GetOid(); var lastCommittedObjectOid = classInfo.CommitedZoneInfo.Last; if (lastCommittedObjectOid != null) { // Also updates the last committed object next object oid in // memory to connect the committed // zone with unconnected for THIS transaction (only in memory) var oih = cache.GetObjectInfoHeaderByOid(lastCommittedObjectOid, true); oih.SetNextObjectOID(objectInfo.GetOid()); // And sets the previous oid of the current object with the last // committed oid objectInfo.SetPreviousInstanceOID(lastCommittedObjectOid); } } else { // Gets the last object, updates its (next object) // pointer to the new object and updates the class info 'last // uncommitted object // oid' field var oip = classInfo.LastObjectInfoHeader; if (oip == null) { throw new OdbRuntimeException( NDatabaseError.InternalError.AddParameter("last OIP is null in manageNewObjectPointers oid=" + objectInfo.GetOid())); } if (oip.GetNextObjectOID() != objectInfo.GetOid()) { oip.SetNextObjectOID(objectInfo.GetOid()); // Here we are working in unconnected zone, so this // can be done without transaction: actually // write in database file UpdateNextObjectFieldOfObjectInfo(oip.GetOid(), oip.GetNextObjectOID(), false); objectInfo.SetPreviousInstanceOID(oip.GetOid()); // Resets the class info oid: In some case, // (client // server) it may be -1. oip.SetClassInfoId(classInfo.ClassInfoId); // object info oip has been changed, we must put it // in the cache to turn this change available for current // transaction until the commit _storageEngine.GetSession().GetCache().AddObjectInfoOfNonCommitedObject(oip); } } // always set the new last object oid and the number of objects classInfo.UncommittedZoneInfo.Last = objectInfo.GetOid(); classInfo.UncommittedZoneInfo.IncreaseNbObjects(); // Then updates the last info pointers of the class info // with this new created object // At this moment, the objectInfo.getHeader() do not have the // attribute ids. // but later in this code, the attributes will be set, so the class // info also will have them classInfo.LastObjectInfoHeader = objectInfo.GetHeader(); // // Saves the fact that something has changed in the class (number of // objects and/or last object oid) _storageEngine.GetSession().GetMetaModel().AddChangedClass(classInfo); }