1 //------------------------------------------------------------------------------
2 // <copyright file="RecordManager.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8 
9 namespace System.Data {
10     using System;
11     using System.Collections.Generic;
12     using System.Diagnostics;
13 
14     internal sealed class RecordManager {
15         private readonly DataTable table;
16 
17         private int lastFreeRecord;
18         private int minimumCapacity = 50;
19         private int recordCapacity = 0;
20         private readonly List<int> freeRecordList = new List<int>();
21 
22         DataRow[] rows;
23 
RecordManager(DataTable table)24         internal RecordManager(DataTable table) {
25             if (table == null) {
26                 throw ExceptionBuilder.ArgumentNull("table");
27             }
28             this.table = table;
29         }
30 
GrowRecordCapacity()31         private void GrowRecordCapacity() {
32             if (NewCapacity(recordCapacity) < NormalizedMinimumCapacity(minimumCapacity))
33                 RecordCapacity = NormalizedMinimumCapacity(minimumCapacity);
34             else
35                 RecordCapacity = NewCapacity(recordCapacity);
36 
37             // set up internal map : record --> row
38             DataRow[] newRows = table.NewRowArray(recordCapacity);
39             if (rows != null) {
40                 Array.Copy(rows, 0, newRows, 0, Math.Min(lastFreeRecord, rows.Length));
41             }
42             rows = newRows;
43         }
44 
45         internal int LastFreeRecord {
46             get { return lastFreeRecord; }
47         }
48 
49         internal int MinimumCapacity {
50             get {
51                 return minimumCapacity;
52             }
53             set {
54                 if (minimumCapacity != value) {
55                     if (value < 0) {
56                         throw ExceptionBuilder.NegativeMinimumCapacity();
57                     }
58                     minimumCapacity = value;
59                 }
60             }
61         }
62 
63         internal int RecordCapacity {
64             get {
65                 return recordCapacity;
66             }
67             set {
68                 if (recordCapacity != value) {
69                     for (int i = 0; i < table.Columns.Count; i++) {
70                         table.Columns[i].SetCapacity(value);
71                     }
72                     recordCapacity = value;
73                 }
74             }
75         }
76 
NewCapacity(int capacity)77         internal static int NewCapacity(int capacity) {
78             return (capacity < 128) ? 128 : (capacity + capacity);
79         }
80 
81         // Normalization: 64, 256, 1024, 2k, 3k, ....
NormalizedMinimumCapacity(int capacity)82         private int NormalizedMinimumCapacity(int capacity) {
83             if (capacity < 1024 - 10) {
84                 if (capacity < 256 - 10) {
85                     if ( capacity < 54 )
86                         return 64;
87                     return 256;
88                 }
89                 return 1024;
90             }
91 
92             return (((capacity + 10) >> 10) + 1) << 10;
93         }
NewRecordBase()94         internal int NewRecordBase() {
95             int record;
96             if (freeRecordList.Count != 0) {
97                 record = freeRecordList[freeRecordList.Count - 1];
98                 freeRecordList.RemoveAt(freeRecordList.Count - 1);
99             }
100             else {
101                 if (lastFreeRecord >= recordCapacity) {
102                     GrowRecordCapacity();
103                 }
104                 record = lastFreeRecord;
105                 lastFreeRecord++;
106             }
107             Debug.Assert(record >=0 && record < recordCapacity, "NewRecord: Invalid record");
108             return record;
109         }
110 
FreeRecord(ref int record)111         internal void FreeRecord(ref int record) {
112             Debug.Assert(-1 <= record && record < recordCapacity, "invalid record");
113 //            Debug.Assert(record < lastFreeRecord, "Attempt to Free() <outofbounds> record");
114             if (-1 != record) {
115                 this[record] = null;
116 
117                 int count = table.columnCollection.Count;
118                 for(int i = 0; i < count; ++i) {
119                     table.columnCollection[i].FreeRecord(record);
120                 }
121 
122                 // if freeing the last record, recycle it
123                 if (lastFreeRecord == record + 1) {
124                     lastFreeRecord--;
125                 }
126                 else if (record < lastFreeRecord) {
127 //                    Debug.Assert(-1 == freeRecordList.IndexOf(record), "Attempt to double Free() record");
128                     freeRecordList.Add(record);
129                 }
130                 record = -1;
131             }
132         }
133 
Clear(bool clearAll)134         internal void Clear(bool clearAll) {
135             if (clearAll) {
136                 for(int record = 0; record < recordCapacity; ++record) {
137                     rows[record] = null;
138                 }
139                 int count = table.columnCollection.Count;
140                 for(int i = 0; i < count; ++i) {
141                     // SQLBU 415729: Serious performance issue when calling Clear()
142                     // this improves performance by caching the column instead of obtaining it for each row
143                     DataColumn column = table.columnCollection[i];
144                     for(int record = 0; record < recordCapacity; ++record) {
145                         column.FreeRecord(record);
146                     }
147                 }
148                 lastFreeRecord = 0;
149                 freeRecordList.Clear();
150             }
151             else { // just clear attached rows
152                 freeRecordList.Capacity = freeRecordList.Count + table.Rows.Count;
153                 for(int record = 0; record < recordCapacity; ++record) {
154                     if (rows[record]!= null && rows[record].rowID != -1) {
155                         int tempRecord = record;
156                         FreeRecord(ref tempRecord);
157                     }
158                 }
159             }
160         }
161 
162         internal DataRow this[int record] {
163             get {
164                 Debug.Assert(record >= 0 && record < rows.Length, "Invalid record number");
165                 return rows[record];
166             }
167             set {
168                 Debug.Assert(record >= 0 && record < rows.Length, "Invalid record number");
169                 rows[record] = value;
170             }
171         }
172 
SetKeyValues(int record, DataKey key, object[] keyValues)173         internal void SetKeyValues(int record, DataKey key, object[] keyValues) {
174             for (int i = 0; i < keyValues.Length; i++) {
175                 key.ColumnsReference[i][record] = keyValues[i];
176             }
177         }
178 
179         // Increases AutoIncrementCurrent
ImportRecord(DataTable src, int record)180         internal int ImportRecord(DataTable src, int record) {
181             return CopyRecord(src, record, -1);
182         }
183 
184         // No impact on AutoIncrementCurrent if over written
CopyRecord(DataTable src, int record, int copy)185         internal int CopyRecord(DataTable src, int record, int copy) {
186             Debug.Assert(src != null, "Can not Merge record without a table");
187 
188             if (record == -1) {
189                 return copy;
190             }
191             int newRecord = -1;
192             try {
193                 if (copy == -1) {
194                     newRecord = table.NewUninitializedRecord();
195                 }
196                 else {
197                     newRecord = copy;
198                 }
199 
200                 int count = table.Columns.Count;
201                 for (int i = 0; i < count; ++i) {
202                     DataColumn dstColumn = table.Columns[i];
203                     DataColumn srcColumn = src.Columns[dstColumn.ColumnName];
204                     if (null != srcColumn) {
205                         object value = srcColumn[record];
206                         ICloneable cloneableObject = value as ICloneable;
207                         if (null != cloneableObject) {
208                             dstColumn[newRecord] = cloneableObject.Clone();
209                         }
210                         else {
211                             dstColumn[newRecord] = value;
212                         }
213                     }
214                     else if (-1 == copy) {
215                         dstColumn.Init(newRecord);
216                     }
217                 }
218             }
219             catch (Exception e){
220                 //
221                 if (Common.ADP.IsCatchableOrSecurityExceptionType(e)) {
222                     if (-1 == copy) {
223                         FreeRecord(ref newRecord);
224                     }
225                 }
226                 throw;
227             }
228             return newRecord;
229         }
230 
SetRowCache(DataRow[] newRows)231         internal void SetRowCache(DataRow[] newRows) {
232             rows = newRows;
233             lastFreeRecord = rows.Length;
234             recordCapacity = lastFreeRecord;
235         }
236 
237         [Conditional("DEBUG")]
VerifyRecord(int record)238         internal void VerifyRecord(int record) {
239             Debug.Assert((record < lastFreeRecord) && (-1 == freeRecordList.IndexOf(record)), "accesing free record");
240             Debug.Assert((null == rows[record]) ||
241                          (record == rows[record].oldRecord) ||
242                          (record == rows[record].newRecord) ||
243                          (record == rows[record].tempRecord), "record of a different row");
244         }
245 
246         [Conditional("DEBUG")]
VerifyRecord(int record, DataRow row)247         internal void VerifyRecord(int record, DataRow row) {
248             Debug.Assert((record < lastFreeRecord) && (-1 == freeRecordList.IndexOf(record)), "accesing free record");
249             Debug.Assert((null == rows[record]) || (row == rows[record]), "record of a different row");
250         }
251     }
252 }
253