public void OnParentDelete(RowId parentId, HashSet <ChildRow> deleteList) { TField parentValue = this.primaryKey.Value(parentId, this.childTable.table.SnapStore.Version); foreach (RowId childId in this.childTable.Select <TField>(this.childColumn, parentValue)) { switch (this.action) { case ForeignKeyAction.Restrict: throw new ForeignKeyViolationException(this.Name); case ForeignKeyAction.Cascade: if (deleteList.Add(new ChildRow(this.childTable, childId)) && this.childTable.table.PrimaryKey != null) { foreach (IForeignKey fk in this.childTable.table.Children) { fk.OnParentDelete(childId, deleteList); } } break; case ForeignKeyAction.SetDefault: this.childTable.SetField <TField>(childId, this.childColumn, this.childColumn.DefaultValue); break; } } }
public void Validate() { int version = this.childTable.StoreSnapshot.Version; if (this.childTable.table.WasChangedIn(version)) { using (IEnumerator <SnapTableChange <TRecord> > enumerator = this.childTable.table.GetChanges(version)) { while (enumerator.MoveNext()) { if (enumerator.Current.Action != SnapTableAction.Delete) { TField value = enumerator.Current.GetNewField <TField>(this.childColumn); if (!this.allowsDefault || this.childColumn.Compare(value, this.childColumn.DefaultValue) != 0) { RowId parentId = this.primaryKey.FindUnique(value, version); if (parentId == RowId.Empty) { throw new ForeignKeyViolationException(this.Name); } } } } } } }
public void Insert(TField value, RowId rowId) { Key key = new Key() { field = value, rowId = rowId }; RowId rootId = this.Root(this.table.SnapStore.Version); if (this.table.GetLatestField <int>(rootId, this.countField) < this.keyFields.Length) { this.InsertNoneFull(rootId, key); } else { Debug.Assert(this.table.GetLatestField <int>(rootId, this.countField) == this.keyFields.Length); Node node = new Node() { IsLeaf = false, C0 = rootId }; rootId = this.table.Insert(ref node); this.SetRoot(rootId); this.SplitChild(rootId, 0); this.InsertNoneFull(rootId, key); } #if ValidateTree this.Validate(); #endif }
/// <summary> /// Updates field of the row /// </summary> /// <typeparam name="TField">Type of the field</typeparam> /// <param name="rowId">row id</param> /// <param name="field">Field to update</param> /// <param name="value">New value to assign</param> /// <returns></returns> public bool SetField <TField>(RowId rowId, IField <TRecord, TField> field, TField value) { this.ValidateModification(); this.table.ValidateField(field); if (field.Compare(this.table.GetLatestField <TField>(rowId, field), value) != 0) { List <IIndex <TRecord> > list = this.table.Indexes[field.Order]; if (list != null) { foreach (IIndex <TRecord> index in list) { // no need to check for timestamp as only one field updated index.Delete(rowId); } } bool updated = this.table.SetField <TField>(rowId, field, value); Debug.Assert(updated); if (list != null) { foreach (IIndex <TRecord> index in list) { // no need to check for timestamp as only one field updated index.Insert(rowId); } } return(true); } return(false); }
/// <summary> /// Returns action made to the row during the transaction /// </summary> /// <param name="change"></param> /// <returns></returns> public SnapTableAction Action(int change) { if (change < this.InsertCount()) { Debug.Assert(this.forRollback || !this.table.IsDeleted(this.RowId(change), this.newVersion.Version, false), "This row is invalid and should not be enumerated as a change" ); return(SnapTableAction.Insert); } else { RowId rowId = this.RowId(change); bool oldDeleted = this.table.IsDeleted(rowId, this.oldVersion.Version, false); bool newDeleted = this.table.IsDeleted(rowId, this.newVersion.Version, false); if (oldDeleted != newDeleted) { return(oldDeleted ? SnapTableAction.Insert : SnapTableAction.Delete); } else { Debug.Assert(!oldDeleted || this.forRollback, "Row must not be deleted in order to be modified"); return(SnapTableAction.Update); } } }
private void Validate(RowId rowId) { int version = this.table.SnapStore.Version; Node node; this.table.GetData(rowId, version, out node); Debug.Assert(0 < node.Count && node.Count <= this.keyFields.Length || node.Count == 0 && node.IsLeaf && this.Root(version) == rowId); Key key = node.K0; for (int i = 1; i < node.Count; i++) { Key k2 = this.keyFields[i].GetValue(ref node); Debug.Assert(this.keyFields[i].Compare(key, k2) < 0); if (!node.IsLeaf) { this.Validate(this.childFields[i].GetValue(ref node), key, k2); } key = k2; } if (!node.IsLeaf) { this.ValidateFirst(this.childFields[0].GetValue(ref node), node.K0); this.ValidateLast(this.childFields[node.Count].GetValue(ref node), this.keyFields[node.Count - 1].GetValue(ref node)); } }
/// <summary> /// Pushes row to log. Row can be logged only once in transaction, so only initial state (as it was at the beginning of transaction) will be saved /// </summary> /// <param name="row">Reference to the data to be logged</param> /// <param name="rowId">Id of this data. This should be the Id of row provided in the first param.</param> private void PushToLog(ref Row row, RowId rowId) { // ref to row should be in table at rowId. It is impossible to check this in C#, so just check if bits are equal Debug.Assert( 0 == TableSnapshot <TRecord> .Compare( this.Fields, ref row.Data, ref this.table.ItemAddress(rowId.Value).Page[this.table.ItemAddress(rowId.Value).Index].Data ), "ref to row should be in table at rowId" ); ValueList <Snap> .Address snapAddress = this.snap.ItemAddress(this.snap.Count - 1); if (snapAddress.Page[snapAddress.Index].Version < this.SnapStore.Version) { // first change in this transaction so the row is older Debug.Assert(rowId.Value < snapAddress.Page[snapAddress.Index].TableSize, "Row should be already in the table"); this.snap.PrepareAdd(); this.log.PrepareAdd(); Snap point = new Snap() { Version = this.SnapStore.Version, TableSize = this.table.Count, LogSize = this.log.Count + 1 }; RuntimeHelpers.PrepareConstrainedRegions(); try {} finally { int index = this.log.FixedAllocate(); ValueList <Log> .Address logAddress = this.log.ItemAddress(index); logAddress.Page[logAddress.Index].Data = row.Data; logAddress.Page[logAddress.Index].RowId = rowId; logAddress.Page[logAddress.Index].RawLogIndex = row.RawLogIndex; row.LogIndex = index; this.snap.FixedAdd(ref point); } } else { Debug.Assert(snapAddress.Page[snapAddress.Index].Version == this.SnapStore.Version, "Impossible state: this should be the current transaction"); Debug.Assert(snapAddress.Page[snapAddress.Index].TableSize == this.table.Count, "Impossible state: wrong table size"); Debug.Assert(snapAddress.Page[snapAddress.Index].LogSize == this.log.Count, "Impossible state: wrong log size"); // some changes were made in this transaction. Check if the row was already modified in this transaction. // get size of log in the previous transaction ValueList <Snap> .Address oldSnapAddress = this.snap.ItemAddress(this.snap.Count - 2); if (rowId.Value < oldSnapAddress.Page[oldSnapAddress.Index].TableSize && row.LogIndex < oldSnapAddress.Page[oldSnapAddress.Index].LogSize) { // this is the first time the row is updated in this transaction Debug.Assert(snapAddress.Page[snapAddress.Index].LogSize == this.log.Count, "Invalid state: wrong log size"); this.log.PrepareAdd(); RuntimeHelpers.PrepareConstrainedRegions(); try {} finally { int index = this.log.FixedAllocate(); ValueList <Log> .Address logAddress = this.log.ItemAddress(index); logAddress.Page[logAddress.Index].Data = row.Data; logAddress.Page[logAddress.Index].RowId = rowId; logAddress.Page[logAddress.Index].RawLogIndex = row.RawLogIndex; row.LogIndex = index; snapAddress.Page[snapAddress.Index].LogSize = this.log.Count; } } } }
public IEnumerable <RowId> Find(TField value, int version) { RowId rowId = this.FindUnique(value, version); if (!rowId.IsEmpty) { yield return(rowId); } }
private RowId NewRow(int change) { RowId rowId = this.RowId(change); if (this.table.IsDeleted(rowId, this.newVersion.Version, false)) { throw new InvalidOperationException(Properties.Resources.ErrorWrongNewData); } return(rowId); }
private void ValidateFirst(RowId rowId, Key max) { int version = this.table.SnapStore.Version; Node node; this.table.GetData(rowId, version, out node); Debug.Assert(this.MinDegree <= node.Count && node.Count <= this.keyFields.Length); Debug.Assert(this.keyFields[0].Compare(this.keyFields[node.Count - 1].GetValue(ref node), max) < 0); this.Validate(rowId); }
/// <summary> /// Deletes row. /// </summary> /// <param name="rowId"></param> public void Delete(RowId rowId) { Debug.Assert(0 <= rowId.Value && rowId.Value < this.table.Count, "broken rowId"); this.ValidateModification(); ValueList <Row> .Address address = this.table.ItemAddress(rowId.Value); SnapTable <TRecord> .ValidateModification(ref address); this.PushToLog(ref address.Page[address.Index], rowId); // if the row was inserted in this transaction, then after deletion it will be invalid. so just ignore it in all future operations address.Page[address.Index].IsDeleted = true; }
/// <summary> /// Undelete previously deleted row. /// </summary> /// <param name="rowId"></param> public void UnDelete(RowId rowId) { Debug.Assert(0 <= rowId.Value && rowId.Value < this.table.Count, "broken rowId"); this.ValidateModification(); ValueList <Row> .Address address = this.table.ItemAddress(rowId.Value); if (!address.Page[address.Index].IsDeleted) { throw new InvalidOperationException(Properties.Resources.ErrorUndeleteRow); } this.PushToLog(ref address.Page[address.Index], rowId); address.Page[address.Index].IsDeleted = false; }
/// <summary> /// Gets record in the specified version /// </summary> /// <param name="rowId"></param> /// <param name="version"></param> /// <param name="data"></param> public void GetData(RowId rowId, int version, out TRecord data) { if (version != 0) { this.ValidateVersion(version); } int pointIndex = this.snap.Count - 1; ValueList <Snap> .Address snapAddress = this.snap.ItemAddress(pointIndex); while (version < snapAddress.Page[snapAddress.Index].Version) { snapAddress = this.snap.ItemAddress(--pointIndex); } if (!(0 <= rowId.Value && rowId.Value < snapAddress.Page[snapAddress.Index].TableSize)) { throw new ArgumentOutOfRangeException(nameof(rowId)); } // cache log size here. the original can only grow in time int logSize = snapAddress.Page[snapAddress.Index].LogSize; ValueList <Row> .Address rowAddress = this.table.ItemAddress(rowId.Value); if (rowAddress.Page[rowAddress.Index].LogIndex < logSize) { // so the latest version of data was requested. data = rowAddress.Page[rowAddress.Index].Data; bool isDeleted = rowAddress.Page[rowAddress.Index].IsDeleted; LockFreeSync.ReadBarrier(); // check if the row is still of the latest version if (rowAddress.Page[rowAddress.Index].LogIndex < logSize) { // if it is still latest return. if (isDeleted) { data = default; throw new ArgumentOutOfRangeException(nameof(rowId)); } return; } } // older version of the row requested. The data is in the log. Log is immutable so easy to read. ValueList <Log> .Address logAddress = this.log.ItemAddress(rowAddress.Page[rowAddress.Index].LogIndex); while (logSize <= logAddress.Page[logAddress.Index].LogIndex) { Debug.Assert(0 < logAddress.Page[logAddress.Index].LogIndex, "Log entry at 0 does not contain any real data and used as a stub"); logAddress = this.log.ItemAddress(logAddress.Page[logAddress.Index].LogIndex); } if (logAddress.Page[logAddress.Index].IsDeleted) { data = default; throw new ArgumentOutOfRangeException(nameof(rowId)); } data = logAddress.Page[logAddress.Index].Data; }
private string BuildDebuggingVisualization(int version) { System.Text.StringBuilder text = new System.Text.StringBuilder(); RowId root = this.Root(version); int level = 0; while (this.BuildDebuggingVisualization(text, version, level, root)) { text.AppendLine(); level++; } return(text.ToString()); }
private Key Maximum(RowId rowId, int version) { Node node; for (;;) { this.table.GetData(rowId, version, out node); if (node.IsLeaf) { return(this.keyFields[node.Count - 1].GetValue(ref node)); } rowId = this.childFields[node.Count].GetValue(ref node); } }
private Key Minimum(RowId rowId, int version) { Node node; for (;;) { this.table.GetData(rowId, version, out node); if (node.IsLeaf) { return(node.K0); } rowId = node.C0; } }
/// <summary> /// Sets entire structure. /// </summary> /// <param name="rowId"></param> /// <param name="data"></param> /// <returns></returns> public bool SetData(RowId rowId, ref TRecord data) { Debug.Assert(0 <= rowId.Value && rowId.Value < this.table.Count, "broken rowId"); this.ValidateModification(); ValueList <Row> .Address address = this.table.ItemAddress(rowId.Value); SnapTable <TRecord> .ValidateModification(ref address); if (TableSnapshot <TRecord> .Compare(this.Fields, ref address.Page[address.Index].Data, ref data) != 0) { this.PushToLog(ref address.Page[address.Index], rowId); address.Page[address.Index].Data = data; Debug.Assert(TableSnapshot <TRecord> .Compare(this.Fields, ref address.Page[address.Index].Data, ref data) == 0, "Assignment or comparison failed"); return(true); } return(false); }
public bool Remove(TField value, RowId rowId) { Key key = new Key() { field = value, rowId = rowId }; RowId rootId = this.Root(this.table.SnapStore.Version); bool deleted = this.Delete(rootId, key); #if ValidateTree this.Validate(); #endif return(deleted); }
private void Rehash(int newSize) { int version = this.SnapStore.Version; int oldSize = this.Size(version); Debug.Assert(oldSize <= newSize); int count = this.Count(version); List <KeyValuePair <TField, RowId> > list = new List <KeyValuePair <TField, RowId> >(count); Bucket empty = new Bucket(); for (int i = 0; i < oldSize; i++) { Bucket bucket; this.table.GetLatestData(new RowId(i), out bucket); if (!bucket.IsFree) { list.Add(new KeyValuePair <TField, RowId>(bucket.Value, bucket.RowId)); } if (bucket.IsDirty) { this.table.SetData(new RowId(i), ref empty); } } int currentSize = Math.Min(newSize, this.table.LatestCount()); for (int i = oldSize; i < currentSize; i++) { RowId rowId = new RowId(i); if (this.table.IsLatestDeleted(rowId)) { this.table.UnDelete(rowId); } this.table.SetData(rowId, ref empty); } for (int i = currentSize; i < newSize; i++) { this.table.Insert(ref empty); } this.SetSize(newSize); this.SetCount(0); this.SetOccupancy(0); foreach (KeyValuePair <TField, RowId> pair in list) { this.Insert(pair.Key, pair.Value); } Debug.Assert(count == this.Count(version), "Count been changed by rehashing"); }
public SnapTableAction Action(RowId rowId) { SnapTableAction action = this.enumerator.Action(rowId); switch (action) { case SnapTableAction.Insert: return(SnapTableAction.Delete); case SnapTableAction.Delete: return(SnapTableAction.Insert); case SnapTableAction.Update: return(SnapTableAction.Update); default: Debug.Fail("Unknown action"); throw new InvalidOperationException(); } }
/// <summary> /// Updates field of the row /// </summary> /// <typeparam name="TField"></typeparam> /// <param name="rowId"></param> /// <param name="field"></param> /// <param name="value"></param> /// <returns></returns> public bool SetField <TField>(RowId rowId, IField <TRecord, TField> field, TField value) { Debug.Assert(0 <= rowId.Value && rowId.Value < this.table.Count, "broken rowId"); this.ValidateModification(); // It is only possible to set value via basic fields defined on table. this.ValidateField(field); ValueList <Row> .Address address = this.table.ItemAddress(rowId.Value); SnapTable <TRecord> .ValidateModification(ref address); if (field.Compare(field.GetValue(ref address.Page[address.Index].Data), value) != 0) { this.PushToLog(ref address.Page[address.Index], rowId); field.SetValue(ref address.Page[address.Index].Data, value); Debug.Assert(field.Compare(field.GetValue(ref address.Page[address.Index].Data), value) == 0, "Assignment or comparison failed"); return(true); } return(false); }
/// <summary> /// Inserts new row in the table /// </summary> /// <param name="data">data to be inserted</param> /// <returns>index of the new row</returns> public RowId Insert(ref TRecord data) { this.ValidateModification(); if (int.MaxValue - 1 <= this.table.Count) { throw new InvalidOperationException(Properties.Resources.ErrorTableTooBig(this.Name)); } ValueList <Snap> .Address snapAddress = this.snap.ItemAddress(this.snap.Count - 1); bool firstChange = (snapAddress.Page[snapAddress.Index].Version < this.SnapStore.Version); Debug.Assert(firstChange || snapAddress.Page[snapAddress.Index].Version == this.SnapStore.Version, "Impossible state: this should be the current transaction"); Debug.Assert(firstChange || snapAddress.Page[snapAddress.Index].TableSize == this.table.Count, "Impossible state: wrong table size"); Debug.Assert(firstChange || snapAddress.Page[snapAddress.Index].LogSize == this.log.Count, "Impossible state: wrong log size"); this.table.PrepareAdd(); if (firstChange) { this.snap.PrepareAdd(); } RowId rowId; RuntimeHelpers.PrepareConstrainedRegions(); try {} finally { rowId = new RowId(this.table.FixedAllocate()); ValueList <Row> .Address rowAddress = this.table.ItemAddress(rowId.Value); rowAddress.Page[rowAddress.Index].Data = data; if (firstChange) { // this is the first change in this transaction Snap point = new Snap() { Version = this.SnapStore.Version, TableSize = this.table.Count, LogSize = this.log.Count }; this.snap.FixedAdd(ref point); } else { // this transaction already altered this table snapAddress.Page[snapAddress.Index].TableSize = this.table.Count; } } return(rowId); }
private void DeleteRow(RowId rowId) { int timestamp = TableSnapshot <TRecord> .Timestamp(); foreach (List <IIndex <TRecord> > list in this.table.Indexes) { if (list != null) { foreach (IIndex <TRecord> index in list) { if (index.Timestamp != timestamp) { index.Delete(rowId); index.Timestamp = timestamp; } } } } this.table.Delete(rowId); }
/// <summary> /// Reverts changes made in the provided transaction. Intended to be used in undo/redo. /// Warning! Assumes that higher stack functionality is responsible for providing correct version number, /// so there no gaps between versions in undo/redo operations /// </summary> /// <param name="version">Transaction to be reverted</param> public void Revert(int version) { this.ValidateModification(); this.ValidateVersion(version); int pointIndex = this.snap.Count - 1; ValueList <Snap> .Address snapAddress = this.snap.ItemAddress(pointIndex); while (version < snapAddress.Page[snapAddress.Index].Version) { snapAddress = this.snap.ItemAddress(--pointIndex); } if (version == snapAddress.Page[snapAddress.Index].Version) { int tableEnd = snapAddress.Page[snapAddress.Index].TableSize; int logEnd = snapAddress.Page[snapAddress.Index].LogSize; snapAddress = this.snap.ItemAddress(--pointIndex); int tableStart = snapAddress.Page[snapAddress.Index].TableSize; int logStart = snapAddress.Page[snapAddress.Index].LogSize; for (int i = tableStart; i < tableEnd; i++) { ValueList <Row> .Address rowAddress = this.table.ItemAddress(i); if (rowAddress.Page[rowAddress.Index].IsValid) { this.PushToLog(ref rowAddress.Page[rowAddress.Index], new RowId(i)); rowAddress.Page[rowAddress.Index].IsDeleted = true; Debug.Assert(rowAddress.Page[rowAddress.Index].IsValid, "Invalid state: row with a history of changes can't be invalid"); } } for (int i = logStart; i < logEnd; i++) { ValueList <Log> .Address logAddress = this.log.ItemAddress(i); RowId rowId = logAddress.Page[logAddress.Index].RowId; Debug.Assert(rowId.Value < tableStart, "Invalid rowId: changes are only possible to rows that already in the table, not just inserted ones"); ValueList <Row> .Address rowAddress = this.table.ItemAddress(rowId.Value); this.PushToLog(ref rowAddress.Page[rowAddress.Index], rowId); rowAddress.Page[rowAddress.Index].Data = logAddress.Page[logAddress.Index].Data; rowAddress.Page[rowAddress.Index].IsDeleted = logAddress.Page[logAddress.Index].IsDeleted; } } }
/// <summary> /// Deletes row from table /// </summary> /// <param name="rowId"></param> public void Delete(RowId rowId) { this.ValidateModification(); if (this.table.PrimaryKey != null) { HashSet <ChildRow> deleteList = new HashSet <ChildRow>(); deleteList.Add(new ChildRow(this, rowId)); foreach (IForeignKey fk in this.table.Children) { fk.OnParentDelete(rowId, deleteList); } foreach (ChildRow childRow in deleteList) { childRow.Table.DeleteRow(childRow.RowId); } } else { this.DeleteRow(rowId); } }
public RowId Insert(ref TRecord data) { this.ValidateModification(); RowId rowId = this.table.Insert(ref data); int timestamp = TableSnapshot <TRecord> .Timestamp(); foreach (List <IIndex <TRecord> > list in this.table.Indexes) { if (list != null) { foreach (IIndex <TRecord> index in list) { if (index.Timestamp != timestamp) { index.Insert(rowId); index.Timestamp = timestamp; } } } } return(rowId); }
private void SplitChild(RowId rowId, int child) { Node node; this.table.GetLatestData(rowId, out node); Debug.Assert(node.Count < this.keyFields.Length, "The node should be none full"); // make a room for a new key that will bubble up from child node for (int i = node.Count - 1; child <= i; i--) { this.keyFields[i + 1].SetValue(ref node, this.keyFields[i].GetValue(ref node)); } // make a room for a new child for (int i = node.Count; child < i; i--) { this.childFields[i + 1].SetValue(ref node, this.childFields[i].GetValue(ref node)); } RowId childId = this.childFields[child].GetValue(ref node); Node oldChild; this.table.GetLatestData(childId, out oldChild); Debug.Assert(oldChild.Count == this.keyFields.Length, "The node should be full in order to be split"); Node newChild = new Node() { IsLeaf = oldChild.IsLeaf }; this.Move(ref oldChild, this.MinDegree + 1, ref newChild); oldChild.Count--; Debug.Assert(oldChild.Count == newChild.Count && newChild.Count == this.MinDegree); node.Count++; this.keyFields[child].SetValue(ref node, this.keyFields[this.MinDegree].GetValue(ref oldChild)); this.keyFields[this.MinDegree].SetValue(ref oldChild, default(Key)); this.childFields[child + 1].SetValue(ref node, this.table.Insert(ref newChild)); this.table.SetData(childId, ref oldChild); this.table.SetData(rowId, ref node); }
private void InsertNoneFull(RowId rowId, Key key) { Node node; this.table.GetLatestData(rowId, out node); Debug.Assert(node.Count < this.keyFields.Length, "Node should be none full to perform the InserNoneFull"); int i = node.Count - 1; if (node.IsLeaf) { while (0 <= i && this.keyFields[i].Compare(key, this.keyFields[i].GetValue(ref node)) < 0) { this.keyFields[i + 1].SetValue(ref node, this.keyFields[i].GetValue(ref node)); i--; } this.keyFields[i + 1].SetValue(ref node, key); node.Count++; this.table.SetData(rowId, ref node); } else { while (0 <= i && this.keyFields[i].Compare(key, this.keyFields[i].GetValue(ref node)) < 0) { i--; } i++; RowId childId = this.childFields[i].GetValue(ref node); if (this.table.GetLatestField <int>(childId, this.countField) == this.keyFields.Length) { this.SplitChild(rowId, i); if (this.keyFields[i].Compare(key, this.table.GetLatestField <Key>(rowId, this.keyFields[i])) > 0) { i++; } } this.InsertNoneFull(this.table.GetLatestField <RowId>(rowId, this.childFields[i]), key); } }
/// <summary> /// Rolls back current transaction /// </summary> public void Rollback() { this.ValidateModification(); ValueList <Snap> .Address snapAddress = this.snap.ItemAddress(this.snap.Count - 1); if (snapAddress.Page[snapAddress.Index].Version == this.SnapStore.Version) { Debug.Assert(1 < this.snap.Count, "Only real transactions can be rolled back"); // this table was modified in this transaction // it is impossible to completely rollback current changes, so just get old data back and delete new rows and undelete deleted ones. int tableEnd = snapAddress.Page[snapAddress.Index].TableSize; int logEnd = snapAddress.Page[snapAddress.Index].LogSize; if (tableEnd != this.table.Count || logEnd != this.log.Count) { // if rolling back due to exception then ensure snap showing right data. snapAddress.Page[snapAddress.Index].TableSize = tableEnd = this.table.Count; snapAddress.Page[snapAddress.Index].LogSize = logEnd = this.log.Count; } snapAddress = this.snap.ItemAddress(this.snap.Count - 2); int tableStart = snapAddress.Page[snapAddress.Index].TableSize; int logStart = snapAddress.Page[snapAddress.Index].LogSize; for (int i = tableStart; i < tableEnd; i++) { ValueList <Row> .Address rowAddress = this.table.ItemAddress(i); rowAddress.Page[rowAddress.Index].IsDeleted = true; // Row must be freshly inserted but can be deleted in the same transaction, so check here that it is invalid now. Debug.Assert(!rowAddress.Page[rowAddress.Index].IsValid, "Invalid state: row should be freshly inserted one"); } for (int i = logStart; i < logEnd; i++) { ValueList <Log> .Address logAddress = this.log.ItemAddress(i); RowId rowId = logAddress.Page[logAddress.Index].RowId; Debug.Assert(rowId.Value < tableStart, "Invalid rowId: changes are only possible to rows that already in the table, not just inserted ones"); ValueList <Row> .Address rowAddress = this.table.ItemAddress(rowId.Value); rowAddress.Page[rowAddress.Index].Data = logAddress.Page[logAddress.Index].Data; rowAddress.Page[rowAddress.Index].IsDeleted = logAddress.Page[logAddress.Index].IsDeleted; } } }
public bool Remove(TField value) { int version = this.SnapStore.Version; Hash hash = new Hash(value, this.Size(version)); for (int i = 0; i < hash.Size; i++) { RowId bucketIndex = hash.Bucket(i); Bucket bucket; this.table.GetLatestData(bucketIndex, out bucket); if (bucket.IsEmpty) { return(false); } if (!bucket.IsFree && bucket.Hash == hash.Code && this.valueField.Compare(bucket.Value, value) == 0) { this.table.SetField <RowId>(bucketIndex, RowIdField.Field, bucket.HasCollision ? Bucket.Deleted : Bucket.Empty); this.SetCount(this.Count(version) - 1); return(true); } } return(false); }