//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Microsoft
// Microsoft
//------------------------------------------------------------------------------
namespace System.Data {
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Threading;
internal struct IndexField {
public readonly DataColumn Column;
public readonly bool IsDescending; // false = Asc; true = Desc what is default value for this?
internal IndexField(DataColumn column, bool isDescending) {
Debug.Assert(column != null, "null column");
Column = column;
IsDescending = isDescending;
}
public static bool operator == (IndexField if1, IndexField if2) {
return if1.Column == if2.Column && if1.IsDescending == if2.IsDescending;
}
public static bool operator !=(IndexField if1, IndexField if2) {
return !(if1 == if2);
}
// must override Equals if == operator is defined
public override bool Equals(object obj) {
if (obj is IndexField)
return this == (IndexField)obj;
else
return false;
}
// must override GetHashCode if Equals is redefined
public override int GetHashCode() {
return Column.GetHashCode() ^ IsDescending.GetHashCode();
}
}
internal sealed class Index {
private sealed class IndexTree : RBTree {
private readonly Index _index;
internal IndexTree(Index index) : base(TreeAccessMethod.KEY_SEARCH_AND_INDEX) {
_index = index;
}
protected override int CompareNode (int record1, int record2) {
return _index.CompareRecords(record1, record2);
}
protected override int CompareSateliteTreeNode (int record1, int record2) {
return _index.CompareDuplicateRecords(record1, record2);
}
}
// these constants are used to update a DataRow when the record and Row are known, but don't match
private const int DoNotReplaceCompareRecord = 0;
private const int ReplaceNewRecordForCompare = 1;
private const int ReplaceOldRecordForCompare = 2;
private readonly DataTable table;
internal readonly IndexField[] IndexFields;
/// Allow a user implemented comparision of two DataRow
/// User must use correct DataRowVersion in comparison or index corruption will happen
private readonly System.Comparison _comparison;
private readonly DataViewRowState recordStates;
private WeakReference rowFilter;
private IndexTree records;
private int recordCount;
private int refCount;
private Listeners _listeners;
private bool suspendEvents;
private readonly static object[] zeroObjects = new object[0];
private readonly bool isSharable;
private readonly bool _hasRemoteAggregate;
internal const Int32 MaskBits = unchecked((int)0x7FFFFFFF);
private static int _objectTypeCount; // Bid counter
private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
public Index(DataTable table, IndexField[] indexFields, DataViewRowState recordStates, IFilter rowFilter)
: this(table, indexFields, null, recordStates, rowFilter) { }
public Index(DataTable table, System.Comparison comparison, DataViewRowState recordStates, IFilter rowFilter)
: this(table, GetAllFields(table.Columns), comparison, recordStates, rowFilter) { }
// for the delegate methods, we don't know what the dependent columns are - so all columns are dependent
private static IndexField[] GetAllFields(DataColumnCollection columns) {
IndexField[] fields = new IndexField[columns.Count];
for(int i = 0; i < fields.Length; ++i) {
fields[i] = new IndexField(columns[i], false);
}
return fields;
}
private Index(DataTable table, IndexField[] indexFields, System.Comparison comparison, DataViewRowState recordStates, IFilter rowFilter) {
Bid.Trace(" %d#, table=%d, recordStates=%d{ds.DataViewRowState}\n",
ObjectID, (table != null) ? table.ObjectID : 0, (int)recordStates);
Debug.Assert(indexFields != null);
Debug.Assert(null != table, "null table");
if ((recordStates &
(~(DataViewRowState.CurrentRows | DataViewRowState.OriginalRows))) != 0) {
throw ExceptionBuilder.RecordStateRange();
}
this.table = table;
_listeners = new Listeners(ObjectID,
delegate(DataViewListener listener)
{
return (null != listener);
});
IndexFields = indexFields;
this.recordStates = recordStates;
_comparison = comparison;
DataColumnCollection columns = table.Columns;
isSharable = (rowFilter == null) && (comparison == null); // a filter or comparison make an index unsharable
if (null != rowFilter) {
this.rowFilter = new WeakReference(rowFilter);
DataExpression expr = (rowFilter as DataExpression);
if (null != expr) {
_hasRemoteAggregate = expr.HasRemoteAggregate();
}
}
InitRecords(rowFilter);
// do not AddRef in ctor, every caller should be responsible to AddRef it
// if caller does not AddRef, it is expected to be a one-time read operation because the index won't be maintained on writes
}
public bool Equal(IndexField[] indexDesc, DataViewRowState recordStates, IFilter rowFilter) {
if (
!isSharable ||
IndexFields.Length != indexDesc.Length ||
this.recordStates != recordStates ||
null != rowFilter
) {
return false;
}
for (int loop = 0; loop < IndexFields.Length; loop++) {
if (IndexFields[loop].Column!= indexDesc[loop].Column ||
IndexFields[loop].IsDescending != indexDesc[loop].IsDescending) {
return false;
}
}
return true;
}
internal bool HasRemoteAggregate {
get {
return _hasRemoteAggregate;
}
}
internal int ObjectID {
get {
return _objectID;
}
}
public DataViewRowState RecordStates {
get { return recordStates; }
}
public IFilter RowFilter {
get { return (IFilter)((null != rowFilter) ? rowFilter.Target : null); }
}
public int GetRecord(int recordIndex) {
Debug.Assert (recordIndex >= 0 && recordIndex < recordCount, "recordIndex out of range");
return records[recordIndex];
}
public bool HasDuplicates {
get {
return records.HasDuplicates;
}
}
public int RecordCount {
get {
return recordCount;
}
}
public bool IsSharable {
get {
return isSharable;
}
}
private bool AcceptRecord(int record) {
return AcceptRecord(record, RowFilter);
}
private bool AcceptRecord(int record, IFilter filter) {
Bid.Trace(" %d#, record=%d\n", ObjectID, record);
if (filter == null)
return true;
DataRow row = table.recordManager[record];
if (row == null)
return true;
//
DataRowVersion version = DataRowVersion.Default;
if (row.oldRecord == record) {
version = DataRowVersion.Original;
}
else if (row.newRecord == record) {
version = DataRowVersion.Current;
}
else if (row.tempRecord == record) {
version = DataRowVersion.Proposed;
}
return filter.Invoke(row, version);
}
/// Only call from inside a lock(this)
internal void ListChangedAdd(DataViewListener listener) {
_listeners.Add(listener);
}
/// Only call from inside a lock(this)
internal void ListChangedRemove(DataViewListener listener) {
_listeners.Remove(listener);
}
public int RefCount {
get {
return refCount;
}
}
public void AddRef() {
Bid.Trace(" %d#\n", ObjectID);
LockCookie lc = table.indexesLock.UpgradeToWriterLock(-1);
try {
Debug.Assert(0 <= refCount, "AddRef on disposed index");
Debug.Assert(null != records, "null records");
if (refCount == 0) {
table.ShadowIndexCopy();
table.indexes.Add(this);
}
refCount++;
}
finally {
table.indexesLock.DowngradeFromWriterLock(ref lc);
}
}
public int RemoveRef() {
Bid.Trace(" %d#\n", ObjectID);
int count;
LockCookie lc = table.indexesLock.UpgradeToWriterLock(-1);
try {
count = --refCount;
if (refCount <= 0) {
table.ShadowIndexCopy();
table.indexes.Remove(this);
}
}
finally {
table.indexesLock.DowngradeFromWriterLock(ref lc);
}
return count;
}
private void ApplyChangeAction(int record, int action, int changeRecord) {
if (action != 0) {
if (action > 0) {
if (AcceptRecord(record)) {
InsertRecord(record, true);
}
}
else if ((null != _comparison) && (-1 != record)) {
// when removing a record, the DataRow has already been updated to the newer record
// depending on changeRecord, either the new or old record needs be backdated to record
// for Comparison to operate correctly
DeleteRecord(GetIndex(record, changeRecord));
}
else {
// unnecessary codepath other than keeping original code path for redbits
DeleteRecord(GetIndex(record));
}
}
}
public bool CheckUnique() {
#if DEBUG
Debug.Assert(records.CheckUnique(records.root) != HasDuplicates, "CheckUnique difference");
#endif
return !HasDuplicates;
}
// only used for main tree compare, not satalite tree
private int CompareRecords(int record1, int record2) {
if (null != _comparison) {
return CompareDataRows(record1, record2);
}
if (0 < IndexFields.Length) {
for (int i = 0; i < IndexFields.Length; i++) {
int c = IndexFields[i].Column.Compare(record1, record2);
if (c != 0) {
return (IndexFields[i].IsDescending ? -c : c);
}
}
return 0;
}
else {
Debug.Assert(null != table.recordManager[record1], "record1 no datarow");
Debug.Assert(null != table.recordManager[record2], "record2 no datarow");
// DataRow needs to always updated appropriately via GetIndex(int,int)
//table.recordManager.VerifyRecord(record1, table.recordManager[record1]);
//table.recordManager.VerifyRecord(record2, table.recordManager[record2]);
// Need to use compare because subtraction will wrap
// to positive for very large neg numbers, etc.
return table.Rows.IndexOf(table.recordManager[record1]).CompareTo(table.Rows.IndexOf(table.recordManager[record2]));
}
}
private int CompareDataRows(int record1, int record2)
{
table.recordManager.VerifyRecord(record1, table.recordManager[record1]);
table.recordManager.VerifyRecord(record2, table.recordManager[record2]);
return _comparison(table.recordManager[record1], table.recordManager[record2]);
}
// PS: same as previous CompareRecords, except it compares row state if needed
// only used for satalite tree compare
private int CompareDuplicateRecords(int record1, int record2) {
#if DEBUG
if (null != _comparison) {
Debug.Assert(0 == CompareDataRows(record1, record2), "duplicate record not a duplicate by user function");
}
else if (record1 != record2) {
for (int i = 0; i < IndexFields.Length; i++) {
int c = IndexFields[i].Column.Compare(record1, record2);
Debug.Assert(0 == c, "duplicate record not a duplicate");
}
}
#endif
Debug.Assert(null != table.recordManager[record1], "record1 no datarow");
Debug.Assert(null != table.recordManager[record2], "record2 no datarow");
// DataRow needs to always updated appropriately via GetIndex(int,int)
//table.recordManager.VerifyRecord(record1, table.recordManager[record1]);
//table.recordManager.VerifyRecord(record2, table.recordManager[record2]);
if (null == table.recordManager[record1]) {
return ((null == table.recordManager[record2]) ? 0 : -1);
}
else if (null == table.recordManager[record2]) {
return 1;
}
// Need to use compare because subtraction will wrap
// to positive for very large neg numbers, etc.
int diff = table.recordManager[record1].rowID.CompareTo(table.recordManager[record2].rowID);
// if they're two records in the same row, we need to be able to distinguish them.
if ((diff == 0) && (record1 != record2)) {
diff = ((int)table.recordManager[record1].GetRecordState(record1)).CompareTo((int)table.recordManager[record2].GetRecordState(record2));
}
return diff;
}
private int CompareRecordToKey(int record1, object[] vals) {
for (int i = 0; i < IndexFields.Length; i++) {
int c = IndexFields[i].Column.CompareValueTo(record1, vals[i]);
if (c != 0) {
return (IndexFields[i].IsDescending ? -c : c);
}
}
return 0;
}
// DeleteRecordFromIndex deletes the given record from index and does not fire any Event. IT SHOULD NOT FIRE EVENT
// I added this since I can not use existing DeleteRecord which is not silent operation
public void DeleteRecordFromIndex(int recordIndex) { // this is for expression use, to maintain expression columns's sort , filter etc. do not fire event
DeleteRecord(recordIndex, false);
}
// old and existing DeleteRecord behavior, we can not use this for silently deleting
private void DeleteRecord(int recordIndex) {
DeleteRecord(recordIndex, true);
}
private void DeleteRecord(int recordIndex, bool fireEvent) {
Bid.Trace(" %d#, recordIndex=%d, fireEvent=%d{bool}\n", ObjectID, recordIndex, fireEvent);
if (recordIndex >= 0) {
recordCount--;
int record = records.DeleteByIndex(recordIndex);
MaintainDataView(ListChangedType.ItemDeleted, record, !fireEvent);
if (fireEvent) {
// 1) Webdata 104939 do not fix this, it would be breaking change
// 2) newRecord = -1, oldrecord = recordIndex;
OnListChanged(ListChangedType.ItemDeleted, recordIndex);
}
}
}
// SQLBU 428961: Serious performance issue when creating DataView
// this improves performance by allowing DataView to iterating instead of computing for records over index
// this will also allow Linq over DataSet to enumerate over the index
// avoid boxing by returning RBTreeEnumerator (a struct) instead of IEnumerator
public RBTree.RBTreeEnumerator GetEnumerator(int startIndex) {
return new IndexTree.RBTreeEnumerator(records, startIndex);
}
// What it actually does is find the index in the records[] that
// this record inhabits, and if it doesn't, suggests what index it would
// inhabit while setting the high bit.
//
public int GetIndex(int record) {
int index = records.GetIndexByKey(record);
return index;
}
///
/// When searching by value for a specific record, the DataRow may require backdating to reflect the appropriate state
/// otherwise on Delete of a DataRow in the Added state, would result in the where the row
/// reflection record would be in the Detatched instead of Added state.
///
private int GetIndex(int record, int changeRecord) {
Debug.Assert(null != _comparison, "missing comparison");
int index;
DataRow row = table.recordManager[record];
int a = row.newRecord;
int b = row.oldRecord;
try {
switch(changeRecord) {
case ReplaceNewRecordForCompare:
row.newRecord = record;
break;
case ReplaceOldRecordForCompare:
row.oldRecord = record;
break;
}
table.recordManager.VerifyRecord(record, row);
index = records.GetIndexByKey(record);
}
finally {
switch(changeRecord) {
case ReplaceNewRecordForCompare:
Debug.Assert(record == row.newRecord, "newRecord has change during GetIndex");
row.newRecord = a;
break;
case ReplaceOldRecordForCompare:
Debug.Assert(record == row.oldRecord, "oldRecord has change during GetIndex");
row.oldRecord = b;
break;
}
#if DEBUG
if (-1 != a) {
table.recordManager.VerifyRecord(a, row);
}
#endif
}
return index;
}
public object[] GetUniqueKeyValues() {
if (IndexFields == null || IndexFields.Length == 0) {
return zeroObjects;
}
List